prosperon/scripts/profile.js

398 lines
9.2 KiB
JavaScript
Raw Normal View History

2024-09-22 23:10:36 -05:00
/*
TYPES OF PROFILING
2024-09-25 20:51:20 -05:00
2024-09-23 18:17:46 -05:00
cpu gathering - gets stack frames randomly for a few seconds
frames - user defined to see how long engine takes
cache - can see specific events that happened
memory - can see how much memory is allocated and from where
2024-09-22 23:10:36 -05:00
*/
2024-09-26 11:36:09 -05:00
var t_units = ["ns", "us", "ms", "s", "ks", "Ms"];
2024-09-25 20:51:20 -05:00
2024-09-26 11:36:09 -05:00
function calc_cpu(fn, times, diff = 0) {
2024-09-25 20:51:20 -05:00
var series = [];
for (var i = 0; i < times; i++) {
var st = profile.now();
2024-09-26 11:36:09 -05:00
fn(i);
2024-09-25 20:51:20 -05:00
series.push(profile.now() - st - diff);
}
2024-09-25 20:51:20 -05:00
return series;
}
2024-09-26 11:36:09 -05:00
function empty_fn() {}
profile.cpu = function profile_cpu(fn, times = 1, q = "unnamed") {
2024-08-05 15:26:18 -05:00
var retgather = gathering_cpu;
profile.gather_stop();
var empty = calc_cpu(empty_fn, 100000);
var mean = Math.mean(empty);
2024-09-26 11:36:09 -05:00
var series = calc_cpu(fn, times, mean);
var elapsed = Math.sum(series);
2024-09-26 11:36:09 -05:00
var avgt = profile.best_t(elapsed / series.length);
2024-07-24 14:17:32 -05:00
var totalt = profile.best_t(elapsed);
2024-09-26 11:36:09 -05:00
say(`profile [${q}]: ${avgt} ± ${profile.best_t(Math.ci(series))} [${totalt} for ${times} loops]`);
2024-08-05 15:26:18 -05:00
say(`result of function is ${fn()}`);
2024-07-24 14:17:32 -05:00
2024-09-26 11:36:09 -05:00
if (retgather) profile.start_prof_gather();
};
profile.ms = function (t) {
return profile.secs(t) * 1000;
};
2024-07-24 14:17:32 -05:00
var callgraph = {};
2024-09-23 18:17:46 -05:00
profile.rawstacks = {};
2024-07-24 14:17:32 -05:00
2024-09-26 19:28:54 -05:00
function add_callgraph(fn, line, time, alone) {
2024-07-24 14:17:32 -05:00
var cc = callgraph[line];
if (!cc) {
var cc = {};
callgraph[line] = cc;
cc.time = 0;
cc.hits = 0;
cc.fn = fn;
cc.line = line;
2024-09-26 19:28:54 -05:00
cc.alone = {
time: 0,
hits: 0
}
2024-07-24 14:17:32 -05:00
}
cc.time += time;
cc.hits++;
2024-09-26 19:28:54 -05:00
if (alone) {
cc.alone.time += time;
cc.alone.hits++;
}
2024-07-24 14:17:32 -05:00
}
2024-09-22 10:45:31 -05:00
var hittar = 500; // number of call instructions before getting a new frame
var hitpct = 0.2; // amount to randomize it
2024-07-24 14:17:32 -05:00
var start_gather = profile.now();
2024-09-23 18:17:46 -05:00
profile.cpu_start = undefined;
2024-09-26 11:36:09 -05:00
profile.clear_cpu = function () {
2024-09-23 18:17:46 -05:00
callgraph = {};
2024-09-26 19:28:54 -05:00
profile.cpu_instr = undefined;
2024-09-26 11:36:09 -05:00
};
2024-08-04 15:20:11 -05:00
2024-09-26 11:36:09 -05:00
profile.start_cpu_gather = function (gathertime = 5) {
2024-09-26 19:28:54 -05:00
profile.clear_cpu();
2024-09-26 11:36:09 -05:00
// gather cpu frames for 'time' seconds
2024-09-23 18:17:46 -05:00
if (profile.cpu_start) return;
profile.cpu_start = profile.now();
var st = profile.cpu_start;
2024-09-26 11:36:09 -05:00
profile.gather(hittar, function () {
var time = profile.now() - st;
var err = new Error();
2024-09-26 11:36:09 -05:00
var stack = err.stack.split("\n").slice(1);
var rawstack = stack.join("\n");
2024-09-23 18:17:46 -05:00
profile.rawstacks[rawstack] ??= {
time: 0,
2024-09-26 11:36:09 -05:00
hits: 0,
2024-09-23 18:17:46 -05:00
};
profile.rawstacks[rawstack].hits++;
profile.rawstacks[rawstack].time += time;
2024-09-26 11:36:09 -05:00
stack = stack.map(x => x.slice(7).split(" "));
var fns = stack.map(x => x[0]).filter(x => x);
var lines = stack.map(x => x[1]).filter(x => x);
2024-09-26 11:36:09 -05:00
lines = lines.map(x => x.slice(1, x.length - 1));
2024-09-26 19:28:54 -05:00
add_callgraph(fns[0], lines[0], time, true);
for (var i = 1; i < fns.length; i++) add_callgraph(fns[i], lines[i], time, false);
2024-09-26 11:36:09 -05:00
st = profile.now();
2024-09-26 11:36:09 -05:00
if (profile.secs(st - profile.cpu_start) < gathertime) profile.gather_rate(Math.variate(hittar, hitpct));
2024-09-22 10:45:31 -05:00
else {
profile.gather_stop();
2024-09-23 18:17:46 -05:00
profile.cpu_start = undefined;
2024-09-22 10:45:31 -05:00
var e = Object.values(callgraph);
2024-09-26 19:28:54 -05:00
e = e.filter( x=> x.line);
2024-09-26 11:36:09 -05:00
for (var x of e) {
var ffs = x.line.split(":");
2024-09-26 19:28:54 -05:00
2024-09-26 11:36:09 -05:00
x.timestr = profile.best_t(x.time);
x.timeper = x.time / x.hits;
x.timeperstr = profile.best_t(x.timeper);
2024-09-26 19:28:54 -05:00
x.pct = (profile.secs(x.time) / gathertime) * 100;
x.alone.timestr = profile.best_t(x.alone.time);
x.alone.timeper = x.alone.time/x.alone.hits;
x.alone.timeperstr = profile.best_t(x.alone.timeper);
x.alone.pct = (profile.secs(x.alone.time)/gathertime*100);
2024-09-26 11:36:09 -05:00
x.fncall = get_line(ffs[0], ffs[1]);
2024-09-26 19:28:54 -05:00
x.log = x.line + " " + x.fn + " " + x.fncall;
x.incl = {
time: x.time,
timestr: x.timestr,
timeper: x.timeper,
timeperstr: x.timeperstr,
hits: x.hits,
pct: x.pct,
};
2024-09-26 11:36:09 -05:00
}
profile.cpu_instr = e;
2024-09-22 10:45:31 -05:00
}
});
2024-09-26 11:36:09 -05:00
};
2024-09-26 11:36:09 -05:00
function push_time(arr, ob, max) {
2024-09-04 13:23:20 -05:00
arr.push({
2024-09-26 11:36:09 -05:00
time: profile.now(),
ob,
2024-09-04 13:23:20 -05:00
});
}
profile.cpu_frames = [];
2024-09-23 18:17:46 -05:00
profile.last_cpu_frame = undefined;
2024-09-26 11:36:09 -05:00
profile.cpu_frame = function () {
profile.gather(Math.random_range(300, 600), function () {
2024-09-23 18:17:46 -05:00
var err = new Error();
2024-09-26 11:36:09 -05:00
profile.last_cpu_frame = err.stack; //.split('\n').slicconsole.stack(2);
2024-08-04 15:20:11 -05:00
profile.gather_stop();
});
2024-09-26 11:36:09 -05:00
};
2024-07-24 14:17:32 -05:00
var filecache = {};
2024-09-26 11:36:09 -05:00
function get_line(file, line) {
2024-07-24 14:17:32 -05:00
var text = filecache[file];
if (!text) {
var f = io.slurp(file);
if (!f) {
filecache[file] = "undefined";
return filecache[file];
}
2024-09-26 11:36:09 -05:00
filecache[file] = io.slurp(file).split("\n");
2024-07-24 14:17:32 -05:00
text = filecache[file];
}
2024-09-25 20:51:20 -05:00
2024-09-26 11:36:09 -05:00
if (typeof text === "string") return text;
text = text[Number(line) - 1];
2024-07-24 14:17:32 -05:00
if (!text) return "NULL";
return text.trim();
}
2024-09-26 11:36:09 -05:00
profile.stop_cpu_instr = function () {
return;
};
2024-07-24 14:17:32 -05:00
profile.best_t = function (t) {
var qq = 0;
2024-09-25 20:51:20 -05:00
while (t > 1000 && qq < t_units.length - 1) {
2024-07-24 14:17:32 -05:00
t /= 1000;
qq++;
}
2024-07-25 22:07:22 -05:00
2024-07-24 14:17:32 -05:00
return `${t.toPrecision(4)} ${t_units[qq]}`;
};
2024-09-22 10:45:31 -05:00
/*
2024-09-25 20:51:20 -05:00
Frame averages are an instrumented profiling technique. Place frame() calls
in your code to get a call graph for things you are interested in.
2024-09-22 10:45:31 -05:00
*/
2024-08-04 15:20:11 -05:00
var frame_avg = false;
2024-09-26 11:36:09 -05:00
profile.start_frame_avg = function () {
2024-08-04 15:20:11 -05:00
if (frame_avg) return;
profile_frames = {};
profile_frame_ts = [];
profile_cframe = profile_frames;
pframe = 0;
frame_avg = true;
2024-09-26 11:36:09 -05:00
};
2024-08-04 15:20:11 -05:00
2024-09-26 11:36:09 -05:00
profile.stop_frame_avg = function () {
2024-08-04 15:20:11 -05:00
frame_avg = false;
2024-09-26 11:36:09 -05:00
};
2024-08-04 15:20:11 -05:00
2024-09-26 11:36:09 -05:00
profile.toggle_frame_avg = function () {
2024-08-04 15:20:11 -05:00
if (frame_avg) profile.stop_frame_avg();
else profile.start_frame_avg();
2024-09-26 11:36:09 -05:00
};
2024-08-04 15:20:11 -05:00
2024-09-04 14:34:45 -05:00
var profile_framer = {
series: [],
avg: {},
frame: 72000,
};
var profile_cframe = undefined;
var pframe = 0;
2024-09-04 14:34:45 -05:00
var profile_stack = [];
2024-09-26 11:36:09 -05:00
profile.frame = function profile_frame(title) {
2024-09-26 19:28:54 -05:00
return;
2024-09-23 18:17:46 -05:00
if (profile.cpu_start) return;
2024-08-04 15:20:11 -05:00
if (!frame_avg) return;
2024-09-26 11:36:09 -05:00
2024-09-04 14:34:45 -05:00
if (!profile_cframe) {
profile_cframe = {};
profile_framer.series.push({
2024-09-26 11:36:09 -05:00
time: profile.now(),
data: profile_cframe,
2024-09-04 14:34:45 -05:00
});
2024-09-26 11:36:09 -05:00
} else profile_stack.push(profile_cframe);
2024-07-24 14:17:32 -05:00
profile_cframe[title] ??= {};
profile_cframe = profile_cframe[title];
2024-09-04 14:34:45 -05:00
profile_cframe.time = profile.now();
2024-09-26 11:36:09 -05:00
};
2024-07-24 14:17:32 -05:00
2024-09-26 11:36:09 -05:00
profile.endframe = function profile_endframe() {
2024-09-26 19:28:54 -05:00
return;
2024-08-04 15:20:11 -05:00
if (!frame_avg) return;
2024-09-04 14:34:45 -05:00
profile_cframe.time = profile.now() - profile_cframe.time;
2024-07-24 14:17:32 -05:00
profile_cframe = profile_frame_ts.pop();
2024-09-26 11:36:09 -05:00
};
2024-07-24 14:17:32 -05:00
2024-09-22 10:45:31 -05:00
/*
Cache reporting is to measure how long specific events take, that are NOT every frame
Useful to measure things like how long it takes to make a specific creature
*/
2024-08-15 12:26:37 -05:00
var cache_reporting = false;
var report_cache = {};
var cachest = 0;
var cachegroup;
var cachetitle;
2024-08-15 12:26:37 -05:00
2024-09-26 11:36:09 -05:00
profile.cache_reporting = function () {
return cache_reporting;
};
profile.cache_toggle = function () {
cache_reporting = !cache_reporting;
};
profile.cache_dump = function () {
2024-08-15 12:26:37 -05:00
report_cache = {};
2024-09-26 11:36:09 -05:00
};
2024-08-15 12:26:37 -05:00
2024-09-26 11:36:09 -05:00
profile.cache = function profile_cache(group, title) {
2024-08-15 12:26:37 -05:00
if (!cache_reporting) return;
cachest = profile.now();
cachegroup = group;
cachetitle = title;
2024-09-26 11:36:09 -05:00
};
2024-09-26 11:36:09 -05:00
profile.endcache = function profile_endcache(tag = "") {
addreport(cachegroup, cachetitle + tag, cachest);
2024-09-26 11:36:09 -05:00
};
2024-07-24 14:17:32 -05:00
function addreport(group, line, start) {
2024-09-26 11:36:09 -05:00
if (typeof group !== "string") group = "UNGROUPED";
report_cache[group] ??= {};
var cache = report_cache[group];
2024-07-24 14:17:32 -05:00
cache[line] ??= [];
var t = profile.now();
cache[line].push(t - start);
return t;
2024-09-26 11:36:09 -05:00
}
2024-07-24 14:17:32 -05:00
2024-09-26 11:36:09 -05:00
function printreport(cache, name) {
var report = `==${name}==` + "\n";
2024-07-24 14:17:32 -05:00
var reports = [];
for (var i in cache) {
2024-09-25 20:51:20 -05:00
var time = cache[i].reduce((a, b) => a + b);
2024-07-24 14:17:32 -05:00
reports.push({
2024-09-26 11:36:09 -05:00
time: time,
name: i,
hits: cache[i].length,
avg: time / cache[i].length,
2024-07-24 14:17:32 -05:00
});
}
2024-09-25 20:51:20 -05:00
reports = reports.sort((a, b) => {
if (a.avg < b.avg) return 1;
2024-07-24 14:17:32 -05:00
return -1;
});
2024-09-25 20:51:20 -05:00
2024-09-26 11:36:09 -05:00
for (var rep of reports) report += `${rep.name} ${profile.best_t(rep.avg)} (${rep.hits} hits) (total ${profile.best_t(rep.time)})\n`;
2024-07-24 14:17:32 -05:00
return report;
2024-09-26 11:36:09 -05:00
}
2024-08-05 15:26:18 -05:00
2024-09-24 14:12:59 -05:00
profile.data = {};
2024-09-25 12:46:59 -05:00
profile.curframe = 0;
2024-09-26 19:28:54 -05:00
profile.snapshot = {};
var fps = [];
profile.report_frame = function(t)
{
fps.push(t);
if (fps.length > 15) {
profile.snapshot.fps = Math.mean(fps);
fps = [];
}
}
2024-09-26 11:36:09 -05:00
function prof_add_stats(obj, stat) {
2024-09-25 12:46:59 -05:00
for (var i in stat) {
obj[i] ??= [];
2024-09-25 20:51:20 -05:00
if (obj[i].last() !== stat[i]) obj[i][profile.curframe] = stat[i];
2024-09-25 12:46:59 -05:00
}
}
2024-09-24 14:12:59 -05:00
2024-09-26 11:36:09 -05:00
profile.pushdata = function (arr, val) {
2024-09-26 19:28:54 -05:00
2024-09-26 11:36:09 -05:00
if (arr.last() !== val) arr[profile.curframe] = val;
};
2024-09-24 17:07:32 -05:00
2024-09-26 19:28:54 -05:00
profile.capturing = false;
2024-09-26 11:36:09 -05:00
profile.capture_data = function () {
2024-09-26 19:28:54 -05:00
if (!profile.capturing && profile.data.memory.malloc_size) return;
2024-09-25 12:46:59 -05:00
prof_add_stats(profile.data.memory, os.mem());
prof_add_stats(profile.data.gfx, imgui.framestats());
prof_add_stats(profile.data.actors, actor.__stats());
profile.curframe++;
2024-09-26 11:36:09 -05:00
};
2024-09-24 14:12:59 -05:00
2024-09-26 11:36:09 -05:00
profile.best_mem = function (bytes) {
var sizes = ["Bytes", "KB", "MB", "GB", "TB"];
if (bytes == 0) return "0 Bytes";
2024-08-05 15:26:18 -05:00
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
2024-09-26 11:36:09 -05:00
return (bytes / Math.pow(1024, i)).toPrecision(3) + " " + sizes[i];
};
2024-08-05 15:26:18 -05:00
2024-09-26 11:36:09 -05:00
profile.cleardata = function () {
2024-09-25 15:27:26 -05:00
profile.data.gpu = {};
profile.data.physics = {};
profile.data.script = {};
profile.data.memory = {};
profile.data.gfx = {};
profile.data.actors = {};
profile.data.cpu = {
scripts: [],
render: [],
physics: [],
};
2024-09-26 11:36:09 -05:00
};
2024-09-25 15:27:26 -05:00
profile.cleardata();
2024-09-04 13:23:20 -05:00
profile.last_mem = undefined;
profile.mems = [];
profile.gcs = [];
2024-09-26 11:36:09 -05:00
profile.print_gc = function () {
2024-08-06 14:23:21 -05:00
var gc = os.check_gc();
if (!gc) return;
2024-09-24 17:07:32 -05:00
profile.data.gc ??= [];
2024-09-25 12:46:59 -05:00
profile.data.gc[profile.curframe] = gc;
2024-09-26 11:36:09 -05:00
};
2024-08-06 14:23:21 -05:00
2024-09-26 11:36:09 -05:00
return { profile };