@@ -24,14 +24,32 @@ let vendors: [Data: String] = [
2424 Data . init ( [ 0x02 , 0x10 , 0x00 , 0x00 ] ) : " AMD "
2525]
2626
27+ private func maxANEPower( for platform: Platform ? ) -> Double {
28+ switch platform {
29+ case . m1, . m1Pro, . m1Max: return 2.0
30+ case . m1Ultra: return 4.0
31+ case . m2, . m2Pro, . m2Max: return 2.5
32+ case . m2Ultra: return 5.0
33+ case . m3, . m3Pro, . m3Max: return 3.0
34+ case . m3Ultra: return 6.0
35+ case . m4, . m4Pro, . m4Max: return 6.0
36+ case . m4Ultra: return 12.0
37+ case . m5, . m5Pro, . m5Max: return 8.0
38+ case . m5Ultra: return 16.0
39+ default : return 8.0
40+ }
41+ }
42+
2743internal class InfoReader : Reader < GPUs > {
2844 private var gpus : GPUs = GPUs ( )
2945 private var displays : [ gpu_s ] = [ ]
3046 private var devices : [ device ] = [ ]
3147
3248 private var aneChannels : CFMutableDictionary ? = nil
3349 private var aneSubscription : IOReportSubscriptionRef ? = nil
34- private var previousANEResidencies : [ ( on: Int64 , total: Int64 ) ] = [ ]
50+ private var previousANEEnergy : Double = 0
51+ private var previousANERead : Date ? = nil
52+ private var aneMaxPower : Double = 8.0
3553
3654 private var framesChannels : CFMutableDictionary ? = nil
3755 private var framesSubscription : IOReportSubscriptionRef ? = nil
@@ -49,6 +67,7 @@ internal class InfoReader: Reader<GPUs> {
4967 let devices = PCIdevices . filter { $0. object ( forKey: " IOName " ) as? String == " display " }
5068
5169 #if arch(arm64)
70+ self . aneMaxPower = maxANEPower ( for: SystemKit . shared. device. platform)
5271 self . setupANE ( )
5372 self . setupFrames ( )
5473 #endif
@@ -220,10 +239,11 @@ internal class InfoReader: Reader<GPUs> {
220239 }
221240
222241 #if arch(arm64)
223- let aneValue = self . readANEUtilization ( )
242+ let anePower = self . readANEPower ( )
243+ let aneUtil = anePower. map { min ( 1.0 , max ( 0.0 , $0 / self . aneMaxPower) ) }
224244 let fpsValue = self . readFrames ( )
225245 for i in self . gpus. list. indices where self . gpus. list [ i] . IOClass. lowercased ( ) . contains ( " agx " ) {
226- self . gpus. list [ i] . aneUtilization = aneValue ?? 0
246+ self . gpus. list [ i] . aneUtilization = aneUtil ?? 0
227247 self . gpus. list [ i] . fps = fpsValue
228248 }
229249 #endif
@@ -288,10 +308,10 @@ internal class InfoReader: Reader<GPUs> {
288308 return Double ( delta) / elapsed
289309 }
290310
291- // MARK: - ANE utilization
311+ // MARK: - ANE power
292312
293313 private func setupANE( ) {
294- guard let channel = IOReportCopyChannelsInGroup ( " SoC Stats " as CFString , " Cluster Power States " as CFString , 0 , 0 , 0 ) ? . takeRetainedValue ( ) else { return }
314+ guard let channel = IOReportCopyChannelsInGroup ( " Energy Model " as CFString , nil , 0 , 0 , 0 ) ? . takeRetainedValue ( ) else { return }
295315
296316 let size = CFDictionaryGetCount ( channel)
297317 guard let mutable = CFDictionaryCreateMutableCopy ( kCFAllocatorDefault, size, channel) ,
@@ -303,7 +323,7 @@ internal class InfoReader: Reader<GPUs> {
303323 sub? . release ( )
304324 }
305325
306- private func readANEUtilization ( ) -> Double ? {
326+ private func readANEPower ( ) -> Double ? {
307327 guard let subscription = self . aneSubscription,
308328 let channels = self . aneChannels,
309329 let reportSample = IOReportCreateSamples ( subscription, channels, nil ) ? . takeRetainedValue ( ) ,
@@ -312,48 +332,45 @@ internal class InfoReader: Reader<GPUs> {
312332 }
313333 let items = dict [ " IOReportChannels " ] as! CFArray
314334
315- var currentResidencies : [ ( on: Int64 , total: Int64 ) ] = [ ]
335+ var currentEnergy : Double = 0
336+ var found = false
316337
317338 for i in 0 ..< CFArrayGetCount ( items) {
318339 let item = unsafeBitCast ( CFArrayGetValueAtIndex ( items, i) , to: CFDictionary . self)
319340
320341 guard let group = IOReportChannelGetGroup ( item) ? . takeUnretainedValue ( ) as? String ,
321- group == " SoC Stats " ,
322- let subgroup = IOReportChannelGetSubGroup ( item) ? . takeUnretainedValue ( ) as? String ,
323- subgroup == " Cluster Power States " ,
342+ group == " Energy Model " ,
324343 let channel = IOReportChannelGetChannelName ( item) ? . takeUnretainedValue ( ) as? String ,
325- channel. hasPrefix ( " ANE " ) else { continue }
344+ channel. starts ( with : " ANE " ) else { continue }
326345
327- let stateCount = IOReportStateGetCount ( item)
328- guard stateCount == 2 else { continue }
346+ let raw = Double ( IOReportSimpleGetIntegerValue ( item, 0 ) )
347+ let unit = ( IOReportChannelGetUnitLabel ( item) ? . takeUnretainedValue ( ) as? String ) ?
348+ . trimmingCharacters ( in: . whitespaces) ?? " "
329349
330- var on : Int64 = 0
331- var total : Int64 = 0
332- for s in 0 ..< stateCount {
333- let residency = IOReportStateGetResidency ( item, s)
334- let name = IOReportStateGetNameForIndex ( item, s) ? . takeUnretainedValue ( ) as? String ?? " "
335- total += residency
336- if name != " INACT " {
337- on += residency
338- }
350+ let joules : Double
351+ switch unit. lowercased ( ) {
352+ case " mj " : joules = raw / 1e3
353+ case " uj " , " µj " : joules = raw / 1e6
354+ case " nj " : joules = raw / 1e9
355+ case " pj " : joules = raw / 1e12
356+ default : joules = raw / 1e9
339357 }
340358
341- currentResidencies. append ( ( on: on, total: total) )
359+ currentEnergy += joules
360+ found = true
342361 }
343362
344- guard !currentResidencies. isEmpty else { return nil }
345-
346- defer { self . previousANEResidencies = currentResidencies }
347- guard self . previousANEResidencies. count == currentResidencies. count else { return nil }
363+ guard found else { return nil }
348364
349- var totalDeltaOn : Int64 = 0
350- var totalDeltaAll : Int64 = 0
351- for i in 0 ..< currentResidencies. count {
352- totalDeltaOn += currentResidencies [ i] . on - self . previousANEResidencies [ i] . on
353- totalDeltaAll += currentResidencies [ i] . total - self . previousANEResidencies [ i] . total
365+ let now = Date ( )
366+ defer {
367+ self . previousANEEnergy = currentEnergy
368+ self . previousANERead = now
354369 }
355370
356- guard totalDeltaAll > 0 else { return 0 }
357- return Double ( totalDeltaOn) / Double( totalDeltaAll)
371+ guard let previousRead = self . previousANERead else { return 0 }
372+ let elapsed = now. timeIntervalSince ( previousRead)
373+ guard elapsed > 0 else { return 0 }
374+ return ( currentEnergy - self . previousANEEnergy) / elapsed
358375 }
359376}
0 commit comments