disk_darwin: Add support for IOCounters using IOKit

This commit is contained in:
Robin Burchell 2017-03-19 22:13:22 +01:00 committed by Robin Burchell
parent f18f7dcbae
commit b327360349
4 changed files with 259 additions and 4 deletions

View File

@ -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

164
disk/disk_darwin.h Normal file
View File

@ -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;
}

85
disk/disk_darwin_cgo.go Normal file
View File

@ -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
}

10
disk/disk_darwin_nocgo.go Normal file
View File

@ -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
}