9

How do I get total CPU Idle Time in Objective-C/C on macOS?

Here is a code I use to get these metrics. As result percentage is not the same I have in Activity Monitor. So I assume CPU time calculation is incorrect:

#include <sys/sysctl.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/processor_info.h>
#include <mach/mach_host.h>

- (void)printCPUUsage
{
    processor_cpu_load_info_t cpuLoad;
    mach_msg_type_number_t processorMsgCount;
    natural_t processorCount;
    
    uint64_t totalSystemTime = 0, totalUserTime = 0, totalIdleTime = 0, totalCPUTime = 0;
    
    kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &processorCount, (processor_info_array_t *)&cpuLoad, &processorMsgCount);
    
    for (natural_t i = 0; i < processorCount; i++) {
        
        // Calc load types and totals, with guards against 32-bit overflow
        // (values are natural_t)
        uint64_t system = 0, user = 0, idle = 0, total = 0;
        
        system = cpuLoad[i].cpu_ticks[CPU_STATE_SYSTEM];
        user = cpuLoad[i].cpu_ticks[CPU_STATE_USER];
        idle = cpuLoad[i].cpu_ticks[CPU_STATE_IDLE];
        
        total = system + user + idle;
        
        if (total < 1) {
            total = 1;
        }
        
        totalCPUTime += total;
        totalSystemTime += system;
        totalUserTime += user;
        totalIdleTime += idle;
    }


    double onePercent = totalCPUTime/100.0f;
    
    NSLog(@"system: %f", (double)totalSystemTime/(double)onePercent);
    NSLog(@"user: %f", (double)totalUserTime/(double)onePercent);
    NSLog(@"idle: %f", (double)totalIdleTime/(double)onePercent);
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Serge
  • 2,031
  • 3
  • 33
  • 56
  • Take a look at - http://stackoverflow.com/questions/6785069/get-cpu-percent-usage. That calculates the total usage. So, idle time = 100% - total usage % – Anish Ramaswamy Dec 09 '13 at 15:46
  • Actually I use the same approach/code but with calculation difference between current and previous metrics. – Serge Dec 09 '13 at 16:03

1 Answers1

12

The values as returned from the process meter or top are, by default, sample delta based i.e. they calculate the CPU usage since the previous sample, rather than the absolute values.

This corresponds to the option -c n to top when called in the mode:

 top -c n -l 0 | head -5

Which is the default mode. If you want the values as returned in your code, then you need to base the values on immediate samples, using:

 top -c e -l 0 | head -5

These values will correspond to the values you're seeing.

If you want to get similar values to the process meter/top, then you need to take two samples, and display the values of the differences between them.

So for example, we create a structure containing the stats:

struct cpusample {
    uint64_t totalSystemTime;
    uint64_t totalUserTime;
    uint64_t totalIdleTime;

};

we alter the printCPUUsage call so that it performs a sample:

void sample(struct cpusample *sample)
{
    processor_cpu_load_info_t cpuLoad;
    mach_msg_type_number_t processorMsgCount;
    natural_t processorCount;

    uint64_t totalSystemTime = 0, totalUserTime = 0, totalIdleTime = 0;

    kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &processorCount, (processor_info_array_t *)&cpuLoad, &processorMsgCount);

    for (natural_t i = 0; i < processorCount; i++) {

        // Calc load types and totals, with guards against 32-bit overflow
        // (values are natural_t)
        uint64_t system = 0, user = 0, idle = 0;

        system = cpuLoad[i].cpu_ticks[CPU_STATE_SYSTEM];
        user = cpuLoad[i].cpu_ticks[CPU_STATE_USER] + cpuLoad[i].cpu_ticks[CPU_STATE_NICE];
        idle = cpuLoad[i].cpu_ticks[CPU_STATE_IDLE];

        totalSystemTime += system;
        totalUserTime += user;
        totalIdleTime += idle;
    }
    sample->totalSystemTime = totalSystemTime;
    sample->totalUserTime = totalUserTime;
    sample->totalIdleTime = totalIdleTime;
}

Then we take two samples (1 second between samples):

struct cpusample delta;
sample(&sample1);
sleep(1);
sample(&sample2);
deltasample.totalSystemTime = sample2.totalSystemTime - sample1.totalSystemTime;
deltasample.totalUserTime = sample2.totalUserTime - sample1.totalUserTime;
deltasample.totalIdleTime = sample2.totalIdleTime - sample1.totalIdleTime;

The add a printsample code:

void printSample(struct cpusample *sample)
{
    uint64_t total = sample->totalSystemTime + sample->totalUserTime + sample->totalIdleTime;

    double onePercent = total/100.0f;

    NSLog(@"system: %f", (double)sample->totalSystemTime/(double)onePercent);
    NSLog(@"user: %f", (double)sample->totalUserTime/(double)onePercent);
    NSLog(@"idle: %f", (double)sample->totalIdleTime/(double)onePercent);
}

so when you invoke printSample(&deltasample) it prints the delta record, which gives a much more similar value to the one presented by either top or Activity Monitor.

But to be honest, I'd use host_statistics, as the code is cleaner:

void sample(struct cpusample *sample)
{
    kern_return_t kr;
    mach_msg_type_number_t count;
    host_cpu_load_info_data_t r_load;

    uint64_t totalSystemTime = 0, totalUserTime = 0, totalIdleTime = 0;

    count = HOST_CPU_LOAD_INFO_COUNT;
    kr = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (int *)&r_load, &count);
    if (kr != KERN_SUCCESS) {
        printf("oops: %s\n", mach_error_string(kr));
        return;
    }

    sample->totalSystemTime = r_load.cpu_ticks[CPU_STATE_SYSTEM];
    sample->totalUserTime = r_load.cpu_ticks[CPU_STATE_USER] + r_load.cpu_ticks[CPU_STATE_NICE];
    sample->totalIdleTime = r_load.cpu_ticks[CPU_STATE_IDLE];
}
Anya Shenanigans
  • 91,618
  • 3
  • 107
  • 122
  • 1
    I need to get values which shown in activity monitor(CPU). Could you please provide a code sample which do that or solution which will fix my code already provided above. Thank you in advance. – Serge Dec 09 '13 at 18:59
  • 1
    I've updated my answer with a method to more accurately represent the values as displayed in the activity monitor. – Anya Shenanigans Dec 09 '13 at 22:22
  • 1
    bear in mind there is also a `CPU_STATE_NICE`, which is counted as part of the user time for the purposes of calculations. – Anya Shenanigans Dec 09 '13 at 22:29
  • Petesh, thank you for answer. Seems that percentage is calculating properly. But I found that previous totalIdleTime time could be bigger then current. I thought it gets time starting boot OS. Correct? – Serge Dec 10 '13 at 15:09
  • 1
    I'm not privy to the implementation, but those values should be from boot. Continually incrementing seems like correct behaviour; unless, of course, we're actually seeing wraparound on the value (which seems unlikely) – Anya Shenanigans Dec 10 '13 at 19:08
  • Petesh, you are right. It calculates starting boot. One more question: is there a way to convert cpu_ticks (e.g. totalSystemTime) to nanoseconds? As I understood the correct solution is next: totSysTimeInNano = (totalSystemTime/sysconf(_SC_CLK_TCK))*1000000000; But I'm not sure if it's correct since value won't be accurated. – Serge Dec 10 '13 at 19:55
  • 1
    That math is right - bearing in mind that your totalSystemTime is accumulated across all the cores (i.e. divide it by 8 for an 8 core system); however as _SC_CLK_TCK == 100, the use of nano-secs for the value is claiming a false sense of accuracy from the value. – Anya Shenanigans Dec 10 '13 at 20:36
  • Thank you for answer. So to get totSysTimeInNano in nanoseconds I also need to devide to count of cores? totSysTimeInNano = ((totalSystemTime/sysconf(_SC_CLK_TCK))*1000000000)/coresCount; – Serge Dec 10 '13 at 21:32
  • yes, that's correct, but again, that value is claiming an accuracy of nanoseconds while only delivering decisecond granularity. – Anya Shenanigans Dec 10 '13 at 21:36
  • How does `_SC_CLK_TCK` account for modern CPUs, which heavily down-clock themselves when in low use? Is the concept for "clock tick" here different from the conventional meaning of a clock tick of any normal IC? – Alexander Dec 09 '20 at 03:49
  • @Alexander It’s a system timer clock tick, not a hardware clock rate. The usual purpose of a clock tick like 100hz is to allow scheduling - the kernel triggers the scheduler at that rate to wake/sleep threads/processes. – Anya Shenanigans Dec 09 '20 at 11:44
  • @Petesh That makes sense! So just verify my understanding, with each tick of the scheduler, the `cpuLoad[current_cpu].cpu_ticks[current_cpu]` is incremented by 1. Because we have `n` cores each ticking 100/s, when we sum all these values, we need to divide by `n` and `100` to get the number of seconds elapsed, is that correct? – Alexander Dec 09 '20 at 14:58