用于检索 CPU 利用率百分比的 Swift 脚本如果通过 Timer 循环运行,则工作正常。似乎在没有计时器的情况下卡在初始百分比上

Swift script to retrieve CPU utilization percentage works fine if run in a loop via Timer. Seems to get stuck on initial percentage without Timer

提问人:zia-khan 提问时间:11/15/2023 更新时间:11/15/2023 访问量:38

问:

我不是一个敏捷的开发人员。我需要这个 swift 代码来按需获取当前的 CPU 利用率百分比,即每当我查询它时,我都应该获得当前读数。我需要从 node.js 进程运行此脚本。这基本上就像从终端运行脚本一样。这是我从另一个 SO 线程中找到的脚本,它似乎可以正常工作。

import Foundation

// CPU usage credit VenoMKO: https://stackoverflow.com/a/6795612/1033581
class MyCpuUsage {
    var cpuInfo: processor_info_array_t!
    var prevCpuInfo: processor_info_array_t?
    var numCpuInfo: mach_msg_type_number_t = 0
    var numPrevCpuInfo: mach_msg_type_number_t = 0
    var numCPUs: uint = 0
    var updateTimer: Timer!
    let CPUUsageLock: NSLock = NSLock()

    init() {
        let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
        // sysctl Swift usage credit Matt Gallagher: https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlSysctl.swift
        mibKeys.withUnsafeBufferPointer() { mib in
            var sizeOfNumCPUs: size_t = MemoryLayout<uint>.size
            let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
            if status != 0 {
                numCPUs = 1
            }
            updateTimer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
        }
    }

    @objc func updateInfo(_ timer: Timer) {
        var totalInUse: Int32 = 0
        var totalTotal: Int32 = 0
        var numCPUsU: natural_t = 0
        let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
        
        if err == KERN_SUCCESS {
            CPUUsageLock.lock()

            for i in 0 ..< Int32(numCPUs) {
                var inUse: Int32
                var total: Int32
                if let prevCpuInfo = prevCpuInfo {
                    inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                    total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
                } else {
                    inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                    total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
                }

                totalInUse += inUse
                totalTotal += total
            }
            CPUUsageLock.unlock()

            if let prevCpuInfo = prevCpuInfo {
                // vm_deallocate Swift usage credit rsfinn: https://stackoverflow.com/a/48630296/1033581
                let prevCpuInfoSize: size_t = MemoryLayout<integer_t>.stride * Int(numPrevCpuInfo)
                vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
            }

            prevCpuInfo = cpuInfo
            numPrevCpuInfo = numCpuInfo

            cpuInfo = nil
            numCpuInfo = 0

            let totalUsagePercentage = Float(totalInUse) / Float(totalTotal) * 100
            print(String(format: "Total CPU Usage: %.2f%%", totalUsagePercentage))
        } else {
            print("Error!")
        }
    }
}

let cpuMonitor = MyCpuUsage()
RunLoop.current.run()

如您所见,此脚本使用计时器,并在控制台上每 3 秒无限期地打印一次当前百分比。由于我只想按需打印一次结果,因此以下是我所做的修改。只是删除了计时器,或者我想我做到了。

import Foundation

class MyCpuUsage {
    var cpuInfo: processor_info_array_t!
    var prevCpuInfo: processor_info_array_t?
    var numCpuInfo: mach_msg_type_number_t = 0
    var numPrevCpuInfo: mach_msg_type_number_t = 0
    var numCPUs: uint = 0
    let CPUUsageLock: NSLock = NSLock()

    init() {
        let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
        mibKeys.withUnsafeBufferPointer() { mib in
            var sizeOfNumCPUs: size_t = MemoryLayout<uint>.size
            let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
            if status != 0 {
                numCPUs = 1
            }

            // Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
            // calling the updateInfo() func to print one time result
            updateInfo()
        }
    }

    // Removed exposure to objective-c and the unused parameter "timer"
    // @objc func updateInfo(_ timer: Timer) {
    func updateInfo() {
        // I have added these two variables to calculate sum of all cores
        var totalInUse: Int32 = 0
        var totalTotal: Int32 = 0

        var numCPUsU: natural_t = 0
        let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
        if err == KERN_SUCCESS {
            CPUUsageLock.lock()

            for i in 0 ..< Int32(numCPUs) {
                var inUse: Int32
                var total: Int32
                if let prevCpuInfo = prevCpuInfo {
                    inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                    total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
                        - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
                } else {
                    inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                        + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                    total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
                }

                // print(String(format: "Core: %u Usage: %f", i, Float(inUse) / Float(total)))
                // Assigning totals here
                totalInUse += inUse
                totalTotal += total
            }
            CPUUsageLock.unlock()

            if let prevCpuInfo = prevCpuInfo {
                let prevCpuInfoSize: size_t = MemoryLayout<integer_t>.stride * Int(numPrevCpuInfo)
                vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
            }

            prevCpuInfo = cpuInfo
            numPrevCpuInfo = numCpuInfo

            cpuInfo = nil
            numCpuInfo = 0

            let totalUsagePercentage = Float(totalInUse) / Float(totalTotal) * 100

            // This is where I print sum of all cores percentage.
            print(String(format: "Total CPU Usage: %.2f%%", totalUsagePercentage))
        } else {
            print("Error!")
        }
    }
}

let cpuMonitor = MyCpuUsage()

我的修改版本的问题在于,它似乎在每次调用时打印的百分比与从终端进行第一次调用时打印的百分比相同。不仅控制台上的打印值保持不变,而且我还尝试运行进程以增加活动监视器确认的 CPU 使用率,但此脚本继续显示相同的值。任何帮助将不胜感激。

这是终端的屏幕截图。

correct values are displayed when running on Timer

prints 2.45% on each invocation when running the script on-demand

迅捷 的 macOS

评论

1赞 Cœur 11/15/2023
VenoMKO 对他们原始答案的评论中,他们说“获取当前时刻和上一个电话之间的时间段的滴答声”。这意味着它们的代码逻辑只有在两次调用之间计算时才有意义。由于您的简化只保留第一个调用,因此您无法计算与另一个调用的差值,因此您无法正确获得数学运算。修复思路:调用两次,中间可能等待 1 秒,然后只打印第二次。updateInfo()updateInfo()
0赞 zia-khan 11/15/2023
@C感谢您的及时回复。很有道理。由于我根本不会说 Swift,您能否发布一个快速修改作为答案?我也可以这样标记它。谢谢。

答:

1赞 3 revsCœur #1

原始代码(2011 年)中的公式依赖于对 的两次调用之间的用法差异。因此,要使您的想法在没有计时器的情况下工作,您仍然需要至少两次调用 .例如,您可以尝试在两者之间进行第二次。host_processor_infoupdateInfo()sleep(1)

代码注释。
在 Swift Playground 中测试。

import Foundation

class MyCpuUsage {
    var cpuInfo: processor_info_array_t!
    var prevCpuInfo: processor_info_array_t?
    var numCpuInfo: mach_msg_type_number_t = 0
    var numPrevCpuInfo: mach_msg_type_number_t = 0
    let numCPUs: uint
    let CPUUsageLock = NSLock()

    init() {
        // obtaining numCPUs
        let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
        var numCPUs: uint = 0
        mibKeys.withUnsafeBufferPointer() { mib in
            var sizeOfNumCPUs = MemoryLayout<uint>.size
            let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
            if status != 0 {
                numCPUs = 1
            }
        }
        self.numCPUs = numCPUs

        // calling the updateInfo() func to initialize prevCpuInfo
        updateInfo()
        // sleep 1 second
        sleep(1)
        // calling the updateInfo() func to print cpu usage during past second
        updateInfo()
    }

    func updateInfo() {
        var numCPUsU: natural_t = 0
        let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
        guard err == KERN_SUCCESS else {
            print("Error host_processor_info!")
            return
        }

        // Two variables to calculate sum of all cores
        var totalInUse: Int32 = 0
        var totalTotal: Int32 = 0

        CPUUsageLock.lock()

        for i in 0 ..< Int32(numCPUs) {
            var inUse: Int32
            var total: Int32
            if let prevCpuInfo = prevCpuInfo {
                inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                    - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                    + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                    - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                    + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                    - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
                    - prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
            } else {
                inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
                    + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
                    + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
                total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
            }

            // Assigning totals here
            totalInUse += inUse
            totalTotal += total
        }
        CPUUsageLock.unlock()

        if let prevCpuInfo = prevCpuInfo {
            // I free the memory of prevCpuInfo
            let prevCpuInfoSize: size_t = MemoryLayout<integer_t>.stride * Int(numPrevCpuInfo)
            vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
            
            // I print sum of all cores percentage.
            let totalUsagePercentage = Float(totalInUse) / Float(totalTotal) * 100
            print(String(format: "Total CPU Usage: %.2f%%", totalUsagePercentage))
        }

        prevCpuInfo = cpuInfo
        numPrevCpuInfo = numCpuInfo

        cpuInfo = nil
        numCpuInfo = 0
    }
}

let cpuMonitor = MyCpuUsage()

作为记录,在两个电话之间等待 1 秒的想法也在 2013 年由 Anya 在类似的 ObjC 回答中记录:

然后我们取两个样本(样本之间间隔 1 秒)

sample(&sample1);
sleep(1);
sample(&sample2);

评论

0赞 zia-khan 11/15/2023
非常感谢:-)这很完美。在某个地方,我还发现使用了这种方法,而不是我们在这种方法中的简单方法。我发现两个显示器几乎完全相同的结果。有什么主要考虑因素吗?差异?let hostInfo = host_cpu_load_info_t.allocate(capacity: 1) let _ = hostInfo.withMemoryRebound(to: integer_t.self, capacity: Int(size)) { (pointer) -> kern_return_t in return host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, pointer, &size) }host_processor_info
0赞 Cœur 11/15/2023
@zia-khan 两者都没有正确记录:host_statistics,host_processor_info所以我不知道。尝试阅读 VenoMKO 推荐,也许答案就在那里:github.com/gopalkrishnareddy/awesome-iOS-resource/blob/master/......
0赞 Cœur 11/15/2023
@zia-khan 你也可以尝试查看这些方法的旧源代码,这些方法在几年前是 Apple 开源的,这可能有助于你理解这两个 API 之间的区别: github.com/apple/darwin-xnu/blob/main/osfmk/kern/host.c
1赞 Cœur 11/15/2023
@zia-khan 我读到 Anya 在 2013 年写道:“但老实说,我会使用 host_statistics,因为代码更干净