mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
- fix GPU detecting when 2 cards have the same driver (#143)
- processing GPUs with a generic model name (#109) - add debug argument which allows saving some logs to the file (/Documents/log.txt)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<GPUs> {
|
||||
internal var smc: UnsafePointer<SMCService>? = 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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -68,6 +68,10 @@
|
||||
argument = "--disable"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--debug"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>151</string>
|
||||
<string>155</string>
|
||||
<key>Description</key>
|
||||
<string>Simple macOS system monitor in your menu bar</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user