From b32736034938b68a86e392bf5476eb0e68af1b6d Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Sun, 19 Mar 2017 22:13:22 +0100 Subject: [PATCH] disk_darwin: Add support for IOCounters using IOKit --- disk/disk_darwin.go | 4 -- disk/disk_darwin.h | 164 ++++++++++++++++++++++++++++++++++++++++++++++ disk/disk_darwin_cgo.go | 85 ++++++++++++++++++++++++ disk/disk_darwin_nocgo.go | 10 +++ 4 files changed, 259 insertions(+), 4 deletions(-) create mode 100644 disk/disk_darwin.h create mode 100644 disk/disk_darwin_cgo.go create mode 100644 disk/disk_darwin_nocgo.go diff --git a/disk/disk_darwin.go b/disk/disk_darwin.go index 1ccb330..dc642df 100644 --- a/disk/disk_darwin.go +++ b/disk/disk_darwin.go @@ -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 diff --git a/disk/disk_darwin.h b/disk/disk_darwin.h new file mode 100644 index 0000000..d0fe514 --- /dev/null +++ b/disk/disk_darwin.h @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include + +// 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; +} + diff --git a/disk/disk_darwin_cgo.go b/disk/disk_darwin_cgo.go new file mode 100644 index 0000000..c8e8e65 --- /dev/null +++ b/disk/disk_darwin_cgo.go @@ -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 + +// ### 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 +} diff --git a/disk/disk_darwin_nocgo.go b/disk/disk_darwin_nocgo.go new file mode 100644 index 0000000..502c3b9 --- /dev/null +++ b/disk/disk_darwin_nocgo.go @@ -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 +}