powermanager - Allow recording timeouts and power changes for later analysis

pull/2560/head
Martin Boonk 2023-02-05 10:32:33 +01:00
parent d9dfb3adee
commit 6653ffa762
5 changed files with 364 additions and 5 deletions

View File

@ -3,11 +3,89 @@
require('Storage').readJSON("powermanager.default.json", true) || {},
require('Storage').readJSON("powermanager.json", true) || {}
);
if (settings.log) {
let logFile = require('Storage').open("powermanager.log","a");
let def = require('Storage').readJSON("powermanager.def.json", true) || {};
if (!def.start) def.start = Date.now();
if (!def.deferred) def.deferred = {};
let sen = require('Storage').readJSON("powermanager.sen.json", true) || {};
if (!sen.start) sen.start = Date.now();
if (!sen.power) sen.power = {};
E.on("kill", ()=>{
let defExists = require("Storage").read("powermanager.def.json")!==undefined;
if (!(!defExists && def.saved)){
def.saved = Date.now();
require('Storage').writeJSON("powermanager.def.json", def);
}
let senExists = require("Storage").read("powermanager.sen.json")!==undefined;
if (!(!senExists && sen.saved)){
sen.saved = Date.now();
require('Storage').writeJSON("powermanager.sen.json", sen);
}
});
let logPower = (type, oldstate, state, app) => {
logFile.write("p," + type + ',' + (oldstate?1:0) + ',' + (state?1:0) + ',' + app + "\n");
};
let logDeferred = (type, duration, source) => {
logFile.write(type + ',' + duration + ',' + source + "\n");
};
let lastPowerOn = {};
const TO_WRAP = ["GPS","Compass","Barometer","HRM","LCD"];
for (let c of TO_WRAP){
let functionName = "set" + c + "Power";
let checkName = "is" + c + "On";
let type = c + "";
if (!sen.power[type]) sen.power[type] = 0;
lastPowerOn[type] = Date.now();
Bangle[functionName] = ((o) => (a,b) => {
let oldstate = Bangle[checkName]();
let result = o(a,b);
if (!(oldstate && result)) {
if (result) {
//switched on, store time
lastPowerOn[type] = Date.now();
} else {
//switched off
sen.power[type] += Date.now() - lastPowerOn[type];
}
}
if (settings.logDetails) logPower(type, oldstate, result, b);
return result;
})(Bangle[functionName]);
}
let functions = {};
let wrapDeferred = ((o,t) => (a,b,c,d) => {
let wrapped = (q,w,e,r)=>{
let start = Date.now();
let result = a(q,w,e,r);
let end = Date.now()-start;
let f = a.toString().substring(0,100);
if (settings.logDetails) logDeferred(t, end, f);
if (!def.deferred[f]) def.deferred[f] = 0;
def.deferred[f] += end;
return result;
};
return o(wrapped,b,c,d);
});
global.setTimeout = wrapDeferred(global.setTimeout, "t");
global.setInterval = wrapDeferred(global.setInterval, "i");
}
if (settings.warnEnabled){
var chargingInterval;
function handleCharging(charging){
let handleCharging = (charging) => {
if (charging){
if (chargingInterval) clearInterval(chargingInterval);
chargingInterval = setInterval(()=>{
@ -20,12 +98,12 @@
clearInterval(chargingInterval);
chargingInterval = undefined;
}
}
};
Bangle.on("charging",handleCharging);
handleCharging(Bangle.isCharging());
}
if (settings.forceMonoPercentage){
var p = (E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery())/4;
var op = E.getBattery;

View File

@ -2,5 +2,6 @@
"warnEnabled": false,
"warn": 96,
"forceMonoVoltage": false,
"forceMonoPercentage": false
"forceMonoPercentage": false,
"log": false
}

View File

@ -0,0 +1,273 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="content"></div>
<script src="../../core/lib/interface.js"></script>
<script>
var domContent = document.getElementById("content");
function download(filename, callback) {
Util.showModal("Downloading power info...");
Util.readStorage(filename, data => {
Util.hideModal();
callback(data);
});
}
function show() {
Util.showModal("Loading...");
domContent.innerHTML = "";
var htmlOverview = `<table class="table table-striped table-hover">
<div>This needs "Log" to be enabled in power manager settings. The deferred function calls table is only updated on the bangle on reloads.</div>
<thead>
<tr>
<th>Type</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Deferred function calls</td>
<td>
<button class="btn btn-primary" filename="powermanager.def.json" task="deftable">Table</button>
<button class="btn btn-error" filename="powermanager.def.json" task="clear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
<tr>
<td>Sensors</td>
<td>
<button class="btn btn-primary" filename="powermanager.sen.json" task="sensorstable">Table</button>
<button class="btn btn-error" filename="powermanager.sen.json" task="clear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
<tr>
<td>Details</td>
<td>
<button class="btn btn-primary" filename="powermanager.log" task="detailstable">Table</button>
<button class="btn btn-error" filename="powermanager.log" task="detailsclear" style="float: right;margin-right: 5px;">Clear</button>
</td>
</tr>
</tbody>
</table>`;
domContent.innerHTML = htmlOverview;
Util.hideModal();
var buttons = domContent.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var filename = button.getAttribute("filename");
if (!filename) return;
var task = button.getAttribute("task");
if (task=="detailsclear") {
Util.showModal("Clearing...");
Util.eraseStorageFile(filename,()=>{
Util.hideModal();
show();
});
}
if (task=="clear") {
Util.showModal("Clearing...");
Util.eraseStorage(filename,()=>{
Util.hideModal();
show();
});
}
if (task=="deftable") {
viewDeferredTable(filename);
}
if (task=="sensorstable") {
viewSensorsTable(filename);
}
if (task=="detailstable") {
viewDetailsTable(filename);
}
});
}
}
function viewDeferredTable(filename) {
Puck.eval(`require("Storage").list("powermanager.def.json").length > 0`, (f)=>{
if (f) {
Util.showModal("Reading summarized info...");
Util.readStorage(
filename, data => {
Util.hideModal();
let parsed = JSON.parse(data);
let sum = 0;
let rows = [];
for (var i in parsed.deferred) {
sum += parsed.deferred[i];
rows.push({func: i, time: parsed.deferred[i]});
}
rows.sort((a,b)=>{return b.time/sum - a.time/sum;});
let tableRows = "";
for (var i in rows) {
let c = rows[i];
tableRows += `<tr>
<td>${(c.time/1000).toFixed(2)}s</td>
<td>${(c.time/sum*100).toFixed(2)}%</td>
<td>${c.func}</td>`
}
let duration = parsed.saved - parsed.start;
var htmlOverview = `<h1>Deferred function calls</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
<div>
This are functions used in timeouts and intervals and their accumulated execution times. Recorded in a time span of <b>${Math.round((duration)/1000)}s</b>. Timeouts/intervals have run for <b>${Math.round(sum/1000)}s (${(sum/duration*100).toFixed(2)}%)</b>. Percentages are calculated from summarized timeout/interval running time.
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Time</th>
<th>Percentage</th>
<th>Function</th>
</tr>
</thead>
<tbody>\n`;
htmlOverview += tableRows;
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
//try finding possible sources for the given function, currently does not work because function.toString() not being identical to code in the *.js files.
/*Puck.eval(`require("Storage").list(/.*.js$/)`, (f)=>{
console.log("Found files:", f, rows[1].func);
for (let file of f){
let query = `require("Storage").read('${file}').includes('${rows[1].func}')`;
console.log("Query: " + query);
Puck.eval(query, (r)=>{
if (r) domContent.querySelector("#row_1").innerHTML = file;
console.log("Found", file, r)
});
}
});*/
});
} else {
var htmlOverview = `<h1>Deferred function calls</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<div>
No data available.
</div>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
}
});
}
function viewSensorsTable(filename) {
Puck.eval(`require("Storage").list("powermanager.sen.json").length > 0`, (f)=>{
if (f) {
Util.showModal("Reading sensor info...");
Util.readStorage(
filename, data => {
Util.hideModal();
let parsed = JSON.parse(data);
console.log("Sensors", parsed);
let duration = parsed.saved - parsed.start;
let rows = [];
for (var i in parsed.power) {
rows.push({func: i, time: parsed.power[i]});
}
rows.sort((a,b)=>{return b.time/duration - a.time/duration;});
let tableRows = "";
for (var i in rows) {
let c = rows[i];
tableRows += `<tr>
<td>${(c.time/1000).toFixed(2)}s</td>
<td>${(c.time/duration*100).toFixed(2)}%</td>
<td>${c.func}</td>`
}
var htmlOverview = `<h1>Sensor power</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;margin-left: 10px;">Back</button>
<div>
Recorded in a time span of <b>${Math.round(duration/1000)}s</b>. Percentages are calculated from recording time.
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Time</th>
<th>Percentage</th>
<th>Sensor</th>
</tr>
</thead>
<tbody>\n`;
htmlOverview += tableRows;
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
});
} else {
var htmlOverview = `<h1>Sensor power</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<div>
No data available.
</div>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
}
});
}
function viewDetailsTable(filename) {
Util.showModal("Reading details info...");
Util.readStorageFile(
filename, data => {
Util.hideModal();
var htmlOverview = `<h1>Detailed logging</h1>
<button class="btn btn-primary" id="back" style="float: right;margin-right: 5px;">Back</button>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Type</th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>\n`;
let rows = data.trim().split("\n");
for (var row of rows) {
let cols = row.split(",");
htmlOverview += `<tr>
<td>${cols[0]}</td>
<td>${cols[1]}</td>
<td>${cols[2]}</td>
<td>${cols[3]}</td>
<td>${cols[4]}</td>
</tr>`
}
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#back").addEventListener("click",event => {
show();
});
});
}
function onInit() {
show();
}
</script>
</body>
</html>

View File

@ -9,6 +9,7 @@
"tags": "tool",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"storage": [
{"name":"powermanager.boot.js","url":"boot.js"},
{"name":"powermanager.settings.js","url":"settings.js"},

View File

@ -36,6 +36,12 @@
writeSettings("forceMonoVoltage", v);
}
},
'Log': {
value: !!settings.log,
onchange: v => {
writeSettings("log", v);
}
},
'Charge warning': function() {
E.showMenu(submenu_chargewarn);
},