Monitor M1 MacBook Air power use with InfluxDB®

How to grab Powermetrics output using NodeJS and push the data to InfluxDB for monitoring use.

Lui Yong Sheng
Bootcamp

--

Dashboard in Presentation Mode

For some reasons, I want to monitor the power usage of my M1 MacBook Air. The quickest way to grab power related system information directly from the macOS Terminal is actually using the powermetrics command. However, printing out the results on screen doesn’t meet my monitoring requirements, therefore, I try to think of a better solution to store and visualise the data.

After a quick search on Google, I decided to use InfluxDB as my Time-Series Database to record the power usage signal because it provides an easy-to-use and easy-to-configure dashboard feature.

🍺 Install InfluxDB on macOS (Homebrew)

To install InfluxDB on your Mac, you can simply use the Homebrew package manager by running this command:

brew install influxdb

Once installation is completed, you can run this command to start the InfluxDB service:

brew services start influxdb

Now you will have the InfluxDB up and running.

Visit http://localhost:8086/ to use the interactive web dashboard to setup your organization, user, bucket and also authentication methods and tokens for later use.

The welcome page when you first visit localhost:8086
Setup your account, organisation and also your bucket name.
You are almost ready.
InfluxDB has a rich set of client libraries and also plugins for you to pick.
You can quickly grab the token, org and bucket keys.

See more detail documentation.

🔋 How to get the power usage information

Power consumption of your Mac devices were measured using Apple’s powermetrics utility.

Here is a sample log from powermetrics output after running the following command:

sudo powermetrics -i 1000 --samplers cpu_power,gpu_power -a --hide-cpu-duty-cycle --show-usage-summary --show-extra-power-info
*** Summary system activity (Sun Oct 10 19:52:50 2021 +0800) (1706.02ms elapsed) ******* Processor usage ****E-Cluster Power: 102 mW
E-Cluster HW active frequency: 1159 MHz
E-Cluster HW active residency: 56.84% (600 MHz: .58% 972 MHz: 74% 1332 MHz: 9.3% 1704 MHz: 5.3% 2064 MHz: 11%)
E-Cluster idle residency: 43.16%
E-Cluster instructions retired: 2.08946e+09
E-Cluster instructions per clock: 0.967239
CPU 0 frequency: 1182 MHz
CPU 0 idle residency: 68.26%
CPU 0 active residency: 31.74% (600 MHz: .01% 972 MHz: 23% 1332 MHz: 3.5% 1704 MHz: 1.3% 2064 MHz: 4.1%)
CPU 1 frequency: 1162 MHz
CPU 1 idle residency: 69.46%
CPU 1 active residency: 30.54% (600 MHz: .01% 972 MHz: 23% 1332 MHz: 3.0% 1704 MHz: 1.5% 2064 MHz: 3.3%)
CPU 2 frequency: 1167 MHz
CPU 2 idle residency: 72.65%
CPU 2 active residency: 27.35% (600 MHz: .04% 972 MHz: 20% 1332 MHz: 3.3% 1704 MHz: 1.5% 2064 MHz: 2.8%)
CPU 3 frequency: 1181 MHz
CPU 3 idle residency: 77.61%
CPU 3 active residency: 22.39% (600 MHz: .01% 972 MHz: 15% 1332 MHz: 3.7% 1704 MHz: 1.4% 2064 MHz: 2.1%)
P-Cluster Power: 541 mW
P-Cluster HW active frequency: 1079 MHz
P-Cluster HW active residency: 19.48% (600 MHz: 73% 828 MHz: 1.6% 1056 MHz: 2.6% 1284 MHz: 1.2% 1500 MHz: 1.5% 1728 MHz: 1.2% 1956 MHz: 1.8% 2184 MHz: .88% 2388 MHz: 1.8% 2592 MHz: 1.0% 2772 MHz: 2.4% 2988 MHz: 2.8% 3096 MHz: 1.7% 3144 MHz: 1.1% 3204 MHz: 5.6%)
P-Cluster idle residency: 80.52%
P-Cluster instructions retired: 2.11126e+09
P-Cluster instructions per clock: 2.5916
CPU 4 frequency: 2490 MHz
CPU 4 idle residency: 83.17%
CPU 4 active residency: 16.83% (600 MHz: .05% 828 MHz: .64% 1056 MHz: 1.4% 1284 MHz: .82% 1500 MHz: .75% 1728 MHz: .69% 1956 MHz: .95% 2184 MHz: .53% 2388 MHz: .78% 2592 MHz: .73% 2772 MHz: 1.1% 2988 MHz: .76% 3096 MHz: .04% 3144 MHz: .95% 3204 MHz: 6.7%)
CPU 5 frequency: 2498 MHz
CPU 5 idle residency: 94.75%
CPU 5 active residency: 5.25% (600 MHz: .00% 828 MHz: .22% 1056 MHz: .51% 1284 MHz: .39% 1500 MHz: .40% 1728 MHz: .00% 1956 MHz: .00% 2184 MHz: .05% 2388 MHz: .22% 2592 MHz: .20% 2772 MHz: .52% 2988 MHz: .27% 3096 MHz: .00% 3144 MHz: .18% 3204 MHz: 2.3%)
CPU 6 frequency: 2883 MHz
CPU 6 idle residency: 98.08%
CPU 6 active residency: 1.92% (600 MHz: .13% 828 MHz: .01% 1056 MHz: .00% 1284 MHz: .06% 1500 MHz: .00% 1728 MHz: .01% 1956 MHz: .00% 2184 MHz: .00% 2388 MHz: .01% 2592 MHz: .01% 2772 MHz: .11% 2988 MHz: .12% 3096 MHz: .23% 3144 MHz: .05% 3204 MHz: 1.2%)
CPU 7 frequency: 2373 MHz
CPU 7 idle residency: 99.76%
CPU 7 active residency: 0.24% (600 MHz: .00% 828 MHz: .01% 1056 MHz: .00% 1284 MHz: .08% 1500 MHz: 0% 1728 MHz: 0% 1956 MHz: 0% 2184 MHz: 0% 2388 MHz: 0% 2592 MHz: 0% 2772 MHz: .03% 2988 MHz: .02% 3096 MHz: .00% 3144 MHz: .02% 3204 MHz: .08%)
System instructions retired: 4.20072e+09
System instructions per clock: 1.41206
ANE Power: 0 mW
DRAM Power: 165 mW
CPU Power: 643 mW
GPU Power: 14 mW
Package Power: 796 mW
**** GPU usage ****GPU active frequency: 12 MHz
GPU active residency: 3.02% (396 MHz: 3.0% 528 MHz: 0% 720 MHz: 0% 924 MHz: 0% 1128 MHz: 0% 1278 MHz: 0%)
GPU requested frequency: (396 MHz: 2.8% 528 MHz: .24% 720 MHz: 0% 924 MHz: 0% 1128 MHz: 0% 1278 MHz: 0%)
GPU idle residency: 96.98%
GPU Power: 14 mW

💻 Run powermetrics using NodeJS

In this example, I will be using NodeJS to execute the powermetrics command, process and sanitise the output data and write it into the InfluxDB.

To execute shell command using NodeJS, you can use the spawn function provided by the NodeJS child_process .

const spawn = require("child_process").spawn;const power = spawn("powermetrics", [
"-i 1000 --samplers cpu_power,gpu_power -a --hide-cpu-duty-cycle --show-usage-summary --show-extra-power-info",
]);
power.stdout.on("data", (data) => {
console.log(Date.now(), " - Signal captured.");
});

To read the output data, you can use the stdout.on("data") listener. But before you process the output data as string , you need to run the toString() function to convert raw bytes to encoded string.

// ...
power.stdout.on("data", (data) => {
console.log(Date.now(), " - Signal captured.");
const strings = data.toString().split(/\n/);
const extract = strings.filter((s) => {
return s.includes("mW");
});
});

The method above split the output data string by \n newline and then extract the line that contains the keyword mW as the power consumption info are measured using mW as unit.

Once all the power related rows extracted, we can now split the key-value using thePower: keyword.

// ...
extract.forEach((data) => {
const kv = data.split(" Power: ");
const [k, v] = kv;
});

Since we already have the key-value, we can now proceed to send this data to InfluxDB. Remember to change the YOUR_TOKEN , YOUR_ORG and YOUR_BUCKET values.

const { InfluxDB, Point } = require("@influxdata/influxdb-client");// You can generate a Token from the "Tokens Tab" in the UI
const token =
"YOUR_TOKEN";
const org = "YOUR_ORG";
const bucket = "YOUR_BUCKET";
const client = new InfluxDB({ url: "http://localhost:8086", token: token });const writeApi = client.getWriteApi(org, bucket);
writeApi.useDefaultTags({ host: "host1" });
const spawn = require("child_process").spawn;const power = spawn("powermetrics", [
"-i 1000 --samplers cpu_power,gpu_power -a --hide-cpu-duty-cycle --show-usage-summary --show-extra-power-info",
]);
power.stdout.on("data", (data) => {
console.log(Date.now(), " - Signal captured.");
const strings = data.toString().split(/\n/);
const extract = strings.filter((s) => {
return s.includes("mW");
});
extract.forEach((data) => {
const kv = data.split(" Power: ");
const [k, v] = kv;
if (k && v) {
const point = new Point(k).uintField(k, parseInt(v.replace("mW")));
writeApi.writePoint(point);
console.log(k, v)
}
});
});

To run the JavaScript file, you need to use sudo as the powermetrics utility needs sudo to access.

sudo node app.js

When you see the output is something like below, it indicates that the data is successfully sent to InfluxDB.

1633869035406  - Signal captured.
E-Cluster 318 mW
P-Cluster 680 mW
ANE 0 mW
DRAM 235 mW
CPU 998 mW
GPU 43 mW
Package 1252 mW
GPU 43 mW

📈 Visualising the data

Go to Boards > + Create Dashboard > New Dashboard
Click Add Cell
Select CPU, GPU, and DRAM from your bucket and click Submit to preview the chart. If everything is ok, click the green tick button.
Voila! You got your first chart.
You can also add Gauge cell with customization.

To learn more on the InfluxDB Dashboard customization, visit https://docs.influxdata.com/influxdb/v2.0/visualize-data/dashboards/create-dashboard/.

😃 Happy Coding!

--

--