feat: added efficiency and performance cores frequency and total cores frequency

This commit is contained in:
Serhiy Mytrovtsiy
2024-12-20 17:07:27 +01:00
parent 8fac9e4eb3
commit d68d691b18
14 changed files with 336 additions and 195 deletions

View File

@@ -662,6 +662,23 @@ internal func getIOName(_ entry: io_registry_entry_t) -> String? {
return String(cString: UnsafeRawPointer(pointer).assumingMemoryBound(to: CChar.self))
}
internal func convertCFDataToArr(_ data: CFData) -> [Int32] {
let length = CFDataGetLength(data)
var bytes = [UInt8](repeating: 0, count: length)
CFDataGetBytes(data, CFRange(location: 0, length: length), &bytes)
var arr: [Int32] = []
var chunks = stride(from: 0, to: bytes.count, by: 8).map { Array(bytes[$0..<min($0 + 8, bytes.count)])}
for chunk in chunks {
let v = UInt32(chunk[0]) | UInt32(chunk[1]) << 8 | UInt32(chunk[2]) << 16 | UInt32(chunk[3]) << 24
arr.append(Int32(v / 1000 / 1000))
}
bytes.removeAll()
chunks.removeAll()
return arr
}
public class ColorView: NSView {
public var inactiveColor: NSColor = NSColor.lightGray.withAlphaComponent(0.75)

View File

@@ -109,6 +109,8 @@ public struct cpu_s {
public var eCores: Int32? = nil
public var pCores: Int32? = nil
public var cores: [core_s]? = nil
public var eCoreFrequencies: [Int32]? = nil
public var pCoreFrequencies: [Int32]? = nil
}
public struct dimm_s {
@@ -128,6 +130,7 @@ public struct gpu_s {
public var vendor: String? = nil
public var vram: String? = nil
public var cores: Int? = nil
public var frequencies: [Int32]? = nil
}
public struct info_s {
@@ -319,6 +322,10 @@ public class SystemKit {
cpu.pCores = cores.1
cpu.cores = cores.2
}
if let freq = getFrequencies() {
cpu.eCoreFrequencies = freq.0
cpu.pCoreFrequencies = freq.1
}
return cpu
}
@@ -386,10 +393,8 @@ public class SystemKit {
IOObjectRelease(child)
}
IOObjectRelease(entry)
IOObjectRelease(service)
}
IOObjectRelease(iterator)
return (eCores, pCores, list)
@@ -417,6 +422,10 @@ public class SystemKit {
gpu.vram = vram
}
if let freq = getFrequencies() {
gpu.frequencies = freq.2
}
list.append(gpu)
}
}
@@ -429,6 +438,36 @@ public class SystemKit {
return list
}
private func getFrequencies() -> ([Int32], [Int32], [Int32])? {
var iterator = io_iterator_t()
let result = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("AppleARMIODevice"), &iterator)
if result != kIOReturnSuccess {
print("Error find AppleARMIODevice: " + (String(cString: mach_error_string(result), encoding: String.Encoding.ascii) ?? "unknown error"))
return nil
}
var eFreq: [Int32] = []
var pFreq: [Int32] = []
var gpuFreq: [Int32] = []
while case let child = IOIteratorNext(iterator), child != 0 {
defer { IOObjectRelease(child) }
guard let name = getIOName(child), name == "pmgr", let props = getIOProperties(child) else { continue }
if let data = props.value(forKey: "voltage-states1-sram") {
eFreq = convertCFDataToArr(data as! CFData)
}
if let data = props.value(forKey: "voltage-states5-sram") {
pFreq = convertCFDataToArr(data as! CFData)
}
if let data = props.value(forKey: "voltage-states9-sram") {
gpuFreq = convertCFDataToArr(data as! CFData)
}
}
return (eFreq, pFreq, gpuFreq)
}
public func getRamInfo() -> ram_s? {
guard let res = process(path: "/usr/sbin/system_profiler", arguments: ["SPMemoryDataType", "-json"]) else {
return nil

32
Modules/CPU/bridge.h Normal file
View File

@@ -0,0 +1,32 @@
//
// bridge.h
// Stats
//
// Created by Serhiy Mytrovtsiy on 17/12/2024
// Using Swift 6.0
// Running on macOS 15.1
//
// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved.
//
#ifndef bridge_h
#define bridge_h
#include <CoreFoundation/CoreFoundation.h>
typedef struct IOReportSubscriptionRef* IOReportSubscriptionRef;
CFDictionaryRef IOReportCopyChannelsInGroup(CFStringRef a, CFStringRef b, uint64_t c, uint64_t d, uint64_t e);
void IOReportMergeChannels(CFDictionaryRef a, CFDictionaryRef b, CFTypeRef null);
IOReportSubscriptionRef IOReportCreateSubscription(void* a, CFMutableDictionaryRef b, CFMutableDictionaryRef* c, uint64_t d, CFTypeRef e);
CFDictionaryRef IOReportCreateSamples(IOReportSubscriptionRef a, CFMutableDictionaryRef b, CFTypeRef c);
CFDictionaryRef IOReportCreateSamplesDelta(CFDictionaryRef a, CFDictionaryRef b, CFTypeRef c);
CFStringRef IOReportChannelGetGroup(CFDictionaryRef a);
CFStringRef IOReportChannelGetSubGroup(CFDictionaryRef a);
CFStringRef IOReportChannelGetChannelName(CFDictionaryRef a);
CFStringRef IOReportChannelGetUnitLabel(CFDictionaryRef a);
int32_t IOReportStateGetCount(CFDictionaryRef a);
CFStringRef IOReportStateGetNameForIndex(CFDictionaryRef a, int32_t b);
int64_t IOReportStateGetResidency(CFDictionaryRef a, int32_t b);
#endif /* bridge_h */

View File

@@ -75,7 +75,7 @@ public class CPU: Module {
return color.additional as! NSColor
}
private var pCoreColor: NSColor {
let color = SColor.secondBlue
let color = SColor.indigo
let key = Store.shared.string(key: "\(self.config.name)_pCoresColor", defaultValue: color.key)
if let c = SColor.fromString(key).additional as? NSColor {
return c
@@ -115,7 +115,8 @@ public class CPU: Module {
self.limitReader = LimitReader(.CPU, popup: true) { [weak self] value in
self?.popupView.limitCallback(value)
}
self.frequencyReader = FrequencyReader(.CPU, popup: true) { [weak self] value in
#else
self.frequencyReader = FrequencyReader(.CPU, popup: false) { [weak self] value in
self?.popupView.frequencyCallback(value)
}
#endif
@@ -135,12 +136,6 @@ public class CPU: Module {
self.settingsView.setTopInterval = { [weak self] value in
self?.processReader?.setInterval(value)
}
self.settingsView.IPGCallback = { [weak self] value in
if value {
self?.frequencyReader?.setup()
}
self?.popupView.toggleFrequency(state: value)
}
self.setReaders([
self.loadReader,

View File

@@ -32,6 +32,18 @@ internal class Popup: PopupWrapper {
}
}
private let averageHeight: CGFloat = (22*3) + Constants.Popup.separatorHeight
private var frequencyHeight: CGFloat {
get {
var count: CGFloat = 1
if SystemKit.shared.device.info.cpu?.eCores != nil {
count += 1
}
if SystemKit.shared.device.info.cpu?.pCores != nil {
count += 1
}
return (22*count) + Constants.Popup.separatorHeight
}
}
private let processHeight: CGFloat = 22
private var systemField: NSTextField? = nil
@@ -45,6 +57,11 @@ internal class Popup: PopupWrapper {
private var average1Field: NSTextField? = nil
private var average5Field: NSTextField? = nil
private var average15Field: NSTextField? = nil
private var coresFreqField: NSTextField? = nil
private var eCoresFreqField: NSTextField? = nil
private var pCoresFreqField: NSTextField? = nil
private var eCoresFreqColorView: NSView? = nil
private var pCoresFreqColorView: NSView? = nil
private var systemColorView: NSView? = nil
private var userColorView: NSView? = nil
@@ -83,7 +100,7 @@ internal class Popup: PopupWrapper {
private var chartColor: NSColor { self.chartColorState.additional as? NSColor ?? NSColor.systemBlue }
private var eCoresColorState: SColor = .teal
private var eCoresColor: NSColor { self.eCoresColorState.additional as? NSColor ?? NSColor.systemTeal }
private var pCoresColorState: SColor = .secondBlue
private var pCoresColorState: SColor = .indigo
private var pCoresColor: NSColor { self.pCoresColorState.additional as? NSColor ?? NSColor.systemBlue }
private var numberOfProcesses: Int {
@@ -115,7 +132,7 @@ internal class Popup: PopupWrapper {
width: Constants.Popup.width,
height: self.dashboardHeight + self.chartHeight + self.averageHeight
))
self.setFrameSize(NSSize(width: self.frame.width, height: self.frame.height + self.detailsHeight + self.processesHeight))
self.setFrameSize(NSSize(width: self.frame.width, height: self.frame.height + self.detailsHeight + self.frequencyHeight + self.processesHeight))
self.systemColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_systemColor", defaultValue: self.systemColorState.key))
self.userColorState = SColor.fromString(Store.shared.string(key: "\(self.title)_userColor", defaultValue: self.userColorState.key))
@@ -135,12 +152,14 @@ internal class Popup: PopupWrapper {
gridView.addRow(with: [self.initChart()])
gridView.addRow(with: [self.initDetails()])
gridView.addRow(with: [self.initAverage()])
gridView.addRow(with: [self.initFrequency()])
gridView.addRow(with: [self.initProcesses()])
gridView.row(at: 0).height = self.dashboardHeight
gridView.row(at: 1).height = self.chartHeight
gridView.row(at: 2).height = self.detailsHeight
gridView.row(at: 3).height = self.averageHeight
gridView.row(at: 4).height = self.frequencyHeight
self.addSubview(gridView)
self.grid = gridView
@@ -189,7 +208,7 @@ internal class Popup: PopupWrapper {
let usage = NSView(frame: NSRect(x: usageX, y: (view.frame.height - usageSize)/2, width: usageSize, height: usageSize))
let temperature = NSView(frame: NSRect(x: (usageX - 50)/2, y: (view.frame.height - 50)/2 - 3, width: 50, height: 50))
let frequency = NSView(frame: NSRect(x: (usageX+usageSize) + (usageX - 50)/2, y: (view.frame.height - 50)/2 - 3, width: 50, height: 50))
let frequency = NSView(frame: NSRect(x: (usageX+usageSize) + (usageX - 50)/2, y: 0, width: 50, height: self.dashboardHeight))
self.circle = PieChartView(frame: NSRect(x: 0, y: 0, width: usage.frame.width, height: usage.frame.height), segments: [], drawValue: true)
self.circle!.toolTip = localizedString("CPU usage")
@@ -315,6 +334,30 @@ internal class Popup: PopupWrapper {
return view
}
private func initFrequency() -> NSView {
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.frequencyHeight))
let separator = separatorView(localizedString("Frequency"), origin: NSPoint(x: 0, y: self.frequencyHeight-Constants.Popup.separatorHeight), width: self.frame.width)
let container: NSStackView = NSStackView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: separator.frame.origin.y))
container.orientation = .vertical
container.spacing = 0
self.coresFreqField = popupRow(container, title: "\(localizedString("All cores")):", value: "").1
if isARM {
if SystemKit.shared.device.info.cpu?.eCores != nil {
(self.eCoresFreqColorView, _, self.eCoresFreqField) = popupWithColorRow(container, color: self.eCoresColor, title: "\(localizedString("Efficiency cores")):", value: "")
}
if SystemKit.shared.device.info.cpu?.pCores != nil {
(self.pCoresFreqColorView, _, self.pCoresFreqField) = popupWithColorRow(container, color: self.pCoresColor, title: "\(localizedString("Performance cores")):", value: "")
}
}
view.addSubview(separator)
view.addSubview(container)
return view
}
private func initProcesses() -> NSView {
if self.numberOfProcesses == 0 { return NSView() }
@@ -388,7 +431,7 @@ internal class Popup: PopupWrapper {
})
}
public func frequencyCallback(_ value: Double?) {
public func frequencyCallback(_ value: [Double]?) {
guard let value else { return }
DispatchQueue.main.async(execute: {
@@ -397,13 +440,33 @@ internal class Popup: PopupWrapper {
}
if (self.window?.isVisible ?? false) || !self.initializedFrequency {
if value > self.maxFreq {
self.maxFreq = value
}
if let freqCircle = self.frequencyCircle {
freqCircle.setValue((100*value)/self.maxFreq)
freqCircle.setText("\((value/1000).rounded(toPlaces: 2))")
if value.count == 1 {
let freq = value.first ?? 0
if freq > self.maxFreq {
self.maxFreq = freq
}
self.coresFreqField?.stringValue = "\(Int(freq)) MHz"
if let circle = self.frequencyCircle {
circle.setValue((100*freq)/self.maxFreq)
circle.setText("\((freq/1000).rounded(toPlaces: 2))")
}
} else if value.count == 2 {
let e = value.first ?? 0
let p = value.last ?? 0
self.eCoresFreqField?.stringValue = "\(Int(e)) MHz"
self.pCoresFreqField?.stringValue = "\(Int(p)) MHz"
if let eCoreCount = SystemKit.shared.device.info.cpu?.eCores, let pCoreCount = SystemKit.shared.device.info.cpu?.pCores {
let freq = ((e * Double(eCoreCount)) + (p * Double(pCoreCount))) / Double(eCoreCount + pCoreCount)
if freq > self.maxFreq {
self.maxFreq = freq
}
self.coresFreqField?.stringValue = "\(Int(freq)) MHz"
if let circle = self.frequencyCircle {
circle.setValue((100*freq)/self.maxFreq)
circle.setText("\((freq/1000).rounded(toPlaces: 2))")
}
}
}
self.initializedFrequency = true
@@ -461,15 +524,6 @@ internal class Popup: PopupWrapper {
})
}
public func toggleFrequency(state: Bool) {
DispatchQueue.main.async(execute: {
if let view = self.frequencyCircle {
view.isHidden = !state
}
self.initializedFrequency = false
})
}
// MARK: - Settings
public override func settings() -> NSView? {
@@ -578,7 +632,10 @@ internal class Popup: PopupWrapper {
}
self.eCoresColorState = newValue
Store.shared.set(key: "\(self.title)_eCoresColor", value: key)
self.eCoresColorView?.layer?.backgroundColor = (newValue.additional as? NSColor)?.cgColor
if let color = (newValue.additional as? NSColor) {
self.eCoresColorView?.layer?.backgroundColor = color.cgColor
self.eCoresFreqColorView?.layer?.backgroundColor = color.cgColor
}
}
@objc private func togglePCoresColor(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String, let newValue = SColor.allColors.first(where: { $0.key == key }) else {
@@ -586,7 +643,10 @@ internal class Popup: PopupWrapper {
}
self.pCoresColorState = newValue
Store.shared.set(key: "\(self.title)_pCoresColor", value: key)
self.pCoresColorView?.layer?.backgroundColor = (newValue.additional as? NSColor)?.cgColor
if let color = (newValue.additional as? NSColor) {
self.pCoresColorView?.layer?.backgroundColor = color.cgColor
self.pCoresFreqColorView?.layer?.backgroundColor = color.cgColor
}
}
@objc private func toggleLineChartHistory(_ sender: NSMenuItem) {
guard let key = sender.representedObject as? String, let value = Int(key) else { return }

View File

@@ -43,7 +43,7 @@ public class Portal: PortalWrapper {
private var idleColor: NSColor { self.idleColorState.additional as? NSColor ?? NSColor.lightGray }
private var eCoresColorState: SColor = .teal
private var eCoresColor: NSColor { self.eCoresColorState.additional as? NSColor ?? NSColor.systemTeal }
private var pCoresColorState: SColor = .secondBlue
private var pCoresColorState: SColor = .indigo
private var pCoresColor: NSColor { self.pCoresColorState.additional as? NSColor ?? NSColor.systemBlue }
public override func load() {

View File

@@ -289,161 +289,173 @@ public class TemperatureReader: Reader<Double> {
}
}
public class FrequencyReader: Reader<Double> {
private typealias PGSample = UInt64
private typealias UDouble = UnsafeMutablePointer<Double>
// inspired by https://github.com/shank03/StatsBar/blob/e175aa71c914ce882ce2e90163f3eb18262a8e25/StatsBar/Service/IOReport.swift
public class FrequencyReader: Reader<[Double]> {
private var eCoreFreqs: [Int32] = []
private var pCoreFreqs: [Int32] = []
private typealias PG_InitializePointerFunction = @convention(c) () -> Bool
private typealias PG_ShutdownPointerFunction = @convention(c) () -> Bool
private typealias PG_ReadSamplePointerFunction = @convention(c) (Int, UnsafeMutablePointer<PGSample>) -> Bool
private typealias PGSample_GetIAFrequencyPointerFunction = @convention(c) (PGSample, PGSample, UDouble, UDouble, UDouble) -> Bool
private typealias PGSample_ReleasePointerFunction = @convention(c) (PGSample) -> Bool
private var channels: CFMutableDictionary? = nil
private var subscription: IOReportSubscriptionRef? = nil
private var prev: (samples: CFDictionary, time: TimeInterval)? = nil
private var bundle: CFBundle? = nil
private let measurementCount: Int = 4
private var pgIntialize: PG_InitializePointerFunction? = nil
private var pgShutdown: PG_ShutdownPointerFunction? = nil
private var pgReadSample: PG_ReadSamplePointerFunction? = nil
private var pgSampleGetIAFrequency: PGSample_GetIAFrequencyPointerFunction? = nil
private var pgSampleRelease: PGSample_ReleasePointerFunction? = nil
private var sample: PGSample = 0
private var reconnectAttempt: Int = 0
private var isEnabled: Bool {
get {
return Store.shared.bool(key: "CPU_IPG", defaultValue: false)
}
private struct IOSample {
let group: String
let subGroup: String
let channel: String
let unit: String
let delta: CFDictionary
}
public override func setup() {
guard self.isEnabled else { return }
let path: CFString = "/Library/Frameworks/IntelPowerGadget.framework" as CFString
let bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path, CFURLPathStyle.cfurlposixPathStyle, true)
self.bundle = CFBundleCreate(kCFAllocatorDefault, bundleURL)
if self.bundle == nil {
error("IntelPowerGadget framework not found", log: self.log)
return
}
if !CFBundleLoadExecutable(self.bundle) {
error("failed to load IPG framework", log: self.log)
return
}
guard let pgIntialize = CFBundleGetFunctionPointerForName(self.bundle, "PG_Initialize" as CFString) else {
error("failed to find PG_Initialize", log: self.log)
return
}
guard let pgShutdown = CFBundleGetFunctionPointerForName(self.bundle, "PG_Shutdown" as CFString) else {
error("failed to find PG_Shutdown", log: self.log)
return
}
guard let pgReadSample = CFBundleGetFunctionPointerForName(self.bundle, "PG_ReadSample" as CFString) else {
error("failed to find PG_ReadSample", log: self.log)
return
}
guard let pgSampleGetIAFrequency = CFBundleGetFunctionPointerForName(self.bundle, "PGSample_GetIAFrequency" as CFString) else {
error("failed to find PGSample_GetIAFrequency", log: self.log)
return
}
guard let pgSampleRelease = CFBundleGetFunctionPointerForName(self.bundle, "PGSample_Release" as CFString) else {
error("failed to find PGSample_Release", log: self.log)
return
}
self.pgIntialize = unsafeBitCast(pgIntialize, to: PG_InitializePointerFunction.self)
self.pgShutdown = unsafeBitCast(pgShutdown, to: PG_ShutdownPointerFunction.self)
self.pgReadSample = unsafeBitCast(pgReadSample, to: PG_ReadSamplePointerFunction.self)
self.pgSampleGetIAFrequency = unsafeBitCast(pgSampleGetIAFrequency, to: PGSample_GetIAFrequencyPointerFunction.self)
self.pgSampleRelease = unsafeBitCast(pgSampleRelease, to: PGSample_ReleasePointerFunction.self)
if let initialize = self.pgIntialize {
if !initialize() {
error("IPG initialization failed", log: self.log)
return
}
}
}
deinit {
if let bundle = self.bundle {
CFBundleUnloadExecutable(bundle)
}
}
public override func terminate() {
if let shutdown = self.pgShutdown {
if !shutdown() {
error("IPG shutdown failed", log: self.log)
return
}
}
if let release = self.pgSampleRelease {
if self.sample != 0 {
_ = release(self.sample)
return
}
}
}
private func reconnect() {
if self.reconnectAttempt >= 5 {
return
}
self.sample = 0
self.terminate()
if let initialize = self.pgIntialize {
if !initialize() {
error("IPG initialization failed", log: self.log)
return
}
}
self.reconnectAttempt += 1
self.eCoreFreqs = SystemKit.shared.device.info.cpu?.eCoreFrequencies ?? []
self.pCoreFreqs = SystemKit.shared.device.info.cpu?.pCoreFrequencies ?? []
self.channels = self.getChannels()
var dict: Unmanaged<CFMutableDictionary>?
self.subscription = IOReportCreateSubscription(nil, self.channels, &dict, 0, nil)
}
public override func read() {
if !self.isEnabled || self.pgReadSample == nil || self.pgSampleGetIAFrequency == nil || self.pgSampleRelease == nil {
return
}
guard !self.eCoreFreqs.isEmpty && !self.pCoreFreqs.isEmpty, self.channels != nil, self.subscription != nil else { return }
let minECoreFreq = Double(self.eCoreFreqs.min() ?? 0)
let minPCoreFreq = Double(self.pCoreFreqs.min() ?? 0)
// first sample initlialization
if self.sample == 0 {
if !self.pgReadSample!(0, &self.sample) {
error("read self.sample failed", log: self.log)
Task {
var eCores: [Double] = []
var pCores: [Double] = []
for (samples, _) in await self.getSamples() {
var eCore: [Double] = []
var pCore: [Double] = []
for sample in samples {
guard sample.group == "CPU Stats" else { continue }
if sample.channel.starts(with: "ECPU") {
eCore.append(self.calculateFrequencies(dict: sample.delta, freqs: self.eCoreFreqs))
}
if sample.channel.starts(with: "PCPU") {
pCore.append(self.calculateFrequencies(dict: sample.delta, freqs: self.pCoreFreqs))
}
}
let eCoresAvgFreq: Double = eCore.isEmpty ? 0 : (eCore.reduce(0.0, { $0 + $1 }) / Double(eCore.count))
let pCoresAvgFreq: Double = pCore.isEmpty ? 0 : (pCore.reduce(0.0, { $0 + $1 }) / Double(pCore.count))
eCores.append(max(eCoresAvgFreq, minECoreFreq))
pCores.append(max(pCoresAvgFreq, minPCoreFreq))
}
return
let eFreq: Double = eCores.reduce(0, { $0 + $1 }) / Double(self.measurementCount)
let pFreq: Double = pCores.reduce(0, { $0 + $1 }) / Double(self.measurementCount)
self.callback([eFreq, pFreq])
}
}
private func calculateFrequencies(dict: CFDictionary, freqs: [Int32]) -> Double {
let items = self.getResidencies(dict: dict)
let offset = items.firstIndex { $0.0 != "IDLE" && $0.0 != "DOWN" && $0.0 != "OFF" }!
let usage = items.dropFirst(offset).reduce(0.0) { $0 + Double($1.f) }
let count = freqs.count
var avgFreq: Double = 0
for i in 0..<count {
let percent = usage == 0 ? 0 : Double(items[i + offset].f) / usage
avgFreq += percent * Double(freqs[i])
}
var local: PGSample = 0
var value: Double = 0
var min: Double = 0
var max: Double = 0
return avgFreq
}
func getResidencies(dict: CFDictionary) -> [(ns: String, f: Int64)] {
let count = IOReportStateGetCount(dict)
var res: [(String, Int64)] = []
if !self.pgReadSample!(0, &local) {
self.reconnect()
error("read local sample failed", log: self.log)
return
for i in 0..<count {
let name = IOReportStateGetNameForIndex(dict, i)?.takeUnretainedValue() ?? ("" as CFString)
let val = IOReportStateGetResidency(dict, i)
res.append((name as String, val))
}
defer {
if !self.pgSampleRelease!(self.sample) {
error("release self.sample failed", log: self.log)
return res
}
private func getChannels() -> CFMutableDictionary? {
let channelNames = [
("CPU Stats", "CPU Complex Performance States"),
("CPU Stats", "CPU Core Performance States")
]
var channels: [CFDictionary] = []
for (gname, sname) in channelNames {
let channel = IOReportCopyChannelsInGroup(gname as CFString?, sname as CFString?, 0, 0, 0)
guard let channel = channel?.takeRetainedValue() else { continue }
channels.append(channel)
}
let chan = channels[0]
for i in 1..<channels.count {
IOReportMergeChannels(chan, channels[i], nil)
}
let size = CFDictionaryGetCount(chan)
guard let channel = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, size, chan),
let chan = channel as? [String: Any], chan["IOReportChannels"] != nil else {
return nil
}
return channel
}
private func getSamples() async -> [([IOSample], TimeInterval)] {
let duration = 500
let step = UInt64(duration / self.measurementCount)
var prev = self.prev ?? self.getSample() ?? self.prev!
var samples = [([IOSample], TimeInterval)]()
for _ in 0..<self.measurementCount {
let milliseconds = UInt64(step) * 1_000_000
try? await Task.sleep(nanoseconds: milliseconds)
guard let next = self.getSample() else { continue }
guard let diff = IOReportCreateSamplesDelta(prev.samples, next.samples, nil)?.takeRetainedValue() else {
continue
}
self.sample = local
let elapsed = Date(timeIntervalSince1970: next.time).timeIntervalSince(Date(timeIntervalSince1970: prev.time))
prev = next
samples.append((self.collectIOSamples(data: diff), max(elapsed, TimeInterval(1))))
}
if !self.pgSampleGetIAFrequency!(self.sample, local, &value, &min, &max) {
error("read frequency failed", log: self.log)
return
self.prev = prev
return samples
}
private func getSample() -> (samples: CFDictionary, time: TimeInterval)? {
guard let sample = IOReportCreateSamples(self.subscription, self.channels, nil)?.takeRetainedValue() else {
return nil
}
return (sample, Date().timeIntervalSince1970)
}
private func collectIOSamples(data: CFDictionary) -> [IOSample] {
let dict = data as! [String: Any]
let items = dict["IOReportChannels"] as! CFArray
let itemSize = CFArrayGetCount(items)
var samples = [IOSample]()
for index in 0..<itemSize {
let dict = CFArrayGetValueAtIndex(items, index)
let item = unsafeBitCast(dict, to: CFDictionary.self)
let group = IOReportChannelGetGroup(item)?.takeUnretainedValue() ?? ("" as CFString)
let subGroup = IOReportChannelGetSubGroup(item)?.takeUnretainedValue() ?? ("" as CFString)
let channel = IOReportChannelGetChannelName(item)?.takeUnretainedValue() ?? ("" as CFString)
let unit = IOReportChannelGetUnitLabel(item)?.takeUnretainedValue() ?? ("" as CFString)
samples.append(IOSample(group: group as String, subGroup: subGroup as String, channel: channel as String, unit: unit as String, delta: item))
}
self.callback(value)
return samples
}
}

View File

@@ -16,7 +16,6 @@ internal class Settings: NSStackView, Settings_v {
private var usagePerCoreState: Bool = false
private var hyperthreadState: Bool = false
private var splitValueState: Bool = false
private var IPGState: Bool = false
private var updateIntervalValue: Int = 1
private var updateTopIntervalValue: Int = 1
private var numberOfProcesses: Int = 8
@@ -27,7 +26,6 @@ internal class Settings: NSStackView, Settings_v {
public var callback: (() -> Void) = {}
public var callbackWhenUpdateNumberOfProcesses: (() -> Void) = {}
public var IPGCallback: ((_ state: Bool) -> Void) = {_ in }
public var setInterval: ((_ value: Int) -> Void) = {_ in }
public var setTopInterval: ((_ value: Int) -> Void) = {_ in }
@@ -41,7 +39,6 @@ internal class Settings: NSStackView, Settings_v {
self.hyperthreadState = Store.shared.bool(key: "\(self.title)_hyperhreading", defaultValue: self.hyperthreadState)
self.usagePerCoreState = Store.shared.bool(key: "\(self.title)_usagePerCore", defaultValue: self.usagePerCoreState)
self.splitValueState = Store.shared.bool(key: "\(self.title)_splitValue", defaultValue: self.splitValueState)
self.IPGState = Store.shared.bool(key: "\(self.title)_IPG", defaultValue: self.IPGState)
self.updateIntervalValue = Store.shared.int(key: "\(self.title)_updateInterval", defaultValue: self.updateIntervalValue)
self.updateTopIntervalValue = Store.shared.int(key: "\(self.title)_updateTopInterval", defaultValue: self.updateTopIntervalValue)
self.numberOfProcesses = Store.shared.int(key: "\(self.title)_processes", defaultValue: self.numberOfProcesses)
@@ -135,17 +132,6 @@ internal class Settings: NSStackView, Settings_v {
self.addArrangedSubview(PreferencesSection(rows))
}
#if arch(x86_64)
if hasIPG {
self.addArrangedSubview(PreferencesSection([
PreferencesRow("\(localizedString("CPU frequency")) (IPG)", component: switchView(
action: #selector(self.toggleIPG),
state: self.IPGState
))
]))
}
#endif
}
@objc private func changeUpdateInterval(_ sender: NSMenuItem) {
@@ -203,12 +189,6 @@ internal class Settings: NSStackView, Settings_v {
self.callback()
}
@objc func toggleIPG(_ sender: NSControl) {
self.IPGState = controlState(sender)
Store.shared.set(key: "\(self.title)_IPG", value: self.IPGState)
self.IPGCallback(self.IPGState)
}
@objc func toggleSplitValue(_ sender: NSControl) {
self.splitValueState = controlState(sender)
Store.shared.set(key: "\(self.title)_splitValue", value: self.splitValueState)

View File

@@ -15,6 +15,7 @@
5C0A9CAA2C46838A00EE6A89 /* GPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A90E18924EAD2BB00471E9A /* GPU.framework */; };
5C0A9CAF2C46838F00EE6A89 /* RAM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A81C7562449A41400825D92 /* RAM.framework */; };
5C0A9CB42C46839500EE6A89 /* Disk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9AF9EE0224648751005D2270 /* Disk.framework */; };
5C1E45562D11D66A00525864 /* libIOReport.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C1E45552D11D66200525864 /* libIOReport.tbd */; };
5C21D80B296C7B81005BA16D /* CombinedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C21D80A296C7B81005BA16D /* CombinedView.swift */; };
5C2229A329CCB3C400F00E69 /* Clock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C22299D29CCB3C400F00E69 /* Clock.framework */; };
5C2229A429CCB3C400F00E69 /* Clock.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5C22299D29CCB3C400F00E69 /* Clock.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -495,6 +496,8 @@
5C0A9CA12C467AA300EE6A89 /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = "<group>"; };
5C0A9CA32C467F7A00EE6A89 /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = "<group>"; };
5C0E550A2B5D545A00FFF1FB /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
5C1E45552D11D66200525864 /* libIOReport.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libIOReport.tbd; path = usr/lib/libIOReport.tbd; sourceTree = SDKROOT; };
5C1E45572D11D67600525864 /* bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bridge.h; sourceTree = "<group>"; };
5C21D80A296C7B81005BA16D /* CombinedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedView.swift; sourceTree = "<group>"; };
5C22299D29CCB3C400F00E69 /* Clock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Clock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5C2229A829CCB41900F00E69 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
@@ -804,6 +807,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5C1E45562D11D66A00525864 /* libIOReport.tbd in Frameworks */,
9A2847C22666AA8700EC1F6D /* Kit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1155,6 +1159,7 @@
5C3068722C32E83200B05EFA /* widget.swift */,
9A97CECD2537331B00742D8F /* Info.plist */,
9A97CEFF2537340400742D8F /* config.plist */,
5C1E45572D11D67600525864 /* bridge.h */,
);
path = CPU;
sourceTree = "<group>";
@@ -1162,6 +1167,7 @@
9A998CD622A199920087ADE7 /* Frameworks */ = {
isa = PBXGroup;
children = (
5C1E45552D11D66200525864 /* libIOReport.tbd */,
9ADE7005265D039300D2FBA8 /* IOKit.framework */,
9A97CE2A25371B2300742D8F /* IntelPowerGadget.framework */,
9A998CD922A199970087ADE7 /* ServiceManagement.framework */,
@@ -1699,7 +1705,7 @@
New,
);
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1600;
LastUpgradeCheck = 1620;
ORGANIZATIONNAME = "Serhiy Mytrovtsiy";
TargetAttributes = {
5C22299C29CCB3C400F00E69 = {
@@ -2850,7 +2856,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
};
name = Debug;
@@ -2888,7 +2893,6 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -3259,6 +3263,7 @@
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = Modules/CPU/bridge.h;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
USE_HEADERMAP = YES;
@@ -3299,6 +3304,7 @@
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = Modules/CPU/bridge.h;
SWIFT_VERSION = 5.0;
USE_HEADERMAP = YES;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction

View File

@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>641</string>
<string>642</string>
<key>Description</key>
<string>Simple macOS system monitor in your menu bar</string>
<key>LSApplicationCategoryType</key>

View File

@@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>2.11.21</string>
<key>CFBundleVersion</key>
<string>641</string>
<string>642</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>