// // reader.swift // GPU // // Created by Serhiy Mytrovtsiy on 17/08/2020. // Using Swift 5.0. // Running on macOS 10.15. // // Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved. // import Cocoa import StatsKit import ModuleKit import os.log public struct device { public let vendor: String? public let model: String public let pci: String public var used: Bool } let vendors: [Data: String] = [ Data.init([0x86, 0x80, 0x00, 0x00]): "Intel", Data.init([0x02, 0x10, 0x00, 0x00]): "AMD" ] internal class InfoReader: Reader { internal var smc: UnsafePointer? = nil private var gpus: GPUs = GPUs() private var devices: [device] = [] public override func setup() { guard let PCIdevices = fetchIOService("IOPCIDevice") else { return } let devices = PCIdevices.filter{ $0.object(forKey: "IOName") as? String == "display" } devices.forEach { (dict: NSDictionary) in guard let deviceID = dict["device-id"] as? Data, let vendorID = dict["vendor-id"] as? Data else { os_log(.error, log: log, "device-id or vendor-id not found") 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 { os_log(.error, log: log, "GPU model not found") return } let model = modelName.replacingOccurrences(of: "\0", with: "") var vendor: String? = nil if let v = vendors.first(where: { $0.key == vendorID }) { vendor = v.value } self.devices.append(device( vendor: vendor, model: model, pci: pci, used: false )) } } public override func read() { guard let accelerators = fetchIOService(kIOAcceleratorClassName) else { return } var devices = self.devices accelerators.forEach { (accelerator: NSDictionary) in guard let IOClass = accelerator.object(forKey: "IOClass") as? String else { os_log(.error, log: log, "IOClass not found") return } guard let stats = accelerator["PerformanceStatistics"] as? [String:Any] else { os_log(.error, log: log, "PerformanceStatistics not found") return } var vendor: String? = nil var model: String = "" let accMatch = (accelerator["IOPCIMatch"] as? String ?? accelerator["IOPCIPrimaryMatch"] as? String ?? "").lowercased() for (i, device) in devices.enumerated() { if accMatch.range(of: device.pci) != nil && !device.used { model = device.model vendor = device.vendor devices[i].used = true break } } let ioClass = IOClass.lowercased() var predictModel = "" var type: GPU_types = .unknown let utilization: Int? = stats["Device Utilization %"] as? Int ?? stats["GPU Activity(%)"] as? Int ?? nil var temperature: Int? = stats["Temperature(C)"] as? Int ?? nil let fanSpeed: Int? = stats["Fan Speed(%)"] as? Int ?? nil let coreClock: Int? = stats["Core Clock(MHz)"] as? Int ?? nil let memoryClock: Int? = stats["Memory Clock(MHz)"] as? Int ?? nil if ioClass == "nvaccelerator" || ioClass.contains("nvidia") { // nvidia predictModel = "Nvidia Graphics" type = .discrete } else if ioClass.contains("amd") { // amd predictModel = "AMD Graphics" type = .discrete if temperature == nil || temperature == 0 { if let tmp = self.smc?.pointee.getValue("TGDD"), tmp != 128 { temperature = Int(tmp) } } } else if ioClass.contains("intel") { // intel predictModel = "Intel Graphics" type = .integrated if temperature == nil || temperature == 0 { if let tmp = self.smc?.pointee.getValue("TCGC"), tmp != 128 { temperature = Int(tmp) } } } else if ioClass.contains("agx") { // apple predictModel = stats["model"] as? String ?? "Apple Graphics" type = .integrated } else { predictModel = "Unknown" type = .unknown } if model == "" { model = predictModel } if let v = vendor { model = model.removedRegexMatches(pattern: v, replaceWith: "").trimmingCharacters(in: .whitespacesAndNewlines) } if self.gpus.list.first(where: { $0.model == model }) == nil { self.gpus.list.append(GPU_Info( type: type.rawValue, IOClass: IOClass, vendor: vendor, model: model )) } guard let idx = self.gpus.list.firstIndex(where: { $0.model == model }) else { return } if let agcInfo = accelerator["AGCInfo"] as? [String:Int], let state = agcInfo["poweredOffByAGC"] { self.gpus.list[idx].state = state == 0 } if let value = utilization { self.gpus.list[idx].utilization = Double(value)/100 } if let value = temperature { self.gpus.list[idx].temperature = Double(value) } if let value = fanSpeed { self.gpus.list[idx].fanSpeed = value } if let value = coreClock { self.gpus.list[idx].coreClock = value } if let value = memoryClock { self.gpus.list[idx].memoryClock = value } } self.gpus.list.sort{ !$0.state && $1.state } self.callback(self.gpus) } }