add diskstats on Darwin (#593)
* Add diskstats collector for Darwin * Update year in the header * Update README.md * Add github.com/lufia/iostat to vendored packages * Change stats to follow naming guidelines * Add a entry of github.com/lufia/iostat into vendor.json * Remove /proc/diskstats from descriptionpull/616/head
parent
ab3414e6fd
commit
a077024f51
|
@ -24,7 +24,7 @@ Name | Description | OS
|
|||
arp | Exposes ARP statistics from `/proc/net/arp`. | Linux
|
||||
conntrack | Shows conntrack statistics (does nothing if no `/proc/sys/net/netfilter/` present). | Linux
|
||||
cpu | Exposes CPU statistics | Darwin, Dragonfly, FreeBSD, Linux
|
||||
diskstats | Exposes disk I/O statistics from `/proc/diskstats`. | Linux
|
||||
diskstats | Exposes disk I/O statistics. | Darwin, Linux
|
||||
edac | Exposes error detection and correction statistics. | Linux
|
||||
entropy | Exposes available entropy. | Linux
|
||||
exec | Exposes execution statistics. | Dragonfly, FreeBSD
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !nodiskstats
|
||||
|
||||
package collector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lufia/iostat"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
diskSubsystem = "disk"
|
||||
)
|
||||
|
||||
type typedDescFunc struct {
|
||||
typedDesc
|
||||
value func(stat *iostat.DriveStats) float64
|
||||
}
|
||||
|
||||
type diskstatsCollector struct {
|
||||
descs []typedDescFunc
|
||||
}
|
||||
|
||||
func init() {
|
||||
Factories["diskstats"] = NewDiskstatsCollector
|
||||
}
|
||||
|
||||
// NewDiskstatsCollector returns a new Collector exposing disk device stats.
|
||||
func NewDiskstatsCollector() (Collector, error) {
|
||||
var diskLabelNames = []string{"device"}
|
||||
|
||||
return &diskstatsCollector{
|
||||
descs: []typedDescFunc{
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "reads_completed_total"),
|
||||
"The total number of reads completed successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.NumRead)
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "read_sectors_total"),
|
||||
"The total number of sectors read successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.NumRead) / float64(stat.BlockSize)
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "read_seconds_total"),
|
||||
"The total number of seconds spent by all reads.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return stat.TotalReadTime.Seconds()
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "writes_completed_total"),
|
||||
"The total number of writes completed successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.NumWrite)
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "written_sectors_total"),
|
||||
"The total number of sectors written successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.NumWrite) / float64(stat.BlockSize)
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "write_seconds_total"),
|
||||
"This is the total number of seconds spent by all writes.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return stat.TotalWriteTime.Seconds()
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "read_bytes_total"),
|
||||
"The total number of bytes read successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.BytesRead)
|
||||
},
|
||||
},
|
||||
{
|
||||
typedDesc: typedDesc{
|
||||
desc: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(Namespace, diskSubsystem, "written_bytes_total"),
|
||||
"The total number of bytes written successfully.",
|
||||
diskLabelNames,
|
||||
nil,
|
||||
),
|
||||
valueType: prometheus.CounterValue,
|
||||
},
|
||||
value: func(stat *iostat.DriveStats) float64 {
|
||||
return float64(stat.BytesWritten)
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *diskstatsCollector) Update(ch chan<- prometheus.Metric) error {
|
||||
diskStats, err := iostat.ReadDriveStats()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't get diskstats: %s", err)
|
||||
}
|
||||
|
||||
for _, stats := range diskStats {
|
||||
for _, desc := range c.descs {
|
||||
v := desc.value(stats)
|
||||
ch <- desc.mustNewConstMetric(v, stats.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, kadota kyohei
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
|||
# iostat - reports I/O statistics
|
||||
|
||||
[](https://godoc.org/github.com/lufia/iostat)
|
||||
|
||||
*iostat* reports I/O statistics. currently supported OSes are:
|
||||
|
||||
* macOS
|
|
@ -0,0 +1,20 @@
|
|||
// Package iostat presents I/O statistics.
|
||||
package iostat
|
||||
|
||||
import "time"
|
||||
|
||||
// DriveStats represents I/O statistics of a drive.
|
||||
type DriveStats struct {
|
||||
Name string // drive name
|
||||
Size int64 // total drive size in bytes
|
||||
BlockSize int64 // block size in bytes
|
||||
|
||||
BytesRead int64
|
||||
BytesWritten int64
|
||||
NumRead int64
|
||||
NumWrite int64
|
||||
TotalReadTime time.Duration
|
||||
TotalWriteTime time.Duration
|
||||
ReadLatency time.Duration
|
||||
WriteLatency time.Duration
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
#include <stdint.h>
|
||||
#include "iostat_darwin.h"
|
||||
|
||||
#define IOKIT 1 /* to get io_name_t in device_types.h */
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/storage/IOBlockStorageDriver.h>
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#include <IOKit/IOBSD.h>
|
||||
|
||||
static int getdrivestat(io_registry_entry_t d, DriveStats *stat);
|
||||
static int fillstat(io_registry_entry_t d, DriveStats *stat);
|
||||
|
||||
int
|
||||
readdrivestat(DriveStats a[], int n)
|
||||
{
|
||||
mach_port_t port;
|
||||
CFMutableDictionaryRef match;
|
||||
io_iterator_t drives;
|
||||
io_registry_entry_t d;
|
||||
kern_return_t status;
|
||||
int na, rv;
|
||||
|
||||
IOMasterPort(bootstrap_port, &port);
|
||||
match = IOServiceMatching("IOMedia");
|
||||
CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
|
||||
status = IOServiceGetMatchingServices(port, match, &drives);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
|
||||
na = 0;
|
||||
while(na < n && (d=IOIteratorNext(drives)) > 0){
|
||||
rv = getdrivestat(d, &a[na]);
|
||||
if(rv < 0)
|
||||
return -1;
|
||||
if(rv > 0)
|
||||
na++;
|
||||
IOObjectRelease(d);
|
||||
}
|
||||
IOObjectRelease(drives);
|
||||
return na;
|
||||
}
|
||||
|
||||
static int
|
||||
getdrivestat(io_registry_entry_t d, DriveStats *stat)
|
||||
{
|
||||
io_registry_entry_t parent;
|
||||
kern_return_t status;
|
||||
CFDictionaryRef props;
|
||||
CFStringRef name;
|
||||
CFNumberRef num;
|
||||
int rv;
|
||||
|
||||
memset(stat, 0, sizeof *stat);
|
||||
status = IORegistryEntryGetParentEntry(d, kIOServicePlane, &parent);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
if(!IOObjectConformsTo(parent, "IOBlockStorageDriver")){
|
||||
IOObjectRelease(parent);
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
||||
if(status != KERN_SUCCESS){
|
||||
IOObjectRelease(parent);
|
||||
return -1;
|
||||
}
|
||||
name = (CFStringRef)CFDictionaryGetValue(props, CFSTR(kIOBSDNameKey));
|
||||
CFStringGetCString(name, stat->name, NAMELEN, CFStringGetSystemEncoding());
|
||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaSizeKey));
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->size);
|
||||
num = (CFNumberRef)CFDictionaryGetValue(props, CFSTR(kIOMediaPreferredBlockSizeKey));
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, &stat->blocksize);
|
||||
CFRelease(props);
|
||||
|
||||
rv = fillstat(parent, stat);
|
||||
IOObjectRelease(parent);
|
||||
if(rv < 0)
|
||||
return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct {
|
||||
char *key;
|
||||
size_t off;
|
||||
} statstab[] = {
|
||||
{kIOBlockStorageDriverStatisticsBytesReadKey, offsetof(DriveStats, read)},
|
||||
{kIOBlockStorageDriverStatisticsBytesWrittenKey, offsetof(DriveStats, written)},
|
||||
{kIOBlockStorageDriverStatisticsReadsKey, offsetof(DriveStats, nread)},
|
||||
{kIOBlockStorageDriverStatisticsWritesKey, offsetof(DriveStats, nwrite)},
|
||||
{kIOBlockStorageDriverStatisticsTotalReadTimeKey, offsetof(DriveStats, readtime)},
|
||||
{kIOBlockStorageDriverStatisticsTotalWriteTimeKey, offsetof(DriveStats, writetime)},
|
||||
{kIOBlockStorageDriverStatisticsLatentReadTimeKey, offsetof(DriveStats, readlat)},
|
||||
{kIOBlockStorageDriverStatisticsLatentWriteTimeKey, offsetof(DriveStats, writelat)},
|
||||
};
|
||||
|
||||
static int
|
||||
fillstat(io_registry_entry_t d, DriveStats *stat)
|
||||
{
|
||||
CFDictionaryRef props, v;
|
||||
CFNumberRef num;
|
||||
kern_return_t status;
|
||||
typeof(statstab[0]) *bp, *ep;
|
||||
|
||||
status = IORegistryEntryCreateCFProperties(d, (CFMutableDictionaryRef *)&props, kCFAllocatorDefault, kNilOptions);
|
||||
if(status != KERN_SUCCESS)
|
||||
return -1;
|
||||
v = (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOBlockStorageDriverStatisticsKey));
|
||||
if(v == NULL){
|
||||
CFRelease(props);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ep = &statstab[sizeof(statstab)/sizeof(statstab[0])];
|
||||
for(bp = &statstab[0]; bp < ep; bp++){
|
||||
CFStringRef s;
|
||||
|
||||
s = CFStringCreateWithCString(kCFAllocatorDefault, bp->key, CFStringGetSystemEncoding());
|
||||
num = (CFNumberRef)CFDictionaryGetValue(v, s);
|
||||
if(num)
|
||||
CFNumberGetValue(num, kCFNumberSInt64Type, ((char*)stat)+bp->off);
|
||||
CFRelease(s);
|
||||
}
|
||||
|
||||
CFRelease(props);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// +build darwin
|
||||
|
||||
package iostat
|
||||
|
||||
// #cgo LDFLAGS: -framework CoreFoundation -framework IOKit
|
||||
// #include <stdint.h>
|
||||
// #include "iostat_darwin.h"
|
||||
import "C"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReadDriveStats returns statictics of each of the drives.
|
||||
func ReadDriveStats() ([]*DriveStats, error) {
|
||||
var buf [C.NDRIVE]C.DriveStats
|
||||
n, err := C.readdrivestat(&buf[0], C.int(len(buf)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats := make([]*DriveStats, n)
|
||||
for i := 0; i < int(n); i++ {
|
||||
stats[i] = &DriveStats{
|
||||
Name: C.GoString(&buf[i].name[0]),
|
||||
Size: int64(buf[i].size),
|
||||
BlockSize: int64(buf[i].blocksize),
|
||||
BytesRead: int64(buf[i].read),
|
||||
BytesWritten: int64(buf[i].written),
|
||||
NumRead: int64(buf[i].nread),
|
||||
NumWrite: int64(buf[i].nwrite),
|
||||
TotalReadTime: time.Duration(buf[i].readtime),
|
||||
TotalWriteTime: time.Duration(buf[i].writetime),
|
||||
ReadLatency: time.Duration(buf[i].readlat),
|
||||
WriteLatency: time.Duration(buf[i].writelat),
|
||||
}
|
||||
}
|
||||
return stats, nil
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
typedef struct DriveStats DriveStats;
|
||||
|
||||
enum {
|
||||
NDRIVE = 16,
|
||||
NAMELEN = 31
|
||||
};
|
||||
|
||||
struct DriveStats {
|
||||
char name[NAMELEN+1];
|
||||
int64_t size;
|
||||
int64_t blocksize;
|
||||
|
||||
int64_t read;
|
||||
int64_t written;
|
||||
int64_t nread;
|
||||
int64_t nwrite;
|
||||
int64_t readtime;
|
||||
int64_t writetime;
|
||||
int64_t readlat;
|
||||
int64_t writelat;
|
||||
};
|
||||
|
||||
extern int readdrivestat(DriveStats a[], int n);
|
|
@ -0,0 +1,12 @@
|
|||
// +build !darwin
|
||||
|
||||
package iostat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ReadDriveStats returns statictics of each of the drives.
|
||||
func ReadDriveStats() ([]*DriveStats, error) {
|
||||
return nil, errors.New("not implement")
|
||||
}
|
|
@ -50,6 +50,12 @@
|
|||
"revision": "0826b98aaa29c0766956cb40d45cf7482a597671",
|
||||
"revisionTime": "2015-04-13T19:18:30Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "82wQShWC1Udgn1UbTK42s0l/IpY=",
|
||||
"path": "github.com/lufia/iostat",
|
||||
"revision": "8c7e013c17ce06e6651d2affabe369045b98d3b7",
|
||||
"revisionTime": "2017-06-03T08:40:47Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "bKMZjd2wPw13VwoE7mBeSv5djFA=",
|
||||
"path": "github.com/matttproud/golang_protobuf_extensions/pbutil",
|
||||
|
|
Loading…
Reference in New Issue