2024-09-22 23:10:36 -05:00
|
|
|
/*
|
|
|
|
TYPES OF PROFILING
|
|
|
|
|
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-07-25 22:07:22 -05:00
|
|
|
var t_units = ["ns", "us", "ms", "s", "ks", "Ms"];
|
2024-07-25 17:53:53 -05:00
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
function calc_cpu(fn, times, diff=0)
|
|
|
|
{
|
|
|
|
var series = [];
|
|
|
|
|
|
|
|
for (var i = 0; i < times; i++) {
|
|
|
|
var st = profile.now();
|
|
|
|
fn(i);
|
|
|
|
series.push(profile.now()-st-diff);
|
|
|
|
}
|
|
|
|
|
|
|
|
return series;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2024-08-02 20:52:50 -05:00
|
|
|
profile.gather_stop();
|
|
|
|
var empty = calc_cpu(empty_fn, 100000);
|
|
|
|
var mean = Math.mean(empty);
|
|
|
|
var series = calc_cpu(fn,times, mean);
|
2024-07-24 14:17:32 -05:00
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
var elapsed = Math.sum(series);
|
|
|
|
var avgt = profile.best_t(elapsed/series.length);
|
2024-07-24 14:17:32 -05:00
|
|
|
var totalt = profile.best_t(elapsed);
|
2024-08-02 20:52:50 -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()}`);
|
|
|
|
|
|
|
|
if (retgather)
|
|
|
|
profile.start_prof_gather();
|
2024-07-24 14:17:32 -05:00
|
|
|
}
|
|
|
|
|
2024-09-22 10:45:31 -05:00
|
|
|
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-09-22 10:45:31 -05:00
|
|
|
profile.cpu_cg = callgraph;
|
2024-07-24 14:17:32 -05:00
|
|
|
|
|
|
|
function add_callgraph(fn, line, time) {
|
|
|
|
var cc = callgraph[line];
|
|
|
|
if (!cc) {
|
|
|
|
var cc = {};
|
|
|
|
callgraph[line] = cc;
|
|
|
|
cc.time = 0;
|
|
|
|
cc.hits = 0;
|
|
|
|
cc.fn = fn;
|
|
|
|
cc.line = line;
|
|
|
|
}
|
|
|
|
cc.time += time;
|
|
|
|
cc.hits++;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
profile.clear_cpu = function()
|
|
|
|
{
|
|
|
|
callgraph = {};
|
|
|
|
}
|
2024-08-04 15:20:11 -05:00
|
|
|
|
2024-09-22 10:45:31 -05:00
|
|
|
profile.start_cpu_gather = function(gathertime = 5) // gather cpu frames for 'time' seconds
|
2024-08-02 20:52:50 -05:00
|
|
|
{
|
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-22 10:45:31 -05:00
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
profile.gather(hittar, function() {
|
|
|
|
var time = profile.now()-st;
|
|
|
|
|
|
|
|
var err = new Error();
|
2024-09-23 18:17:46 -05:00
|
|
|
var stack = err.stack.split("\n").slice(1);
|
|
|
|
var rawstack = stack.join('\n');
|
|
|
|
profile.rawstacks[rawstack] ??= {
|
|
|
|
time: 0,
|
|
|
|
hits: 0
|
|
|
|
};
|
|
|
|
profile.rawstacks[rawstack].hits++;
|
|
|
|
profile.rawstacks[rawstack].time += time;
|
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
stack = stack.map(x => x.slice(7).split(' '));
|
2024-07-24 14:17:32 -05:00
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
var fns = stack.map(x => x[0]).filter(x=>x);
|
|
|
|
var lines = stack.map(x => x[1]).filter(x => x);
|
|
|
|
lines = lines.map(x => x.slice(1,x.length-1));
|
|
|
|
|
|
|
|
for (var i = 0; i < fns.length; i++)
|
|
|
|
add_callgraph(fns[i], lines[i], time);
|
|
|
|
|
|
|
|
st = profile.now();
|
2024-09-23 18:17:46 -05:00
|
|
|
if (profile.secs(st-profile.cpu_start) < gathertime)
|
2024-09-22 10:45:31 -05:00
|
|
|
profile.gather_rate(Math.variate(hittar,hitpct));
|
|
|
|
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);
|
|
|
|
e = e.sort((a,b) => {
|
|
|
|
if (a.time > b.time) return -1;
|
|
|
|
return 1;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (var x of e) {
|
|
|
|
var ffs = x.line.split(':');
|
2024-09-23 18:17:46 -05:00
|
|
|
x.timestr = profile.best_t(x.time);
|
2024-09-22 10:45:31 -05:00
|
|
|
var pct = profile.secs(x.time)/gathertime*100;
|
2024-09-23 18:17:46 -05:00
|
|
|
x.timeper = x.time / x.hits;
|
|
|
|
x.timeperstr = profile.best_t(x.timeper);
|
|
|
|
x.fncall = get_line(ffs[0], ffs[1]);
|
|
|
|
x.log =`${x.line}::${x.fn}:: ${x.timestr} (${pct.toPrecision(3)}%) (${x.hits} hits) --> ${get_line(ffs[0], ffs[1])}`;
|
2024-09-22 10:45:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
profile.cpu_instr = e;
|
|
|
|
}
|
2024-08-02 20:52:50 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-09-04 13:23:20 -05:00
|
|
|
function push_time(arr, ob, max)
|
|
|
|
{
|
|
|
|
arr.push({
|
|
|
|
time:profile.now(),
|
|
|
|
ob
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
profile.cpu_frames = [];
|
2024-09-23 18:17:46 -05:00
|
|
|
profile.last_cpu_frame = undefined;
|
2024-08-04 15:20:11 -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();
|
|
|
|
profile.last_cpu_frame = err.stack;//.split('\n').slicconsole.stack(2);
|
2024-08-04 15:20:11 -05:00
|
|
|
profile.gather_stop();
|
|
|
|
});
|
|
|
|
}
|
2024-07-24 14:17:32 -05:00
|
|
|
|
|
|
|
var filecache = {};
|
|
|
|
function get_line(file, line) {
|
|
|
|
var text = filecache[file];
|
|
|
|
if (!text) {
|
|
|
|
var f = io.slurp(file);
|
|
|
|
if (!f) {
|
|
|
|
filecache[file] = "undefined";
|
|
|
|
return filecache[file];
|
|
|
|
}
|
|
|
|
filecache[file] = io.slurp(file).split('\n');
|
|
|
|
text = filecache[file];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof text === 'string') return text;
|
|
|
|
text = text[Number(line)-1];
|
|
|
|
if (!text) return "NULL";
|
|
|
|
return text.trim();
|
|
|
|
}
|
|
|
|
|
2024-08-04 15:20:11 -05:00
|
|
|
profile.stop_cpu_instr = function()
|
2024-07-24 14:17:32 -05:00
|
|
|
{
|
2024-09-24 14:12:59 -05:00
|
|
|
return;
|
2024-07-24 14:17:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
profile.best_t = function (t) {
|
|
|
|
var qq = 0;
|
2024-08-02 20:52:50 -05:00
|
|
|
|
2024-07-24 14:17:32 -05:00
|
|
|
while (t > 1000 && qq < t_units.length-1) {
|
|
|
|
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]}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
profile.report = function (start, msg = "[undefined report]") { console.info(`${msg} in ${profile.best_t(profile.now() - start)}`); };
|
|
|
|
|
2024-09-22 10:45:31 -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-08-04 15:20:11 -05:00
|
|
|
var frame_avg = false;
|
2024-09-04 14:34:45 -05:00
|
|
|
profile.frame_avg_t = 72000;
|
2024-08-04 15:20:11 -05:00
|
|
|
|
|
|
|
profile.start_frame_avg = function()
|
|
|
|
{
|
|
|
|
if (frame_avg) return;
|
|
|
|
profile_frames = {};
|
|
|
|
profile_frame_ts = [];
|
|
|
|
profile_cframe = profile_frames;
|
|
|
|
pframe = 0;
|
|
|
|
frame_avg = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
profile.stop_frame_avg = function()
|
|
|
|
{
|
|
|
|
frame_avg = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
profile.toggle_frame_avg = function()
|
|
|
|
{
|
|
|
|
if (frame_avg) profile.stop_frame_avg();
|
|
|
|
else profile.start_frame_avg();
|
|
|
|
}
|
|
|
|
|
2024-09-04 14:34:45 -05:00
|
|
|
var profile_framer = {
|
|
|
|
series: [],
|
|
|
|
avg: {},
|
|
|
|
frame: 72000,
|
|
|
|
};
|
|
|
|
var profile_cframe = undefined;
|
2024-08-02 20:52:50 -05:00
|
|
|
var pframe = 0;
|
2024-09-04 14:34:45 -05:00
|
|
|
var profile_stack = [];
|
2024-08-02 20:52:50 -05:00
|
|
|
|
|
|
|
profile.frame = function profile_frame(title)
|
2024-07-24 14:17:32 -05:00
|
|
|
{
|
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-04 14:34:45 -05:00
|
|
|
if (!profile_cframe) {
|
|
|
|
profile_cframe = {};
|
|
|
|
profile_framer.series.push({
|
|
|
|
time:profile.now(),
|
|
|
|
data:profile_cframe
|
|
|
|
});
|
|
|
|
} 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-07-24 14:17:32 -05:00
|
|
|
}
|
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
profile.endframe = function profile_endframe()
|
2024-07-24 14:17:32 -05:00
|
|
|
{
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
var print_frame = function(frame, indent, title)
|
|
|
|
{
|
2024-08-02 20:52:50 -05:00
|
|
|
say(indent + `${title} ::::: ${profile.best_t(Math.mean(frame._times))} ± ${profile.best_t(Math.ci(frame._times))} (${frame._times.length} hits)`);
|
2024-07-24 14:17:32 -05:00
|
|
|
|
|
|
|
for (var i in frame) {
|
|
|
|
if (i === '_times') continue;
|
|
|
|
print_frame(frame[i], indent + " ", i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
profile.print_frame_avg = function()
|
|
|
|
{
|
2024-08-04 15:20:11 -05:00
|
|
|
say("===FRAME AVERAGES===\n");
|
|
|
|
|
2024-07-24 14:17:32 -05:00
|
|
|
var indent = "";
|
|
|
|
for (var i in profile_frames)
|
|
|
|
print_frame(profile_frames[i], "", 'frame');
|
2024-08-04 15:20:11 -05:00
|
|
|
|
|
|
|
say("\n");
|
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;
|
|
|
|
|
2024-07-25 17:53:53 -05:00
|
|
|
var report_cache = {};
|
|
|
|
|
|
|
|
var cachest = 0;
|
|
|
|
var cachegroup;
|
|
|
|
var cachetitle;
|
2024-08-15 12:26:37 -05:00
|
|
|
|
|
|
|
profile.cache_reporting = function() { return cache_reporting; }
|
|
|
|
profile.cache_toggle = function() { cache_reporting = !cache_reporting; }
|
|
|
|
profile.cache_dump = function() {
|
|
|
|
report_cache = {};
|
|
|
|
}
|
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
profile.cache = function profile_cache(group, title)
|
2024-07-25 17:53:53 -05:00
|
|
|
{
|
2024-08-15 12:26:37 -05:00
|
|
|
if (!cache_reporting) return;
|
2024-07-25 17:53:53 -05:00
|
|
|
cachest = profile.now();
|
|
|
|
cachegroup = group;
|
|
|
|
cachetitle = title;
|
|
|
|
}
|
|
|
|
|
2024-08-02 20:52:50 -05:00
|
|
|
profile.endcache = function profile_endcache(tag = "")
|
2024-07-25 17:53:53 -05:00
|
|
|
{
|
|
|
|
addreport(cachegroup, cachetitle + tag, cachest);
|
|
|
|
}
|
2024-07-24 14:17:32 -05:00
|
|
|
|
2024-07-25 17:53:53 -05:00
|
|
|
function addreport(group, line, start) {
|
2024-07-24 14:17:32 -05:00
|
|
|
if (typeof group !== 'string') group = 'UNGROUPED';
|
2024-07-25 17:53:53 -05:00
|
|
|
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-07-25 17:53:53 -05:00
|
|
|
function printreport(cache, name) {
|
2024-07-24 14:17:32 -05:00
|
|
|
var report = `==${name}==` + "\n";
|
|
|
|
|
|
|
|
var reports = [];
|
|
|
|
for (var i in cache) {
|
|
|
|
var time = cache[i].reduce((a,b)=>a+b);
|
|
|
|
reports.push({
|
|
|
|
time:time,
|
|
|
|
name:i,
|
|
|
|
hits:cache[i].length,
|
|
|
|
avg:time/cache[i].length
|
|
|
|
});
|
|
|
|
}
|
|
|
|
reports = reports.sort((a,b) => {
|
|
|
|
if (a.avg<b.avg) return 1;
|
|
|
|
return -1;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (var rep of reports)
|
|
|
|
report += `${rep.name} ${profile.best_t(rep.avg)} (${rep.hits} hits) (total ${profile.best_t(rep.time)})\n`;
|
|
|
|
|
|
|
|
return report;
|
|
|
|
};
|
2024-08-05 15:26:18 -05:00
|
|
|
|
2024-09-24 14:12:59 -05:00
|
|
|
profile.data = {};
|
2024-09-24 17:07:32 -05:00
|
|
|
profile.data.cpu = {
|
|
|
|
scripts: [],
|
|
|
|
render: [],
|
|
|
|
physics: [],
|
|
|
|
};
|
|
|
|
|
2024-09-24 14:12:59 -05:00
|
|
|
profile.data.gpu = {};
|
|
|
|
profile.data.physics = {};
|
|
|
|
profile.data.script = {};
|
2024-09-24 17:07:32 -05:00
|
|
|
profile.data.memory = {};
|
2024-09-25 12:46:59 -05:00
|
|
|
profile.data.gfx = {};
|
|
|
|
profile.data.actors = {};
|
|
|
|
profile.curframe = 0;
|
|
|
|
|
|
|
|
function prof_add_stats(obj, stat)
|
|
|
|
{
|
|
|
|
for (var i in stat) {
|
|
|
|
obj[i] ??= [];
|
|
|
|
if (obj[i].last() !== stat[i])
|
|
|
|
obj[i][profile.curframe] = stat[i];
|
|
|
|
}
|
|
|
|
}
|
2024-09-24 14:12:59 -05:00
|
|
|
|
2024-09-24 17:07:32 -05:00
|
|
|
profile.pushdata = function(arr, val)
|
|
|
|
{
|
|
|
|
if (arr.last() !== val)
|
2024-09-25 12:46:59 -05:00
|
|
|
arr[profile.curframe] = val;
|
2024-09-24 17:07:32 -05:00
|
|
|
}
|
|
|
|
|
2024-09-24 14:12:59 -05:00
|
|
|
profile.capture_data = function()
|
|
|
|
{
|
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-24 14:12:59 -05:00
|
|
|
}
|
|
|
|
|
2024-08-05 15:26:18 -05:00
|
|
|
profile.best_mem = function(bytes)
|
|
|
|
{
|
|
|
|
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
if (bytes == 0) return '0 Bytes';
|
|
|
|
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
|
|
return (bytes / Math.pow(1024, i)).toPrecision(3) + ' ' + sizes[i];
|
|
|
|
}
|
|
|
|
|
2024-09-04 13:23:20 -05:00
|
|
|
profile.last_mem = undefined;
|
|
|
|
profile.mems = [];
|
|
|
|
profile.gcs = [];
|
2024-08-06 14:23:21 -05:00
|
|
|
profile.print_gc = function()
|
|
|
|
{
|
|
|
|
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-08-06 14:23:21 -05:00
|
|
|
}
|
|
|
|
|
2024-08-05 15:26:18 -05:00
|
|
|
return {profile};
|