Node.js IoT – Build a Cross Platform CPU Sensor

cross platform cpu sensor

I took a little hiatus in our series to take my family on a trip to Japan with layovers on each end of the trip in China which included a ride on the Shanghai Maglev Train, the fastest train in the world.  We had a fantastic time, and it was a great educational experience for the kids.  It is also good to be back home!

We are back again with our Node.js IoT tutorial series and ready to continue developing our “CPU sensor” as CPU loading/utilization is a “sensor” we can measure, record, and ultimately stream to other locations.  Today, we will expand our CPU sensor and make it cross platform—and learn more about Node.js in the process. In future tutorials, we will harness the power of Node.js to interact with physical sensors that live outside of our computing environment. If you have not read my previous post on Building a CPU Sensor, you might want to go ahead and do so now as it provides instructions for preparing your development environment and getting the first iteration of the CPU sensor created.

CPU sensor loop – create using an IIFE

In the last tutorial, we ultimately created a CPU sensor that emits values once per second as shown here:

'use strict';
const os = require('os');
const delaySeconds = 1;
const decimals = 3;

function loop() {
  console.log('CPU load is ' + cpuLoad());
  setTimeout(loop, delaySeconds * 1000);
}

function cpuLoad() {
  let cpuLoad = os.loadavg()[0] * 100;
  return cpuLoad.toFixed(decimals);
}

loop();

I want to introduce a variation on creating a loop for producing CPU sensor values using a JavaScript design pattern called an IIFE.  An IIFE (pronounced “iffy”) is an immediately-invoked function expression and is used to contain the scope of variables within JavaScript.  It can also be used as an alternative syntax to get our loop up and running when our Node.js program is invoked.

Here is a basic IIFE example:

(function() {
  // the code in here is immediately invoked and run.
})();

Let’s modify our CPU sensor loop to use an IIFE.  Why?  Because we can.  🙂

'use strict';
const os = require('os');
const delaySeconds = 1;
const decimals = 3;

(function loop() {
  console.log('CPU load is ' + cpuLoad());
  setTimeout(loop, delaySeconds * 1000);
})();

function cpuLoad() {
  let cpuLoad = os.loadavg()[0] * 100;
  return cpuLoad.toFixed(decimals);
}

As you see, we have eliminated the loop() statement and are using an IIFE to invoke the code.  This is a simple refactoring and does not change the functionality of the program in any other way.  I prefer the previous non-IIFE syntax so we will not continue to use the IIFE syntax; however, I wanted to present it here to show you how it works.

Make the CPU sensor work on Windows

Our CPU sensor works well on the Raspberry Pi and other *nix system such as Linux and OS X.  Unfortunately, the os.loadavg function always returns a value of zero for Windows-based systems since (according to the Node.js documentation) load average is a UNIX-y concept with no real equivalent on Windows platforms. Let’s see if we can work around this and accommodate our Windows friends as well.

We’ll use the windows-cpu npm module to help us out.  This module retrieves CPU load numbers that we can use for our CPU sensor by utilizing the Windows Management Instrumentation Command-line tool (wmic.exe) behind the scenes to gather CPU statistics.

Our first step is to create a package.json file for our Node project so we can register the windows-cpu module as a dependency. First, navigate to the root of your node project directory:

cd /d p:\node\cpu-sensor

Next, create a default package.json file using the npm init command:

npm init

Accept all of the defaults or enter something different if you desire:

P:\node\cpu-sensor>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (cpu-sensor)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)

Alternatively, if you are planning to accept all of the defaults, you can use the -y option with npm init as described in the npm documentation to create a package.json file.

npm init -y

Next, install the windows-cpu npm package and save the package as a dependency in your package.json file.

npm install windows-cpu --save

We could omit the steps of creating a package.json file and simply run an npm install in the root of our project directory; however, we are seeking to instill good developer practices so someone else could take our project down the road and do an npm install to retrieve all of the npm package dependencies associated with our project and utilize our code.

Next, let’s modify our existing code and add a function to retrieve the Windows CPU load:

function windowsCpuLoad(cb) {
  cpu.totalLoad((error, results) => {
    if (error) {
      return cb(error);
    }
    cb(null, results);
  });
}

In this function, we utilize Node.js callbacks.  You can learn more about callbacks here.  Please don’t get frustrated since it will take some time for you to wrap your brain around callbacks and how they work.  For now, simply understand that we are returning a callback function (cb) utilizing the Node convention that the first callback argument is null if there are no errors and the second callback argument is the result.

We can then invoke our function as follows:

windowsCpuLoad((error, result) => {
  if (error) {
    console.log('Windows CPU load - error retrieving');
  } else {
    console.log('Windows CPU load is ' + result);
  }
});

This uses the ES6 arrow syntax to handle the callback function.  The second argument of the callback function contains the result variable containing the CPU load from the windows-cpu module.

Let’s put the code all together so you can see it in context:

// npm init, npm i windows-cpu, first attempt, showing both for reference
'use strict';
const os = require('os');
const cpu = require('windows-cpu');

const delaySeconds = 1;
const decimals = 3;

function loop() {
  console.log('CPU load is ' + cpuLoad(decimals));
  windowsCpuLoad((error, result) => {
    if (error) {
      console.log('Windows CPU load - error retrieving');
    } else {
      console.log('Windows CPU load is ' + result);
    }
  });

  setTimeout(loop, delaySeconds * 1000);
}

loop();

function cpuLoad() {
  let cpuLoad = os.loadavg()[0] * 100;
  return cpuLoad.toFixed(decimals);
}

function windowsCpuLoad(cb) {
  cpu.totalLoad((error, results) => {
    if (error) {
      return cb(error);
    }
    cb(null, results);
  });
}

In our loop, we are now invoking both the original CPU load function and invoking the windowsCpuLoad function we just created.

Let’s try out our new creation:

P:\node\cpu-sensor> node index.js

You should now see results streaming in for your CPU sensor! You will see values of zero for the “CPU load” and values greater than zero for the “Windows CPU load”.

Create cross platform CPU load function

As a final step in this tutorial, we will create a CPU load function that is cross platform and adapts depending on the underlying platform. Here is the new function:

function cpuLoad(cb) {
  if (process.platform === 'win32') {
    cpu.totalLoad((error, cpus) => {
      if (error) {
        return cb(error);
      }
      // Average the CPU loads since may be multiple cores on the machine.
      let sum = cpus.reduce((a, b) => {
        return a + b;
      });
      let avg = sum / cpus.length;
      cb(null, avg);
    });
  } else {
    let linuxCpuLoad = os.loadavg()[0] * 100;
    linuxCpuLoad = linuxCpuLoad.toFixed(decimals);
    cb(null, linuxCpuLoad);
  }
}

We utilize the process.platform property to discern whether we are running on a Windows platform. If so, we utilize the code we created previously with a callback function.  If not, we utilize the standard os.loadavg function with a callback.  I also add the capability here to average the CPU loads if multiple cores exist on the machine.

Let’s synthesize this together into our final cross platform CPU sensor that runs every second:

// average the CPU cores in case have more than one. Will handle array of 1 fine too.
'use strict';
const os = require('os');
const cpu = require('windows-cpu');

const delaySeconds = 1;
const decimals = 3;

function loop() {
  cpuLoad((error, result) => {
    if (error) {
      console.log('CPU load - error retrieving');
    } else {
      console.log('CPU load is ' + result);
    }
  });

  setTimeout(loop, delaySeconds * 1000);
}

loop();

function cpuLoad(cb) {
  if (process.platform === 'win32') {
    cpu.totalLoad((error, cpus) => {
      if (error) {
        return cb(error);
      }
      // Average the CPU loads since may be multiple cores on the machine.
      let sum = cpus.reduce((a, b) => {
        return a + b;
      });
      let avg = sum / cpus.length;
      cb(null, avg);
    });
  } else {
    let linuxCpuLoad = os.loadavg()[0] * 100;
    linuxCpuLoad = linuxCpuLoad.toFixed(decimals);
    cb(null, linuxCpuLoad);
  }
}

Go ahead and take this code for a spin on your Windows machine and on your Raspberry Pi.  You can stress test you CPU on Windows using something like Prime95 and on the Raspberry Pi using sysbench as described in the previous tutorial.  You should see higher loads emitting from your CPU sensor as the CPU is stressed.

Stay tuned for our next tutorial where we refactor the CPU sensor into a separate module so we can learn more about Node fundamentals for success in future projects!

Follow @thisDaveJ on Twitter to stay up to date on the latest tutorials and tech articles.

Related Articles

Beginner’s Guide to Installing Node.js on a Raspberry Pi
Using Visual Studio Code with a Raspberry Pi (Raspbian)
Visual Studio Code Jumpstart for Node.js Developers
Node.js Learning through Making – Build a CPU Sensor

2 thoughts on “Node.js IoT – Build a Cross Platform CPU Sensor

Leave a Reply

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