Monitor M1 MacBook Air power use with InfluxDB®
How to grab Powermetrics output using NodeJS and push the data to InfluxDB for monitoring use.
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.
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
To learn more on the InfluxDB Dashboard customization, visit https://docs.influxdata.com/influxdb/v2.0/visualize-data/dashboards/create-dashboard/.
😃 Happy Coding!