Search code examples
swiftmacoscpu

Swift - How to get CPU load properly


I'm trying to get the current CPU load as a percentage, but when I try with the code below, the data I get is absurd

print(stats.cpu_ticks)

not much changes

time cpu_tick[0] cpu_tick[1] cpu_tick[2] cpu_tick[3]
1s 1810101 782080 22191087 0
2s 1810214 782122 22191725 0
3s 1810322 782158 22192376 0
print("\tSYSTEM:          \(Int(cpuUsage.system))%")
print("\tUSER:            \(Int(cpuUsage.user))%")
print("\tIDLE:            \(Int(cpuUsage.idle))%")
print("\tNICE:            \(Int(cpuUsage.nice))%")

and here nothing change

time system user idle nice
1s 3% 7% 89% 0%
2s 3% 7% 89% 0%
3s 3% 7% 89% 0%

Load info struct

  fileprivate static func hostCPULoadInfo() -> host_cpu_load_info {
    var info = host_cpu_load_info_data_t()
    var count = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info>.stride / MemoryLayout<integer_t>.stride)
    
    let kerr: kern_return_t = withUnsafeMutablePointer(to: &info, {
      host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0.withMemoryRebound(to: integer_t.self, capacity: 1, { $0 }), &count)
    })
    
    guard kerr == KERN_SUCCESS else {
      return host_cpu_load_info()
    }
    
    return info
  }
  private mutating func cpuUsage() -> (system: Double, user: Double, idle: Double, nice: Double) {
    let stats = LoadInfo.hostCPULoadInfo()
    
    print(stats.cpu_ticks)
    
    let userDiff = stats.cpu_ticks.0 - loadPrevious.cpu_ticks.0
    let systemDiff = stats.cpu_ticks.1 - loadPrevious.cpu_ticks.1
    let idleDiff = stats.cpu_ticks.2 - loadPrevious.cpu_ticks.2
    let niceDiff = stats.cpu_ticks.3 - loadPrevious.cpu_ticks.3
    
    let totalTicks = UInt64(systemDiff + userDiff + idleDiff + niceDiff)
    
    let onePercent = Double(totalTicks / 100)
    
    let system = Double(systemDiff) / onePercent
    let user = Double(userDiff) / onePercent
    let idle = Double(idleDiff) / onePercent
    let nice = Double(niceDiff) / onePercent
    
    loadPrevious = stats
    
    return (system, user, idle, nice)
  }
  public func getCPUUsage() {
    var test = LoadInfo()
    let cpuUsage = test.cpuUsage()
    print("\tSYSTEM:          \(Int(cpuUsage.system))%")
    print("\tUSER:            \(Int(cpuUsage.user))%")
    print("\tIDLE:            \(Int(cpuUsage.idle))%")
    print("\tNICE:            \(Int(cpuUsage.nice))%")
  }

This is how this function is called (every second)

  private func updateStatistics() {
    loadInfo.getCPUUsage()
  }

Solution

  • So the provided code works well with data of similar accuracy to Activity Monitor

      fileprivate static func hostCPULoadInfo() -> host_cpu_load_info {
        var info = host_cpu_load_info_data_t()
        var count = mach_msg_type_number_t(MemoryLayout<host_cpu_load_info>.stride / MemoryLayout<integer_t>.stride)
        
        let kerr: kern_return_t = withUnsafeMutablePointer(to: &info, {
          host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, $0.withMemoryRebound(to: integer_t.self, capacity: 1, { $0 }), &count)
        })
        
        guard kerr == KERN_SUCCESS else {
          return host_cpu_load_info()
        }
        
        return info
      }
    
      var loadPrevious: host_cpu_load_info = hostCPULoadInfo()
    
      private mutating func cpuUsage() -> (system: Double, user: Double, idle: Double, nice: Double) {
        let stats = LoadInfo.hostCPULoadInfo()
        
        print(stats.cpu_ticks)
        
        let userDiff = stats.cpu_ticks.0 - loadPrevious.cpu_ticks.0
        let systemDiff = stats.cpu_ticks.1 - loadPrevious.cpu_ticks.1
        let idleDiff = stats.cpu_ticks.2 - loadPrevious.cpu_ticks.2
        let niceDiff = stats.cpu_ticks.3 - loadPrevious.cpu_ticks.3
        
        let totalTicks = UInt64(systemDiff + userDiff + idleDiff + niceDiff)
        
        let onePercent = Double(totalTicks / 100)
        
        let system = Double(systemDiff) / onePercent
        let user = Double(userDiff) / onePercent
        let idle = Double(idleDiff) / onePercent
        let nice = Double(niceDiff) / onePercent
        
        loadPrevious = stats
        
        return (system, user, idle, nice)
      }
    

    The problem was caused by creating a new LoadInfo instance in the getCPUUsage function every time it was called, so none of the LoadPreviouse data was properly used. All we need to do is simply get rid of the getCPUUsage function and for example in our view structure or wherever we need CPU usage data, create a LoadInfo instance and simply call the cpuUsage function e.g. like this

      @State private var loadInfo = LoadInfo()
    
      private func getCPUUsage() {
        let cpuUsage = loadInfo.cpuUsage()
        print("\tSYSTEM:          \(Int(cpuUsage.system))%")
        print("\tUSER:            \(Int(cpuUsage.user))%")
        print("\tIDLE:            \(Int(cpuUsage.idle))%")
        print("\tNICE:            \(Int(cpuUsage.nice))%")
      }