mirror of
https://github.com/exelban/stats.git
synced 2026-05-07 20:02:34 +00:00
feat: moved ANE utilization from read to calculation from power usage
This commit is contained in:
+51
-34
@@ -24,6 +24,22 @@ let vendors: [Data: String] = [
|
||||
Data.init([0x02, 0x10, 0x00, 0x00]): "AMD"
|
||||
]
|
||||
|
||||
private func maxANEPower(for platform: Platform?) -> Double {
|
||||
switch platform {
|
||||
case .m1, .m1Pro, .m1Max: return 2.0
|
||||
case .m1Ultra: return 4.0
|
||||
case .m2, .m2Pro, .m2Max: return 2.5
|
||||
case .m2Ultra: return 5.0
|
||||
case .m3, .m3Pro, .m3Max: return 3.0
|
||||
case .m3Ultra: return 6.0
|
||||
case .m4, .m4Pro, .m4Max: return 6.0
|
||||
case .m4Ultra: return 12.0
|
||||
case .m5, .m5Pro, .m5Max: return 8.0
|
||||
case .m5Ultra: return 16.0
|
||||
default: return 8.0
|
||||
}
|
||||
}
|
||||
|
||||
internal class InfoReader: Reader<GPUs> {
|
||||
private var gpus: GPUs = GPUs()
|
||||
private var displays: [gpu_s] = []
|
||||
@@ -31,7 +47,9 @@ internal class InfoReader: Reader<GPUs> {
|
||||
|
||||
private var aneChannels: CFMutableDictionary? = nil
|
||||
private var aneSubscription: IOReportSubscriptionRef? = nil
|
||||
private var previousANEResidencies: [(on: Int64, total: Int64)] = []
|
||||
private var previousANEEnergy: Double = 0
|
||||
private var previousANERead: Date? = nil
|
||||
private var aneMaxPower: Double = 8.0
|
||||
|
||||
private var framesChannels: CFMutableDictionary? = nil
|
||||
private var framesSubscription: IOReportSubscriptionRef? = nil
|
||||
@@ -49,6 +67,7 @@ internal class InfoReader: Reader<GPUs> {
|
||||
let devices = PCIdevices.filter{ $0.object(forKey: "IOName") as? String == "display" }
|
||||
|
||||
#if arch(arm64)
|
||||
self.aneMaxPower = maxANEPower(for: SystemKit.shared.device.platform)
|
||||
self.setupANE()
|
||||
self.setupFrames()
|
||||
#endif
|
||||
@@ -220,10 +239,11 @@ internal class InfoReader: Reader<GPUs> {
|
||||
}
|
||||
|
||||
#if arch(arm64)
|
||||
let aneValue = self.readANEUtilization()
|
||||
let anePower = self.readANEPower()
|
||||
let aneUtil = anePower.map { min(1.0, max(0.0, $0 / self.aneMaxPower)) }
|
||||
let fpsValue = self.readFrames()
|
||||
for i in self.gpus.list.indices where self.gpus.list[i].IOClass.lowercased().contains("agx") {
|
||||
self.gpus.list[i].aneUtilization = aneValue ?? 0
|
||||
self.gpus.list[i].aneUtilization = aneUtil ?? 0
|
||||
self.gpus.list[i].fps = fpsValue
|
||||
}
|
||||
#endif
|
||||
@@ -288,10 +308,10 @@ internal class InfoReader: Reader<GPUs> {
|
||||
return Double(delta) / elapsed
|
||||
}
|
||||
|
||||
// MARK: - ANE utilization
|
||||
// MARK: - ANE power
|
||||
|
||||
private func setupANE() {
|
||||
guard let channel = IOReportCopyChannelsInGroup("SoC Stats" as CFString, "Cluster Power States" as CFString, 0, 0, 0)?.takeRetainedValue() else { return }
|
||||
guard let channel = IOReportCopyChannelsInGroup("Energy Model" as CFString, nil, 0, 0, 0)?.takeRetainedValue() else { return }
|
||||
|
||||
let size = CFDictionaryGetCount(channel)
|
||||
guard let mutable = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, size, channel),
|
||||
@@ -303,7 +323,7 @@ internal class InfoReader: Reader<GPUs> {
|
||||
sub?.release()
|
||||
}
|
||||
|
||||
private func readANEUtilization() -> Double? {
|
||||
private func readANEPower() -> Double? {
|
||||
guard let subscription = self.aneSubscription,
|
||||
let channels = self.aneChannels,
|
||||
let reportSample = IOReportCreateSamples(subscription, channels, nil)?.takeRetainedValue(),
|
||||
@@ -312,48 +332,45 @@ internal class InfoReader: Reader<GPUs> {
|
||||
}
|
||||
let items = dict["IOReportChannels"] as! CFArray
|
||||
|
||||
var currentResidencies: [(on: Int64, total: Int64)] = []
|
||||
var currentEnergy: Double = 0
|
||||
var found = false
|
||||
|
||||
for i in 0..<CFArrayGetCount(items) {
|
||||
let item = unsafeBitCast(CFArrayGetValueAtIndex(items, i), to: CFDictionary.self)
|
||||
|
||||
guard let group = IOReportChannelGetGroup(item)?.takeUnretainedValue() as? String,
|
||||
group == "SoC Stats",
|
||||
let subgroup = IOReportChannelGetSubGroup(item)?.takeUnretainedValue() as? String,
|
||||
subgroup == "Cluster Power States",
|
||||
group == "Energy Model",
|
||||
let channel = IOReportChannelGetChannelName(item)?.takeUnretainedValue() as? String,
|
||||
channel.hasPrefix("ANE") else { continue }
|
||||
channel.starts(with: "ANE") else { continue }
|
||||
|
||||
let stateCount = IOReportStateGetCount(item)
|
||||
guard stateCount == 2 else { continue }
|
||||
let raw = Double(IOReportSimpleGetIntegerValue(item, 0))
|
||||
let unit = (IOReportChannelGetUnitLabel(item)?.takeUnretainedValue() as? String)?
|
||||
.trimmingCharacters(in: .whitespaces) ?? ""
|
||||
|
||||
var on: Int64 = 0
|
||||
var total: Int64 = 0
|
||||
for s in 0..<stateCount {
|
||||
let residency = IOReportStateGetResidency(item, s)
|
||||
let name = IOReportStateGetNameForIndex(item, s)?.takeUnretainedValue() as? String ?? ""
|
||||
total += residency
|
||||
if name != "INACT" {
|
||||
on += residency
|
||||
}
|
||||
let joules: Double
|
||||
switch unit.lowercased() {
|
||||
case "mj": joules = raw / 1e3
|
||||
case "uj", "µj": joules = raw / 1e6
|
||||
case "nj": joules = raw / 1e9
|
||||
case "pj": joules = raw / 1e12
|
||||
default: joules = raw / 1e9
|
||||
}
|
||||
|
||||
currentResidencies.append((on: on, total: total))
|
||||
currentEnergy += joules
|
||||
found = true
|
||||
}
|
||||
|
||||
guard !currentResidencies.isEmpty else { return nil }
|
||||
guard found else { return nil }
|
||||
|
||||
defer { self.previousANEResidencies = currentResidencies }
|
||||
guard self.previousANEResidencies.count == currentResidencies.count else { return nil }
|
||||
|
||||
var totalDeltaOn: Int64 = 0
|
||||
var totalDeltaAll: Int64 = 0
|
||||
for i in 0..<currentResidencies.count {
|
||||
totalDeltaOn += currentResidencies[i].on - self.previousANEResidencies[i].on
|
||||
totalDeltaAll += currentResidencies[i].total - self.previousANEResidencies[i].total
|
||||
let now = Date()
|
||||
defer {
|
||||
self.previousANEEnergy = currentEnergy
|
||||
self.previousANERead = now
|
||||
}
|
||||
|
||||
guard totalDeltaAll > 0 else { return 0 }
|
||||
return Double(totalDeltaOn) / Double(totalDeltaAll)
|
||||
guard let previousRead = self.previousANERead else { return 0 }
|
||||
let elapsed = now.timeIntervalSince(previousRead)
|
||||
guard elapsed > 0 else { return 0 }
|
||||
return (currentEnergy - self.previousANEEnergy) / elapsed
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user