mirror of https://github.com/espruino/BangleApps
powermanager - Allow recording timeouts and power changes for later analysis
parent
d9dfb3adee
commit
6653ffa762
|
@ -4,10 +4,88 @@
|
|||
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,7 +98,7 @@
|
|||
clearInterval(chargingInterval);
|
||||
chargingInterval = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on("charging",handleCharging);
|
||||
handleCharging(Bangle.isCharging());
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"warnEnabled": false,
|
||||
"warn": 96,
|
||||
"forceMonoVoltage": false,
|
||||
"forceMonoPercentage": false
|
||||
"forceMonoPercentage": false,
|
||||
"log": false
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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"},
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
writeSettings("forceMonoVoltage", v);
|
||||
}
|
||||
},
|
||||
'Log': {
|
||||
value: !!settings.log,
|
||||
onchange: v => {
|
||||
writeSettings("log", v);
|
||||
}
|
||||
},
|
||||
'Charge warning': function() {
|
||||
E.showMenu(submenu_chargewarn);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue