mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: initialized SMART reads for disks that are SMART capable (#1407)
This commit is contained in:
@@ -10,3 +10,37 @@
|
||||
//
|
||||
|
||||
#include <libproc.h>
|
||||
#include <IOKit/hidsystem/IOHIDEventSystemClient.h>
|
||||
|
||||
struct nvme_smart_log {
|
||||
UInt8 critical_warning;
|
||||
UInt8 temperature[2];
|
||||
UInt8 avail_spare;
|
||||
UInt8 spare_thresh;
|
||||
UInt8 percent_used;
|
||||
UInt8 rsvd6[26];
|
||||
UInt8 data_units_read[16];
|
||||
UInt8 data_units_written[16];
|
||||
UInt8 host_reads[16];
|
||||
UInt8 host_writes[16];
|
||||
UInt8 ctrl_busy_time[16];
|
||||
UInt32 power_cycles[4];
|
||||
UInt32 power_on_hours[4];
|
||||
UInt32 unsafe_shutdowns[4];
|
||||
UInt32 media_errors[4];
|
||||
UInt16 temp_sensor[8];
|
||||
UInt32 thm_temp1_trans_count;
|
||||
UInt32 thm_temp2_trans_count;
|
||||
UInt32 thm_temp1_total_time;
|
||||
UInt32 thm_temp2_total_time;
|
||||
UInt8 rsvd232[280];
|
||||
};
|
||||
|
||||
typedef struct IONVMeSMARTInterface {
|
||||
IUNKNOWN_C_GUTS;
|
||||
|
||||
UInt16 version;
|
||||
UInt16 revision;
|
||||
|
||||
IOReturn ( *SMARTReadData )( void * interface, struct nvme_smart_log * NVMeSMARTData );
|
||||
} IONVMeSMARTInterface;
|
||||
|
||||
@@ -20,8 +20,13 @@ public struct stats {
|
||||
var writeBytes: Int64 = 0
|
||||
}
|
||||
|
||||
public struct smart_t {
|
||||
var temperature: Int = 0
|
||||
var life: Int = 0
|
||||
}
|
||||
|
||||
public struct drive {
|
||||
var parent: io_registry_entry_t = 0
|
||||
var parent: io_object_t = 0
|
||||
|
||||
var mediaName: String = ""
|
||||
var BSDName: String = ""
|
||||
@@ -38,6 +43,7 @@ public struct drive {
|
||||
var free: Int64 = 0
|
||||
|
||||
var activity: stats = stats()
|
||||
var smart: smart_t? = nil
|
||||
}
|
||||
|
||||
public class Disks {
|
||||
@@ -126,6 +132,12 @@ public class Disks {
|
||||
self.array[idx].activity.write = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func updateSMARTData(_ idx: Int, smart: smart_t) {
|
||||
self.queue.async(flags: .barrier) {
|
||||
self.array[idx].smart = smart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Disk_process: IOProcess_p {
|
||||
@@ -171,9 +183,9 @@ public class Disk: Module {
|
||||
private let settingsView: Settings = Settings()
|
||||
private let portalView: Portal = Portal()
|
||||
|
||||
private var capacityReader: CapacityReader? = nil
|
||||
private var activityReader: ActivityReader? = nil
|
||||
private var processReader: ProcessReader? = nil
|
||||
private var capacityReader: CapacityReader = CapacityReader()
|
||||
private var activityReader: ActivityReader = ActivityReader()
|
||||
private var processReader: ProcessReader = ProcessReader()
|
||||
|
||||
private var selectedDisk: String = ""
|
||||
private var notificationLevelState: Bool = false
|
||||
@@ -191,27 +203,23 @@ public class Disk: Module {
|
||||
)
|
||||
guard self.available else { return }
|
||||
|
||||
self.capacityReader = CapacityReader()
|
||||
self.activityReader = ActivityReader()
|
||||
self.processReader = ProcessReader()
|
||||
|
||||
self.selectedDisk = Store.shared.string(key: "\(Disk.name)_disk", defaultValue: self.selectedDisk)
|
||||
|
||||
self.capacityReader?.callbackHandler = { [unowned self] value in
|
||||
self.capacityReader.callbackHandler = { [unowned self] value in
|
||||
if let value = value {
|
||||
self.capacityCallback(value)
|
||||
}
|
||||
}
|
||||
self.capacityReader?.readyCallback = { [unowned self] in
|
||||
self.capacityReader.readyCallback = { [unowned self] in
|
||||
self.readyHandler()
|
||||
}
|
||||
|
||||
self.activityReader?.callbackHandler = { [unowned self] value in
|
||||
self.activityReader.callbackHandler = { [unowned self] value in
|
||||
if let value = value {
|
||||
self.activityCallback(value)
|
||||
}
|
||||
}
|
||||
self.processReader?.callbackHandler = { [unowned self] value in
|
||||
self.processReader.callbackHandler = { [unowned self] value in
|
||||
if let list = value {
|
||||
self.popupView.processCallback(list)
|
||||
}
|
||||
@@ -219,34 +227,28 @@ public class Disk: Module {
|
||||
|
||||
self.settingsView.selectedDiskHandler = { [unowned self] value in
|
||||
self.selectedDisk = value
|
||||
self.capacityReader?.read()
|
||||
self.capacityReader.read()
|
||||
}
|
||||
self.settingsView.callback = { [unowned self] in
|
||||
self.capacityReader?.read()
|
||||
self.capacityReader.read()
|
||||
}
|
||||
self.settingsView.setInterval = { [unowned self] value in
|
||||
self.capacityReader?.setInterval(value)
|
||||
self.capacityReader.setInterval(value)
|
||||
}
|
||||
self.settingsView.callbackWhenUpdateNumberOfProcesses = {
|
||||
self.popupView.numberOfProcessesUpdated()
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.processReader?.read()
|
||||
self.processReader.read()
|
||||
}
|
||||
}
|
||||
|
||||
if let reader = self.capacityReader {
|
||||
self.addReader(reader)
|
||||
}
|
||||
if let reader = self.activityReader {
|
||||
self.addReader(reader)
|
||||
}
|
||||
if let reader = self.processReader {
|
||||
self.addReader(reader)
|
||||
}
|
||||
self.addReader(self.capacityReader)
|
||||
self.addReader(self.activityReader)
|
||||
self.addReader(self.processReader)
|
||||
}
|
||||
|
||||
public override func widgetDidSet(_ type: widget_t) {
|
||||
if type == .speed && self.capacityReader?.interval != 1 {
|
||||
if type == .speed && self.capacityReader.interval != 1 {
|
||||
self.settingsView.setUpdateInterval(value: 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,26 @@
|
||||
|
||||
import Cocoa
|
||||
import Kit
|
||||
import IOKit
|
||||
import Darwin
|
||||
import IOKit.storage
|
||||
|
||||
let kIONVMeSMARTUserClientTypeID = CFUUIDGetConstantUUIDWithBytes(nil,
|
||||
0xAA, 0x0F, 0xA6, 0xF9,
|
||||
0xC2, 0xD6, 0x45, 0x7F,
|
||||
0xB1, 0x0B, 0x59, 0xA1,
|
||||
0x32, 0x53, 0x29, 0x2F
|
||||
)
|
||||
let kIONVMeSMARTInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
|
||||
0xCC, 0xD1, 0xDB, 0x19,
|
||||
0xFD, 0x9A, 0x4D, 0xAF,
|
||||
0xBF, 0x95, 0x12, 0x45,
|
||||
0x4B, 0x23, 0x0A, 0xB6
|
||||
)
|
||||
let kIOCFPlugInInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
|
||||
0xC2, 0x44, 0xE8, 0x58,
|
||||
0x10, 0x9C, 0x11, 0xD4,
|
||||
0x91, 0xD4, 0x00, 0x50,
|
||||
0xE4, 0xC6, 0x42, 0x6F
|
||||
)
|
||||
|
||||
internal class CapacityReader: Reader<Disks> {
|
||||
internal var list: Disks = Disks()
|
||||
@@ -43,6 +61,9 @@ internal class CapacityReader: Reader<Disks> {
|
||||
|
||||
if let path = d.path {
|
||||
self.list.updateFreeSize(idx, newValue: self.freeDiskSpaceInBytes(path))
|
||||
if let smart = self.getSMARTDetails(for: BSDName) {
|
||||
self.list.updateSMARTData(idx, smart: smart)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
@@ -53,6 +74,9 @@ internal class CapacityReader: Reader<Disks> {
|
||||
d.free = self.freeDiskSpaceInBytes(path)
|
||||
d.size = self.totalDiskSpaceInBytes(path)
|
||||
}
|
||||
if let smart = self.getSMARTDetails(for: BSDName) {
|
||||
d.smart = smart
|
||||
}
|
||||
self.list.append(d)
|
||||
self.list.sort()
|
||||
}
|
||||
@@ -117,6 +141,54 @@ internal class CapacityReader: Reader<Disks> {
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
private func getSMARTDetails(for BSDName: String) -> smart_t? {
|
||||
var disk = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, BSDName.cString(using: .utf8)))
|
||||
guard disk != kIOReturnSuccess else { return nil }
|
||||
defer { IOObjectRelease(disk) }
|
||||
|
||||
var parent = disk
|
||||
while IOObjectConformsTo(disk, kIOBlockStorageDeviceClass) == 0 {
|
||||
let error = IORegistryEntryGetParentEntry(disk, kIOServicePlane, &parent)
|
||||
if error != kIOReturnSuccess || parent == kIOReturnSuccess { return nil }
|
||||
disk = parent
|
||||
}
|
||||
|
||||
guard IOObjectConformsTo(disk, kIOBlockStorageDeviceClass) > 0,
|
||||
let raw = IORegistryEntryCreateCFProperty(disk, "NVMe SMART Capable" as CFString, kCFAllocatorDefault, 0),
|
||||
let val = raw.takeRetainedValue() as? Bool, val else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pluginInterface: UnsafeMutablePointer<UnsafeMutablePointer<IOCFPlugInInterface>?>?
|
||||
var smartInterface: UnsafeMutablePointer<UnsafeMutablePointer<IONVMeSMARTInterface>?>?
|
||||
var score: Int32 = 0
|
||||
|
||||
var result = IOCreatePlugInInterfaceForService(disk, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID, &pluginInterface, &score)
|
||||
guard result == kIOReturnSuccess else { return nil }
|
||||
defer { IODestroyPlugInInterface(pluginInterface) }
|
||||
|
||||
result = withUnsafeMutablePointer(to: &smartInterface) {
|
||||
$0.withMemoryRebound(to: Optional<LPVOID>.self, capacity: 1) {
|
||||
pluginInterface?.pointee?.pointee.QueryInterface(pluginInterface, CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID), $0) ?? KERN_NOT_FOUND
|
||||
}
|
||||
}
|
||||
defer { _ = pluginInterface?.pointee?.pointee.Release(smartInterface) }
|
||||
|
||||
guard result == KERN_SUCCESS, let smart = smartInterface?.pointee else { return nil }
|
||||
var smartData: nvme_smart_log = nvme_smart_log()
|
||||
guard smart.pointee.SMARTReadData(smartInterface, &smartData) == kIOReturnSuccess else { return nil }
|
||||
|
||||
let temperatures: [UInt8] = [UInt8(smartData.temperature.1), UInt8(smartData.temperature.0)]
|
||||
var temperature: UInt16 = 0
|
||||
let data = NSData(bytes: temperatures, length: 2)
|
||||
data.getBytes(&temperature, length: 2)
|
||||
|
||||
return smart_t(
|
||||
temperature: Int(UInt16(bigEndian: temperature) - 273),
|
||||
life: 100 - Int(smartData.percent_used)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class ActivityReader: Reader<Disks> {
|
||||
|
||||
Reference in New Issue
Block a user