disk_darwin: Add support for IOCounters using IOKit
This commit is contained in:
parent
f18f7dcbae
commit
b327360349
|
@ -87,10 +87,6 @@ func Partitions(all bool) ([]PartitionStat, error) {
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func IOCounters() (map[string]IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
||||
|
||||
func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
var bufsize uintptr
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/storage/IOBlockStorageDriver.h>
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#include <IOKit/IOBSD.h>
|
||||
|
||||
// The iterator of all things disk. Allocated by StartIOCounterFetch, released
|
||||
// by EndIOCounterFetch.
|
||||
static io_iterator_t diskIter;
|
||||
|
||||
// Begins fetching IO counters.
|
||||
//
|
||||
// Returns 1 if the fetch started successfully, false otherwise.
|
||||
//
|
||||
// If the fetch was started successfully, you must call EndIOCounterFetch once
|
||||
// done to release resources.
|
||||
int StartIOCounterFetch()
|
||||
{
|
||||
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
|
||||
IOServiceMatching(kIOMediaClass),
|
||||
&diskIter) != kIOReturnSuccess) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Releases resources from fetching IO counters.
|
||||
void EndIOCounterFetch()
|
||||
{
|
||||
IOObjectRelease(diskIter);
|
||||
}
|
||||
|
||||
// The current disk entry of interest. Allocated by FetchNextDisk(), released by
|
||||
// ReadDiskInfo().
|
||||
static io_registry_entry_t diskEntry;
|
||||
|
||||
// The parent of diskEntry. Same lifetimes.
|
||||
static io_registry_entry_t parentEntry;
|
||||
|
||||
// Fetches the next disk. Note that a disk entry is allocated, and will be held
|
||||
// until it is processed and freed by ReadDiskInfo.
|
||||
int FetchNextDisk()
|
||||
{
|
||||
while ((diskEntry = IOIteratorNext(diskIter)) != 0) {
|
||||
// We are iterating IOMedia. We need to get the parent too (IOBSD).
|
||||
if (IORegistryEntryGetParentEntry(diskEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess) {
|
||||
// something is wrong...
|
||||
IOObjectRelease(diskEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IOObjectConformsTo(parentEntry, "IOBlockStorageDriver")) {
|
||||
// no use to us, try the next disk
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Got a disk OK.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// No more disks.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Reads the current disk (from iteration) info into DiskInfo struct.
|
||||
// Once done, all resources from the current iteration of reading are freed,
|
||||
// ready for FetchNextDisk() to be called again.
|
||||
int ReadDiskInfo(DiskInfo *info)
|
||||
{
|
||||
// Parent props. Allocated by us.
|
||||
CFDictionaryRef parentProps = NULL;
|
||||
|
||||
// Disk props. Allocated by us.
|
||||
CFDictionaryRef diskProps = NULL;
|
||||
|
||||
// Disk stats, fetched by us, but not allocated by us.
|
||||
CFDictionaryRef stats = NULL;
|
||||
|
||||
if (IORegistryEntryCreateCFProperties(diskEntry, (CFMutableDictionaryRef *)&parentProps,
|
||||
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
|
||||
{
|
||||
// can't get parent props, give up
|
||||
CFRelease(parentProps);
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (IORegistryEntryCreateCFProperties(parentEntry, (CFMutableDictionaryRef *)&diskProps,
|
||||
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess)
|
||||
{
|
||||
// can't get disk props, give up
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(diskEntry);
|
||||
IOObjectRelease(parentEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start fetching
|
||||
CFStringRef cfDiskName = (CFStringRef)CFDictionaryGetValue(parentProps, CFSTR(kIOBSDNameKey));
|
||||
CFStringGetCString(cfDiskName, info->DiskName, MAX_DISK_NAME, CFStringGetSystemEncoding());
|
||||
stats = (CFDictionaryRef)CFDictionaryGetValue( diskProps, CFSTR(kIOBlockStorageDriverStatisticsKey));
|
||||
|
||||
if (stats == NULL) {
|
||||
// stat fetch failed...
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(parentEntry);
|
||||
IOObjectRelease(diskEntry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CFNumberRef cfnum;
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Reads);
|
||||
} else {
|
||||
info->Reads = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->Writes);
|
||||
} else {
|
||||
info->Writes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadBytes);
|
||||
} else {
|
||||
info->ReadBytes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteBytes);
|
||||
} else {
|
||||
info->WriteBytes = 0;
|
||||
}
|
||||
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->ReadTime);
|
||||
} else {
|
||||
info->ReadTime = 0;
|
||||
}
|
||||
if ((cfnum = (CFNumberRef)CFDictionaryGetValue(stats, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) {
|
||||
CFNumberGetValue(cfnum, kCFNumberSInt64Type, &info->WriteTime);
|
||||
} else {
|
||||
info->WriteTime = 0;
|
||||
}
|
||||
|
||||
// note: read/write time are in ns, but we want ms.
|
||||
info->ReadTime = info->ReadTime / 1000 / 1000;
|
||||
info->WriteTime = info->WriteTime / 1000 / 1000;
|
||||
|
||||
CFRelease(parentProps);
|
||||
CFRelease(diskProps);
|
||||
IOObjectRelease(parentEntry);
|
||||
IOObjectRelease(diskEntry);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// +build darwin
|
||||
// +build cgo
|
||||
|
||||
package disk
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -mmacosx-version-min=10.10 -DMACOSX_DEPLOYMENT_TARGET=10.10
|
||||
#cgo LDFLAGS: -mmacosx-version-min=10.10 -lobjc -framework Foundation -framework IOKit
|
||||
#include <stdint.h>
|
||||
|
||||
// ### enough?
|
||||
const int MAX_DISK_NAME = 100;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char DiskName[MAX_DISK_NAME];
|
||||
int64_t Reads;
|
||||
int64_t Writes;
|
||||
int64_t ReadBytes;
|
||||
int64_t WriteBytes;
|
||||
int64_t ReadTime;
|
||||
int64_t WriteTime;
|
||||
} DiskInfo;
|
||||
|
||||
#include "disk_darwin.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func IOCounters() (map[string]IOCountersStat, error) {
|
||||
if C.StartIOCounterFetch() == 0 {
|
||||
return nil, errors.New("Unable to fetch disk list")
|
||||
}
|
||||
|
||||
// Clean up when we are done.
|
||||
defer C.EndIOCounterFetch()
|
||||
ret := make(map[string]IOCountersStat, 0)
|
||||
|
||||
for {
|
||||
res := C.FetchNextDisk()
|
||||
if res == -1 {
|
||||
return nil, errors.New("Unable to fetch disk information")
|
||||
} else if res == 0 {
|
||||
break // done
|
||||
}
|
||||
|
||||
di := C.DiskInfo{}
|
||||
if C.ReadDiskInfo((*C.DiskInfo)(unsafe.Pointer(&di))) == -1 {
|
||||
return nil, errors.New("Unable to fetch disk properties")
|
||||
}
|
||||
|
||||
// Used to only get the necessary part of the C string.
|
||||
isRuneNull := func(r rune) bool {
|
||||
return r == '\u0000'
|
||||
}
|
||||
|
||||
// Map from the darwin-specific C struct to the Go type
|
||||
//
|
||||
// ### missing: IopsInProgress, WeightedIO, MergedReadCount,
|
||||
// MergedWriteCount, SerialNumber
|
||||
// IOKit can give us at least the serial number I think...
|
||||
d := IOCountersStat{
|
||||
// Note: The Go type wants unsigned values, but CFNumberGetValue
|
||||
// doesn't appear to be able to give us unsigned values. So, we
|
||||
// cast, and hope for the best.
|
||||
ReadBytes: uint64(di.ReadBytes),
|
||||
WriteBytes: uint64(di.WriteBytes),
|
||||
ReadCount: uint64(di.Reads),
|
||||
WriteCount: uint64(di.Writes),
|
||||
ReadTime: uint64(di.ReadTime),
|
||||
WriteTime: uint64(di.WriteTime),
|
||||
IoTime: uint64(di.ReadTime + di.WriteTime),
|
||||
Name: strings.TrimFunc(C.GoStringN(&di.DiskName[0], C.MAX_DISK_NAME), isRuneNull),
|
||||
}
|
||||
|
||||
ret[d.Name] = d
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// +build darwin
|
||||
// +build !cgo
|
||||
|
||||
package disk
|
||||
|
||||
import "github.com/shirou/gopsutil/internal/common"
|
||||
|
||||
func IOCounters() (map[string]IOCountersStat, error) {
|
||||
return nil, common.ErrNotImplementedError
|
||||
}
|
Loading…
Reference in New Issue