diff --git a/disk/disk_darwin.c b/disk/disk_darwin.c new file mode 100644 index 0000000..198d3d1 --- /dev/null +++ b/disk/disk_darwin.c @@ -0,0 +1,131 @@ +// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.c +#include +#include +#include "disk_darwin.h" + +#define IOKIT 1 /* to get io_name_t in device_types.h */ + +#include +#include +#include +#include + +#include + +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; +} diff --git a/disk/disk_darwin.h b/disk/disk_darwin.h index d0fe514..c720849 100644 --- a/disk/disk_darwin.h +++ b/disk/disk_darwin.h @@ -1,164 +1,33 @@ -#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; -} - +// https://github.com/lufia/iostat/blob/9f7362b77ad333b26c01c99de52a11bdb650ded2/iostat_darwin.h +typedef struct DriveStats DriveStats; +typedef struct CPUStats CPUStats; + +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; +}; + +struct CPUStats { + natural_t user; + natural_t nice; + natural_t sys; + natural_t idle; +}; + +extern int readdrivestat(DriveStats a[], int n); +extern int readcpustat(CPUStats *cpu); diff --git a/disk/disk_darwin_cgo.go b/disk/disk_darwin_cgo.go index 480e237..623c5fd 100644 --- a/disk/disk_darwin_cgo.go +++ b/disk/disk_darwin_cgo.go @@ -4,32 +4,15 @@ package disk /* -#cgo LDFLAGS: -lobjc -framework Foundation -framework IOKit +#cgo LDFLAGS: -framework CoreFoundation -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 #include "disk_darwin.h" */ import "C" import ( "context" - "errors" - "strings" - "unsafe" "github.com/shirou/gopsutil/internal/common" ) @@ -39,57 +22,28 @@ func IOCounters(names ...string) (map[string]IOCountersStat, error) { } func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) { - if C.StartIOCounterFetch() == 0 { - return nil, errors.New("Unable to fetch disk list") + var buf [C.NDRIVE]C.DriveStats + n, err := C.readdrivestat(&buf[0], C.int(len(buf))) + if err != nil { + return nil, err } - - // 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... + for i := 0; i < int(n); i++ { 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), + ReadBytes: uint64(buf[i].read), + WriteBytes: uint64(buf[i].written), + ReadCount: uint64(buf[i].nread), + WriteCount: uint64(buf[i].nwrite), + ReadTime: uint64(buf[i].readtime / 1000 / 1000), // note: read/write time are in ns, but we want ms. + WriteTime: uint64(buf[i].writetime / 1000 / 1000), + IoTime: uint64((buf[i].readtime + buf[i].writetime) / 1000 / 1000), + Name: C.GoString(&buf[i].name[0]), } - if len(names) > 0 && !common.StringsHas(names, d.Name) { continue } ret[d.Name] = d } - return ret, nil } diff --git a/disk/disk_test.go b/disk/disk_test.go index a0251cb..c42e0a3 100644 --- a/disk/disk_test.go +++ b/disk/disk_test.go @@ -3,6 +3,7 @@ package disk import ( "fmt" "runtime" + "sync" "testing" ) @@ -55,6 +56,23 @@ func TestDisk_io_counters(t *testing.T) { } } +// https://github.com/shirou/gopsutil/issues/560 regression test +func TestDisk_io_counters_concurrency_on_darwin_cgo(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("darwin only") + } + var wg sync.WaitGroup + const max = 1000 + for i := 1; i < max; i++ { + wg.Add(1) + go func() { + defer wg.Done() + IOCounters() + }() + } + wg.Wait() +} + func TestDiskUsageStat_String(t *testing.T) { v := UsageStat{ Path: "/",