A C++ program to get CPU usage from command line in Linux

This article explains how to create a simple program in C++ that shows the current CPU usage from command line in Linux. That’s achieved processing the file /proc/stat which contains several kernel/system statistics.

top, a popular program to get CPU usage from command line in Linux

Recently I have been looking for a simple program to get the CPU usage from command line in Linux. The most used options are htop, iostat, mpstat, sar and top. All of them come with many features and options, but none of them does what I needed, so I decided to write one myself in C++ (more about it later).

In this post I am going to describe how to create a very simple version of this program to get CPU usage from command line in Linux.

CPU statistics in Linux

Linux systems feature a proc pseudo-filesystem to provide an interface to kernel data structures. It is commonly mounted at /proc and it is populated with files covering different aspects of the kernel.

The file which holds information about the status of the CPUs of a machine is /proc/stat and its first lines are something like this:

$ cat /proc/stat 
cpu  31393 169 7639 353168 4164 0 57 0 0 0 
cpu0 8040 39 1875 157930 1578 0 18 0 0 0 
cpu1 7972 30 1915 64967 961 0 11 0 0 0 
cpu2 8167 48 2224 64593 1127 0 24 0 0 0 
cpu3 7213 50 1623 65676 496 0 2 0 0 0
...

Those numbers represent the amount of time that the system spent in different states since boot. Times are expressed in units of USER_HZ, but this is not too important for now. The first row represents the total amount of time spent by all the CPUs combined.

The different CPU states are:

  1. user – time spent in user mode.
  2. nice – time spent in user mode with low priority.
  3. system – time spent in system mode.
  4. idle – time spent in the idle task.
  5. iowait –  time waiting for I/O to complete.
  6. irq – time servicing hardware interrupts.
  7. softirq – time servicing software interrupts.
  8. steal – time spent in other operating systems when running in a virtualized environment.
  9. guest – time spent running a virtual CPU for guest operating systems.
  10. guest_nice – time spent running a low priority virtual CPU for guest operating systems.

Combining states 4 and 5 gives the time spent by the CPU doing nothing, whereas all the other times combined give the time of activity.

Program main

A single snapshot of the status of a system is not very useful on its own, but 2 sequential snapshots can tell us the CPU usage in the period between them. This is what the main does:

int main(int argc, char * argv[])
{
	std::vector<CPUData> entries1;
	std::vector<CPUData> entries2;

	// snapshot 1
	ReadStatsCPU(entries1);

	// 100ms pause
	std::this_thread::sleep_for(std::chrono::milliseconds(100));

	// snapshot 2
	ReadStatsCPU(entries2);

	// print output
	PrintStats(entries1, entries2);
	
	return 0;
}

The 2 vectors entries1 and entries2 store CPUData objects which are structures defined like:

const int NUM_CPU_STATES = 10;

typedef struct CPUData
{
	std::string cpu;
	size_t times[NUM_CPU_STATES];
} CPUData;

They are used to store the data of each cpu line from the /proc/stat file. Basically a CPUData structure represents a snapshot of a CPU.

Read CPU stats

The following function parses /proc/stat to collect times of the different states for each CPU. Every line of the file is parsed and the relevant data stored in a CPUData object. All these objects are kept in a std::vector passed as parameter to the function:

void ReadStatsCPU(std::vector<CPUData> & entries)
{
	std::ifstream fileStat("/proc/stat");

	std::string line;

	const std::string STR_CPU("cpu");
	const std::size_t LEN_STR_CPU = STR_CPU.size();
	const std::string STR_TOT("tot");

	while(std::getline(fileStat, line))
	{
		// cpu stats line found
		if(!line.compare(0, LEN_STR_CPU, STR_CPU))
		{
			std::istringstream ss(line);

			// store entry
			entries.emplace_back(CPUData());
			CPUData & entry = entries.back();

			// read cpu label
			ss >> entry.cpu;

			if(entry.cpu.size() > LEN_STR_CPU)
				entry.cpu.erase(0, LEN_STR_CPU);
			else
				entry.cpu = STR_TOT;

			// read times
			for(int i = 0; i < NUM_CPU_STATES; ++i)
				ss >> entry.times[i];
		}
	}
}

This function opens /proc/stat in line 3 using a std::ifstream. Then it reads all its lines using std::getline in line 11 and stores them, one after one, in a std::string for further processing. Before going further, line 14 checks if the string starts with “cpu” which identifies a line of stats of a CPU. The condition is checked for “false” as std::string::compare returns 0 on a match.

When a CPU stats line is found, it’s processed generating a std::istringstream from the std::string for easier tokenization (extracting all its fields) as showed in line 16.

The initial “cpu” label is read in line 23 and transformed in lines 25-28 to keep only the id of the CPU or to show “tot” for the total CPU times.

Finally times of the 10 different states are read in lines 31-32 using a for cycle.

Computing active and idle time

Once all times are stores in a CPUData object it’s pretty straightforward to compute active and idle times.

The total idle time is the sum of the fields idle and iowait:

size_t GetIdleTime(const CPUData & e)
{
    return  e.times[S_IDLE] +
            e.times[S_IOWAIT];
}

Whereas the active time is the sum of all the other fields:

size_t GetActiveTime(const CPUData & e)
{
    return  e.times[S_USER] +
            e.times[S_NICE] +
            e.times[S_SYSTEM] +
            e.times[S_IRQ] +
            e.times[S_SOFTIRQ] +
            e.times[S_STEAL] +
            e.times[S_GUEST] +
            e.times[S_GUEST_NICE];
}

Obviously the difference between the idle or active times of 2 snapshots gives the time elapsed in the 2 states. As showed in the next paragraph.

Printing the CPU usage

The final step is printing the CPU usage and idle percentages:

void PrintStats(const std::vector<CPUData> & entries1, const std::vector<CPUData> & entries2)
{
	const size_t NUM_ENTRIES = entries1.size();
	
	for(size_t i = 0; i < NUM_ENTRIES; ++i)
	{
		const CPUData & e1 = entries1[i];
		const CPUData & e2 = entries2[i];

		std::cout.width(3);		
		std::cout << e1.cpu << "] ";

		const float ACTIVE_TIME	= static_cast<float>(GetActiveTime(e2) - GetActiveTime(e1));
		const float IDLE_TIME	= static_cast<float>(GetIdleTime(e2) - GetIdleTime(e1));
		const float TOTAL_TIME	= ACTIVE_TIME + IDLE_TIME;

		std::cout << "active: ";
		std::cout.setf(std::ios::fixed, std::ios::floatfield);
		std::cout.width(6);
		std::cout.precision(2);
		std::cout << (100.f * ACTIVE_TIME / TOTAL_TIME) << "%";

		std::cout << " - idle: ";
		std::cout.setf(std::ios::fixed, std::ios::floatfield);
		std::cout.width(6);
		std::cout.precision(2);
		std::cout << (100.f * IDLE_TIME / TOTAL_TIME) << "%" << std::endl;

		std::cout << std::endl;
	}
}

The percentage of time spent in each state is given by the simple formula:

100 * STATE_TIME / TOTAL_TIME

as showed in lines 21 and 27.

Program output

Running the program will produce an output similar to this:

$ ./cpusage
tot] active:  17.95% - idle:  82.05%
  0] active:  10.00% - idle:  90.00%
  1] active:  11.11% - idle:  88.89%
  2] active:  27.27% - idle:  72.73%
  3] active:  11.11% - idle:  88.89%

Source code

The full source code of this tutorial is available on GitHub and released under the Unlicense license.

Improving things: cpu-stat

As mentioned in the introduction, recently I’ve started working on a more advanced C++ program to get the CPU usage from command line in Linux. I called this program cpu-stat and its core concept is that it should provide a plain, simple answers when launched. That’s because this way it’s easier to use it in scripts or as input for other programs. For example, running the program without parameters returns the active percentage time of the total CPU:

$ ./cpu-stat
17.95

Obviously I added some options to get more verbose output and multiple results as well, but those are more for a standalone usage.

Its source code will be available on GitHub soon, but in the meanwhile you can suggest/request features using the GitHub issues page.

Conclusion

I hope you enjoyed this tutorial explaining how to create a C++ program to get CPU usage from command line in Linux. If you have any question feel free to leave a comment.

If you found it useful, please share it on social media using the social buttons below.

Subscribe

Don’t forget to subscribe to the blog newsletter to get notified of future posts (once a week).

You can also get updates following me on Google+, LinkedIn and Twitter.

4 Comments

  1. markg85

    Just a thought.

    You are now doing string matching and parsing to get the cpu statistics, but there also seems to be c kernel functions for exactly that. It would save you the hassle of parsing /proc/stat and it will be quite a bit more efficient to use the c functions.

    Have a look in the stat implementation: https://github.com/torvalds/linux/blob/master/fs/proc/stat.c

    The downside is that your code will be bigger if you go that route and it will probably have to be recompiled if your kernel gets updated..

    Reply
    1. Davide Coppola (Post author)

      I haven’t looked into this much, but to the best of my knowledge programs in user space can’t call kernel functions.

      The only way to communicate with the kernel is using syscalls.

      Reply
      1. markg85

        I realized that right after posting my message 😉
        You would have to make a kernel module, expose a syscall and use that in user space.

        That would be quite a complicated piece of code for something seemingly simple.

        This does make me think.. Why don’t the /proc/* provide an output that is easy to parse. Like json or so. The output as is right now seems to be focused on human readability.

        Reply
        1. Davide Coppola (Post author)

          I am not a kernel developer, but I believe the philosophy behind it is providing information in a compact and “straight to the point” way. It’s low-level stuff after all.

          Parsing space-separated numbers is not that hard after all, as you can see in my (simple) example code. 🙂

          Reply

Leave a Comment

Your email address will not be published. Required fields are marked *