最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - How can I retrieve the CPU usage of my Mac in the form of a percentage and put that into an updating variable? - Stack O

programmeradmin3浏览0评论

I'm coding a Mac app in SwiftUI 6.0.3 and Xcode 16. My Mac is up to date with macOS Sequoia 15.3.1. I'm trying to have a menu bar item that updates at an interval with the percentage of the CPU that I am using. This code returns no errors, and as far as I can tell, should work, but I must be missing something. When I run the app, instead of giving me a percentage, it just says "Calculating..." which is the default value of the cpuUsage variable.

import SwiftUI
import Foundation

@main
struct MenuBarApp: App {
    @State private var cpuUsage: String = "Calculating..."
    
    var body: some Scene {
        // Menu bar item
        MenuBarExtra("icon \(cpuUsage)") {
            // Option to quit app
            Button("Quit") {
                NSApp.terminate(nil)
            }
        }
    }
    
    // Starts repeating CPU monitoring function at an interval of 1 second
    func startCPUMonitoring() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let usage = getCPUUsage() {
                cpuUsage = String(format: "%.1f%%", usage)
            } else {
                cpuUsage = "N/A"
            }
        }
    }

    // Retrieves CPU usage as a percentage of the total
    func getCPUUsage() -> Double? {
        var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
        var cpuLoad = host_cpu_load_info()
        let result = withUnsafeMutablePointer(to: &cpuLoad) {
            $0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
                host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
            }
        }
        
        guard result == KERN_SUCCESS else {
            print("Error retrieving CPU load: \(result)")
            return nil
        }
        
        let user = Double(cpuLoad.cpu_ticks.0)
        let system = Double(cpuLoad.cpu_ticks.1)
        let idle = Double(cpuLoad.cpu_ticks.2)
        let nice = Double(cpuLoad.cpu_ticks.3)
        
        let totalTicks = user + system + idle + nice
        let cpuUsage = (user + system + nice) / totalTicks * 100.0
        
        return cpuUsage
    }
}

I've asked ChatGPT and went through the Apple Developer documentation but the problem is so niche that I can't find a single relatively recent source that discusses anything remotely similar. I think it's probably an issue with what function is being called where or the order of events or something, but I can't figure it out. Please help. I'm just trying to make this work on the latest version of macOS, it doesn't matter if it works for older versions.

I'm coding a Mac app in SwiftUI 6.0.3 and Xcode 16. My Mac is up to date with macOS Sequoia 15.3.1. I'm trying to have a menu bar item that updates at an interval with the percentage of the CPU that I am using. This code returns no errors, and as far as I can tell, should work, but I must be missing something. When I run the app, instead of giving me a percentage, it just says "Calculating..." which is the default value of the cpuUsage variable.

import SwiftUI
import Foundation

@main
struct MenuBarApp: App {
    @State private var cpuUsage: String = "Calculating..."
    
    var body: some Scene {
        // Menu bar item
        MenuBarExtra("icon \(cpuUsage)") {
            // Option to quit app
            Button("Quit") {
                NSApp.terminate(nil)
            }
        }
    }
    
    // Starts repeating CPU monitoring function at an interval of 1 second
    func startCPUMonitoring() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let usage = getCPUUsage() {
                cpuUsage = String(format: "%.1f%%", usage)
            } else {
                cpuUsage = "N/A"
            }
        }
    }

    // Retrieves CPU usage as a percentage of the total
    func getCPUUsage() -> Double? {
        var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
        var cpuLoad = host_cpu_load_info()
        let result = withUnsafeMutablePointer(to: &cpuLoad) {
            $0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
                host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
            }
        }
        
        guard result == KERN_SUCCESS else {
            print("Error retrieving CPU load: \(result)")
            return nil
        }
        
        let user = Double(cpuLoad.cpu_ticks.0)
        let system = Double(cpuLoad.cpu_ticks.1)
        let idle = Double(cpuLoad.cpu_ticks.2)
        let nice = Double(cpuLoad.cpu_ticks.3)
        
        let totalTicks = user + system + idle + nice
        let cpuUsage = (user + system + nice) / totalTicks * 100.0
        
        return cpuUsage
    }
}

I've asked ChatGPT and went through the Apple Developer documentation but the problem is so niche that I can't find a single relatively recent source that discusses anything remotely similar. I think it's probably an issue with what function is being called where or the order of events or something, but I can't figure it out. Please help. I'm just trying to make this work on the latest version of macOS, it doesn't matter if it works for older versions.

Share Improve this question asked Feb 17 at 5:41 thefearsomeshrubthefearsomeshrub 31 silver badge2 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

Assuming your calculations are correct, try this approach using a Button("Start") to start monitoring, and a more precise String(format: "%.6f%%", usage) to show any differences in the value, as shown in this example code.

Note you need to click on the "Start" button to display the results.

@main
struct MenuBarApp: App {
    @State private var cpuUsage: String = "Calculating..."
    
    var body: some Scene {
        // Menu bar item
        MenuBarExtra("icon \(cpuUsage)") {
            Button("Start") {
                startCPUMonitoring()  // <--- here to start
            }
            Button("Quit") {
                NSApp.terminate(nil)
            }
        }
    }
    
    // Starts repeating CPU monitoring function at an interval of 1 second
    func startCPUMonitoring() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let usage = getCPUUsage() {
                cpuUsage = String(format: "%.6f%%", usage)  // <--- here
            } else {
                cpuUsage = "N/A"
            }
        }
    }

    // Retrieves CPU usage as a percentage of the total
    func getCPUUsage() -> Double? {
        var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
        var cpuLoad = host_cpu_load_info()
        let result = withUnsafeMutablePointer(to: &cpuLoad) {
            $0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
                host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
            }
        }
        
        guard result == KERN_SUCCESS else {
            print("Error retrieving CPU load: \(result)")
            return nil
        }
        
        let user = Double(cpuLoad.cpu_ticks.0)
        let system = Double(cpuLoad.cpu_ticks.1)
        let idle = Double(cpuLoad.cpu_ticks.2)
        let nice = Double(cpuLoad.cpu_ticks.3)
        
        let totalTicks = user + system + idle + nice
        let cpuUsage = (user + system + nice) / totalTicks * 100.0
        
        return cpuUsage
    }
}

EDIT-1:

If you want to start the CPU monitoring immediately without having to click "Start", then try this approach using a @Observable class CPUMonitor. This will observe any changes in the cpuUsage and update the View/MenuBar.

@Observable class CPUMonitor {
    var cpuUsage: String = "Calculating..."
    
    init() {
        startCPUMonitoring()  // <--- here
    }
    
    // Starts repeating CPU monitoring function at an interval of 1 second
    func startCPUMonitoring() {
        Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            if let usage = self.getCPUUsage() {
                self.cpuUsage = String(format: "%.6f%%", usage)  // <--- here
            } else {
                self.cpuUsage = "N/A"
            }
        }
    }

    // Retrieves CPU usage as a percentage of the total
    func getCPUUsage() -> Double? {
        var size = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info_data_t>.size / MemoryLayout<integer_t>.size)
        var cpuLoad = host_cpu_load_info()
        let result = withUnsafeMutablePointer(to: &cpuLoad) {
            $0.withMemoryRebound(to: integer_t.self, capacity: Int(size)) {
                host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0, &size)
            }
        }
        
        guard result == KERN_SUCCESS else {
            print("Error retrieving CPU load: \(result)")
            return nil
        }
        
        let user = Double(cpuLoad.cpu_ticks.0)
        let system = Double(cpuLoad.cpu_ticks.1)
        let idle = Double(cpuLoad.cpu_ticks.2)
        let nice = Double(cpuLoad.cpu_ticks.3)
        
        let totalTicks = user + system + idle + nice
        let cpuUsage = (user + system + nice) / totalTicks * 100.0
        
        return cpuUsage
    }
}

@main
struct MenuBarApp: App {
    let cpuMonitor = CPUMonitor() // <--- here
    
    var body: some Scene {
        MenuBarExtra("icon \(cpuMonitor.cpuUsage)") {  // <--- here
            Button("Quit") {
                NSApp.terminate(nil)
            }
        }
    }
}

Works well for me, tested on macOS 15.3.1, using Xcode 16.2.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论