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? 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.. Unmanaged? typealias CreateSubscription = @convention(c) ( UnsafeMutableRawPointer?, CFMutableDictionary?, UnsafeMutablePointer?>?, UInt64, CFTypeRef? ) -> OpaquePointer? typealias CreateSamples = @convention(c) (OpaquePointer?, CFMutableDictionary?, CFTypeRef?) -> Unmanaged? typealias ChannelString = @convention(c) (CFDictionary) -> Unmanaged? 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(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) } }