Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

155 lines
5.7 KiB
Swift

import Darwin
import Foundation
struct SystemStatusPowerEnergySample: Equatable, Sendable {
let joules: Double
let date: Date
}
final class SystemStatusCPUPowerReader {
private let functions: IOReportFunctions?
private let channels: CFMutableDictionary?
private let subscription: OpaquePointer?
init() {
guard
let functions = IOReportFunctions(),
let baseChannels = functions.copyChannelsInGroup("Energy Model" as CFString, nil, 0, 0, 0)?.takeRetainedValue(),
let channels = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, baseChannels)
else {
self.functions = nil
self.channels = nil
self.subscription = nil
return
}
var subscriptionChannels: Unmanaged<CFMutableDictionary>?
self.functions = functions
self.channels = channels
self.subscription = functions.createSubscription(nil, channels, &subscriptionChannels, 0, nil)
subscriptionChannels?.release()
}
func readCPUEnergySample(referenceDate: Date) -> SystemStatusPowerEnergySample? {
guard
let functions,
let channels,
let sample = functions.createSamples(subscription, channels, nil)?.takeRetainedValue(),
let sampleDictionary = sample as? [String: Any],
let rawItems = sampleDictionary["IOReportChannels"]
else {
return nil
}
let items = rawItems as! CFArray
var cpuEnergyJoules: Double?
for index in 0..<CFArrayGetCount(items) {
let item = unsafeBitCast(CFArrayGetValueAtIndex(items, index), to: CFDictionary.self)
guard
let group = functions.channelGetGroup(item)?.takeUnretainedValue() as String?,
group == "Energy Model",
let channelName = functions.channelGetChannelName(item)?.takeUnretainedValue() as String?,
channelName.hasSuffix("CPU Energy"),
let unit = functions.channelGetUnitLabel(item)?.takeUnretainedValue() as String?,
let joules = SystemStatusPowerNormalizer.energyJoules(
from: Double(functions.simpleGetIntegerValue(item, 0)),
unit: unit
)
else {
continue
}
cpuEnergyJoules = joules
}
guard let cpuEnergyJoules else {
return nil
}
return SystemStatusPowerEnergySample(joules: cpuEnergyJoules, date: referenceDate)
}
}
private struct IOReportFunctions {
typealias CopyChannelsInGroup = @convention(c) (CFString?, CFString?, UInt64, UInt64, UInt64) -> Unmanaged<CFDictionary>?
typealias CreateSubscription = @convention(c) (
UnsafeMutableRawPointer?,
CFMutableDictionary?,
UnsafeMutablePointer<Unmanaged<CFMutableDictionary>?>?,
UInt64,
CFTypeRef?
) -> OpaquePointer?
typealias CreateSamples = @convention(c) (OpaquePointer?, CFMutableDictionary?, CFTypeRef?) -> Unmanaged<CFDictionary>?
typealias ChannelString = @convention(c) (CFDictionary) -> Unmanaged<CFString>?
typealias SimpleGetIntegerValue = @convention(c) (CFDictionary, Int32) -> Int64
let handle: UnsafeMutableRawPointer
let copyChannelsInGroup: CopyChannelsInGroup
let createSubscription: CreateSubscription
let createSamples: CreateSamples
let channelGetGroup: ChannelString
let channelGetChannelName: ChannelString
let channelGetUnitLabel: ChannelString
let simpleGetIntegerValue: SimpleGetIntegerValue
init?() {
guard
let handle = dlopen("/usr/lib/libIOReport.dylib", RTLD_LAZY),
let copyChannelsInGroup = Self.loadFunction(
named: "IOReportCopyChannelsInGroup",
from: handle,
as: CopyChannelsInGroup.self
),
let createSubscription = Self.loadFunction(
named: "IOReportCreateSubscription",
from: handle,
as: CreateSubscription.self
),
let createSamples = Self.loadFunction(
named: "IOReportCreateSamples",
from: handle,
as: CreateSamples.self
),
let channelGetGroup = Self.loadFunction(
named: "IOReportChannelGetGroup",
from: handle,
as: ChannelString.self
),
let channelGetChannelName = Self.loadFunction(
named: "IOReportChannelGetChannelName",
from: handle,
as: ChannelString.self
),
let channelGetUnitLabel = Self.loadFunction(
named: "IOReportChannelGetUnitLabel",
from: handle,
as: ChannelString.self
),
let simpleGetIntegerValue = Self.loadFunction(
named: "IOReportSimpleGetIntegerValue",
from: handle,
as: SimpleGetIntegerValue.self
)
else {
return nil
}
self.handle = handle
self.copyChannelsInGroup = copyChannelsInGroup
self.createSubscription = createSubscription
self.createSamples = createSamples
self.channelGetGroup = channelGetGroup
self.channelGetChannelName = channelGetChannelName
self.channelGetUnitLabel = channelGetUnitLabel
self.simpleGetIntegerValue = simpleGetIntegerValue
}
private static func loadFunction<T>(named name: String, from handle: UnsafeMutableRawPointer, as type: T.Type) -> T? {
guard let symbol = dlsym(handle, name) else {
return nil
}
return unsafeBitCast(symbol, to: type)
}
}