diff --git a/Modules/GPU/main.swift b/Modules/GPU/main.swift index d578b2e3..3f584d11 100644 --- a/Modules/GPU/main.swift +++ b/Modules/GPU/main.swift @@ -14,8 +14,8 @@ import ModuleKit import StatsKit public struct GPU_Info { - public let name: String - public let IOclass: String + public let model: String + public let IOClass: String public var state: Bool = false public var utilization: Double = 0 @@ -30,7 +30,7 @@ public struct GPUs: value_t { } internal func igpu() -> GPU_Info? { - return self.active().first{ $0.IOclass == "IntelAccelerator" } + return self.active().first{ $0.IOClass == "IntelAccelerator" } } public var widget_value: Double { @@ -95,7 +95,7 @@ public class GPU: Module { self.settingsView.setList(value!) let activeGPU = value!.active() - let selectedGPU = activeGPU.first{ $0.name == self.selectedGPU } ?? value!.igpu() ?? value!.list[0] + let selectedGPU = activeGPU.first{ $0.model == self.selectedGPU } ?? value!.igpu() ?? value!.list[0] if let widget = self.widget as? Mini { widget.setValue(selectedGPU.utilization) diff --git a/Modules/GPU/popup.swift b/Modules/GPU/popup.swift index 56a3c390..513704f2 100644 --- a/Modules/GPU/popup.swift +++ b/Modules/GPU/popup.swift @@ -36,16 +36,16 @@ internal class Popup: NSView, Popup_p { } value.list.forEach { (gpu: GPU_Info) in - if self.list[gpu.name] == nil { + if self.list[gpu.model] == nil { DispatchQueue.main.async(execute: { - self.list[gpu.name] = GPUView( + self.list[gpu.model] = GPUView( NSRect(x: 0, y: (self.gpuViewHeight + Constants.Popup.margins) * CGFloat(self.list.count), width: self.frame.width, height: self.gpuViewHeight), gpu: gpu ) - self.addSubview(self.list[gpu.name]!) + self.addSubview(self.list[gpu.model]!) }) } else { - self.list[gpu.name]?.update(gpu) + self.list[gpu.model]?.update(gpu) } } @@ -91,7 +91,7 @@ private class GPUView: NSView { private func initName() { let y: CGFloat = self.frame.height - 23 - let width: CGFloat = self.value.name.widthOfString(usingFont: NSFont.systemFont(ofSize: 12, weight: .medium)) + 16 + let width: CGFloat = self.value.model.widthOfString(usingFont: NSFont.systemFont(ofSize: 12, weight: .medium)) + 16 let view: NSView = NSView(frame: NSRect(x: (self.frame.width - width)/2, y: y, width: width, height: 20)) @@ -99,7 +99,7 @@ private class GPUView: NSView { labelView.alignment = .center labelView.textColor = .secondaryLabelColor labelView.font = NSFont.systemFont(ofSize: 12, weight: .medium) - labelView.stringValue = self.value.name + labelView.stringValue = self.value.model let stateView: NSView = NSView(frame: NSRect(x: width - 8, y: (view.frame.height-7)/2, width: 6, height: 6)) stateView.wantsLayer = true diff --git a/Modules/GPU/reader.swift b/Modules/GPU/reader.swift index b68c8a42..b00243e1 100644 --- a/Modules/GPU/reader.swift +++ b/Modules/GPU/reader.swift @@ -14,84 +14,126 @@ import StatsKit import ModuleKit import os.log +public struct device { + public let model: String + public let pci: String + public var used: Bool +} + internal class InfoReader: Reader { internal var smc: UnsafePointer? = nil private var gpus: GPUs = GPUs() - private var devices: [NSDictionary]? = nil + private var devices: [device] = [] public override func setup() { - guard let devices = fetchIOService("IOPCIDevice") else { + guard let PCIdevices = fetchIOService("IOPCIDevice") else { return } + let devices = PCIdevices.filter{ $0.object(forKey: "IOName") as? String == "display" } - self.devices = devices.filter{ $0.object(forKey: "IOName") as? String == "display" } + print("------------", Date(), "------------", to: &Log.log) + print("Found \(devices.count) devices", to: &Log.log) + + devices.forEach { (dict: NSDictionary) in + guard let deviceID = dict["device-id"] as? Data, let vendorID = dict["vendor-id"] as? Data else { + print("device-id or vendor-id not found", to: &Log.log) + return + } + let pci = "0x" + Data([deviceID[1], deviceID[0], vendorID[1], vendorID[0]]).map { String(format: "%02hhX", $0) }.joined().lowercased() + + guard let modelData = dict["model"] as? Data, let modelName = String(data: modelData, encoding: .ascii) else { + print("GPU model not found", to: &Log.log) + return + } + let model = modelName.replacingOccurrences(of: "\0", with: "") + + self.devices.append(device(model: model, pci: pci, used: false)) + } } public override func read() { - guard let acceletators = fetchIOService(kIOAcceleratorClassName) else { + guard let accelerators = fetchIOService(kIOAcceleratorClassName) else { return } - acceletators.forEach { (accelerator: NSDictionary) in - guard let matchedGPU = self.devices?.first(where: { (gpu: NSDictionary) -> Bool in - guard let deviceID = gpu["device-id"] as? Data, let vendorID = gpu["vendor-id"] as? Data else { - return false - } - - let pciMatch = "0x" + Data([deviceID[1], deviceID[0], vendorID[1], vendorID[0]]).map { String(format: "%02hhX", $0) }.joined().lowercased() - let accMatch = (accelerator["IOPCIMatch"] as? String ?? accelerator["IOPCIPrimaryMatch"] as? String ?? "").lowercased() - - return accMatch.range(of: pciMatch) != nil - }) else { return } - - guard let agcInfo = accelerator["AGCInfo"] as? [String:Int] else { + for (i, _) in self.devices.enumerated() { + self.devices[i].used = false + } + + print("------------", "read()", "------------", to: &Log.log) + print("Found \(accelerators.count) accelerators", to: &Log.log) + + accelerators.forEach { (accelerator: NSDictionary) in + guard let IOClass = accelerator.object(forKey: "IOClass") as? String else { + print("IOClass not found", to: &Log.log) return } + print("Processing \(IOClass) accelerator", to: &Log.log) guard let stats = accelerator["PerformanceStatistics"] as? [String:Any] else { + print("PerformanceStatistics not found", to: &Log.log) return } - guard let model = matchedGPU.object(forKey: "model") as? Data, var modelName = String(data: model, encoding: .ascii) else { - return - } - modelName = modelName.replacingOccurrences(of: "\0", with: "") + var model: String = "" + let accMatch = (accelerator["IOPCIMatch"] as? String ?? accelerator["IOPCIPrimaryMatch"] as? String ?? "").lowercased() - guard let IOClass = accelerator.object(forKey: "IOClass") as? String else { - return - } - - if self.gpus.list.first(where: { $0.name == modelName }) == nil { - self.gpus.list.append(GPU_Info(name: modelName, IOclass: IOClass)) - } - - guard let idx = self.gpus.list.firstIndex(where: { $0.name == modelName }) else { - return - } - - let utilization = stats["Device Utilization %"] as? Int ?? 0 -// let totalVram = (accelerator["VRAM,totalMB"] as? Int ?? matchedGPU["VRAM,totalMB"] as? Int ?? 0) * 1000000 -// let freeVram = stats["vramFreeBytes"] as? Int ?? 0 -// let coreClock = stats["Core Clock(MHz)"] as? Int ?? 0 - var temperature = stats["Temperature(C)"] as? Int ?? 0 - - if IOClass == "IntelAccelerator" { - if temperature == 0 { - if let tmp = self.smc?.pointee.getValue("TCGC") { - temperature = Int(tmp) - } else if let tmp = self.smc?.pointee.getValue("TG0D") { - temperature = Int(tmp) - } + for (i, device) in self.devices.enumerated() { + let matched = accMatch.range(of: device.pci) + if matched != nil && !device.used { + model = device.model + self.devices[i].used = true + } else if device.used { + print("Device `\(device.model)` with pci `\(device.pci)` is already used", to: &Log.log) + } else { + print("`\(device.pci)` and `\(accMatch)` not match", to: &Log.log) } } - self.gpus.list[idx].state = agcInfo["poweredOffByAGC"] == 0 + if model == "" { + let ioClass = IOClass.lowercased() + if ioClass == "nvAccelerator" || ioClass.contains("nvidia") { + model = "Nvidia Graphics" + } else if ioClass.contains("amd") { + model = "AMD Graphics" + } else if ioClass.contains("intel") { + model = "Intel Graphics" + } else { + model = "Unknown" + } + } + + if self.gpus.list.first(where: { $0.model == model }) == nil { + self.gpus.list.append(GPU_Info(model: model, IOClass: IOClass)) + } + guard let idx = self.gpus.list.firstIndex(where: { $0.model == model }) else { + return + } + + let utilization = stats["Device Utilization %"] as? Int ?? stats["GPU Activity(%)"] as? Int ?? 0 + var temperature = stats["Temperature(C)"] as? Int ?? 0 + + if IOClass == "IntelAccelerator" && temperature == 0 { + if let tmp = self.smc?.pointee.getValue("TCGC") { + temperature = Int(tmp) + } else if let tmp = self.smc?.pointee.getValue("TG0D") { + temperature = Int(tmp) + } + } + + if let agcInfo = accelerator["AGCInfo"] as? [String:Int] { + self.gpus.list[idx].state = agcInfo["poweredOffByAGC"] == 0 + } self.gpus.list[idx].utilization = utilization == 0 ? 0 : Double(utilization)/100 self.gpus.list[idx].temperature = temperature + + print("\(model): utilization=\(utilization), temperature=\(temperature)", to: &Log.log) } + print("Callback \(self.gpus.list.count) GPUs", to: &Log.log) + self.gpus.list.sort{ !$0.state && $1.state } self.callback(self.gpus) } diff --git a/Modules/GPU/settings.swift b/Modules/GPU/settings.swift index 0f2aaf55..59b4f28c 100644 --- a/Modules/GPU/settings.swift +++ b/Modules/GPU/settings.swift @@ -91,7 +91,7 @@ internal class Settings: NSView, Settings_v { } internal func setList(_ list: GPUs) { - let disks = list.active().map{ $0.name } + let disks = list.active().map{ $0.model } DispatchQueue.main.async(execute: { if self.button?.itemTitles.count != disks.count { self.button?.removeAllItems() diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme index 562677d8..1534aa80 100644 --- a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme +++ b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme @@ -68,6 +68,10 @@ argument = "--disable" isEnabled = "NO"> + + CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 151 + 155 Description Simple macOS system monitor in your menu bar LSApplicationCategoryType diff --git a/StatsKit/helpers.swift b/StatsKit/helpers.swift index d3c3eb72..62cec751 100644 --- a/StatsKit/helpers.swift +++ b/StatsKit/helpers.swift @@ -610,7 +610,21 @@ public class ColorView: NSView { } public struct Log: TextOutputStream { + public static var log: Log = Log() + + private var debug: Bool = false + + private init() { + if CommandLine.arguments.contains("--debug") { + self.debug = true + } + } + public func write(_ string: String) { + if !debug { + return + } + let fm = FileManager.default let log = fm.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("log.txt") if let handle = try? FileHandle(forWritingTo: log) {