Skip to content

Instantly share code, notes, and snippets.

@ithinkihaveacat
Last active January 30, 2024 23:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ithinkihaveacat/7e7d0f2f620cc27ad606d2df85d81fa5 to your computer and use it in GitHub Desktop.
Save ithinkihaveacat/7e7d0f2f620cc27ad606d2df85d81fa5 to your computer and use it in GitHub Desktop.
Simple parser for `adb shell dumpsys batterystats` output
#!/usr/bin/env node
// usage:
//
// $ adb shell dumpsys batterystats | ./process.js
// 1706369159690,2024-01-27 15:25:59,86
// 1706372879649,2024-01-27 16:27:59,85
// 1706378519690,2024-01-27 18:01:59,84
// 1706381668555,2024-01-27 18:54:28,82
// 1706386019511,2024-01-27 20:06:59,81
// [ ... ]
const fs = require("fs");
const readline = require("readline");
const BATTERY_HISTORY_ENTRY_REGEX = new RegExp(
"^\\s*(?:(?:\\+(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?" +
"(?:(\\d+)s)?(?:(\\d+)ms)?)|0) \\(\\d+\\) (.+)$"
);
const TIME_MESSAGE_REGEX =
/^(?:RESET:)?TIME: (\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})$/;
const BATTERY_PERCENT_REGEX = /^\s*(\d{3})/;
function timeOffsetFromHistoryEntryMatch(result) {
// Parse the components of the relative timestamp
const [d, h, m, s, ms] = result
.slice(1, 6)
.map((v) => (v ? Number(v) || 0 : 0));
return ms + 1000 * (s + 60 * (m + 60 * (h + d * 24)));
}
function utcTimestampFromTimeMessageMatch(timeResult) {
return Date.UTC(
Number(timeResult[1]),
Number(timeResult[2]) - 1,
Number(timeResult[3]),
Number(timeResult[4]),
Number(timeResult[5]),
Number(timeResult[6]),
0
);
}
function utcTimestampToTimeString(t) {
const date = new Date(t); // Convert to milliseconds
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0"); // Month starts from 0
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
let inputSource;
// Check if a file name is passed
if (process.argv.length >= 3) {
const filePath = process.argv[2];
// Create a readable stream
inputSource = fs.createReadStream(filePath);
// Handle file not found error
inputSource.on("error", (err) => {
console.error(`Could not open file: ${err.message}`);
process.exit(1);
});
} else {
// If no file name is provided, read from stdin
inputSource = process.stdin;
}
const rl = readline.createInterface({
input: inputSource,
output: process.stdout,
terminal: false,
});
let lastExplicitUtcTimestamp;
let lastExplicitOffset;
let lastPercent;
rl.on("line", (line) => {
if (line.match(/^Per-PID Stats/)) {
process.exit(0);
}
const result = line.match(BATTERY_HISTORY_ENTRY_REGEX);
let utcTimestamp = null;
if (result) {
const offset = timeOffsetFromHistoryEntryMatch(result);
message = result[6];
// Check if the message contains an absolute timestamp
const timeResult = message.match(TIME_MESSAGE_REGEX);
if (timeResult) {
utcTimestamp = utcTimestampFromTimeMessageMatch(timeResult);
lastExplicitUtcTimestamp = utcTimestamp;
lastExplicitOffset = offset;
} else if (lastExplicitUtcTimestamp) {
utcTimestamp = lastExplicitUtcTimestamp - lastExplicitOffset + offset;
}
} else {
// continuation of the previous line, reuse the previous timestamp
message = line;
}
let percent;
const resultMessage = message.match(BATTERY_PERCENT_REGEX);
if (resultMessage) {
percent = parseInt(resultMessage[0], 10);
} else {
return;
}
if (lastPercent == percent) {
return;
} else {
lastPercent = percent;
}
if (utcTimestamp && percent) {
//console.log({ utcTimestamp, timestamp, percent, message, line });
console.log(
[utcTimestamp, utcTimestampToTimeString(utcTimestamp), percent].join(",")
);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment