1
0
Fork 0

add probe class to debug.nas

This commit is contained in:
Henning Stahlke 2019-01-31 12:53:28 +01:00
parent e24206f605
commit 801512bb8c

View file

@ -63,6 +63,9 @@
# #
# debug.propify(<variable>) ... turn about everything into a props.Node # debug.propify(<variable>) ... turn about everything into a props.Node
# #
# debug.Probe class ... base class to collect stats; details below
# debug.Breakpoint class ... conditional backtrace; details below
#
# CAVE: this file makes extensive use of ANSI color codes. These are # CAVE: this file makes extensive use of ANSI color codes. These are
# interpreted by UNIX shells and MS Windows with ANSI.SYS extension # interpreted by UNIX shells and MS Windows with ANSI.SYS extension
# installed. If the color codes aren't interpreted correctly, then # installed. If the color codes aren't interpreted correctly, then
@ -438,6 +441,156 @@ var isnan = func {
return !!size(err); return !!size(err);
} }
# Probe class - collect statistics; controlled via property tree
# Data can be viewed / modified in the prop tree /_debug/nas/probe-<myLabel>/*
# ./enable bool,
# ./reset bool, reset hit counters to 0 and _start_time to now
# ./hits[i] number of hits, by default i=0 -> hits
# secondary counters can be added under the same path
# with addCounter()
# ./time after generateStats() show how long the probe was enabled
# ./hps after generateStats() show avg. hits/second
# ./hitratio after generateStats() if two counters: hits[1]/hits[0]
#
# == Example ==
# var myProbe = debug.Probe.new("myLabel").enable();
# var cnt2 = myProbe.addCounter(); # create a 2nd counter
#
# #at the place of interest (e.g. in some loop or class method) insert:
# myProbe.hit(); # count how often this place in the code is hit
# if (condition)
# myProbe.hit(cnt2); # count how often condition is true
#
# print(myProbe.getHits());
# print(myProbe.getHits(cnt2)/myProbe.getHits()); # print hit ratio
#
var Probe = {
# label: Used in property path
# prefix: Optional
new: func(label, class = "probe") {
var obj = {
parents: [Probe],
label: "",
hits: [0],
node: nil,
_enableN: nil,
_resetN: nil,
_hitsN: [],
_start_time: 0,
_stop_time: 0,
_timeoutN: nil, # > 0, disable probe after _timeout seconds
_L: [],
};
if (typeof(label) != "scalar" or typeof(class) != "scalar") {
die("Invalid argument type to Probe.new");
}
class = globals.string.replace(class, " ", "_");
label = globals.string.replace(label, " ", "_");
obj.label = globals.string.replace(label, "/", "_");
obj.node = props.globals.getNode("/_debug/nas/"~class~"-"~obj.label, 1);
obj.node.removeChildren();
obj._enableN = obj.node.addChild("enable");
obj._enableN.setBoolValue(0);
append(obj._L,
setlistener(obj._enableN, func(n) {
if (n.getValue()) obj.enable();
else obj.disable();
}, 0, 0)
);
obj._resetN = obj.node.addChild("reset");
obj._resetN.setBoolValue(0);
append(obj._L,
setlistener(obj._resetN, func(n) {
if (n.getValue()) {
obj.reset();
n.setValue(0);
}
}, 0, 0)
);
append(obj._hitsN, obj.node.addChild("hits"));
obj._hitsN[0].setIntValue(0);
return obj;
},
reset: func {
forindex (var i; me.hits) {
me.hits[i] = 0;
me._hitsN[i].setValue(0);
}
me._start_time = systime();
},
# set timeout, next hit() after timeout will disable()
setTimeout: func(seconds) {
if (!isa(me._timeoutN, props.Node))
me._timeoutN = me.node.getNode("timeout", 1);
me._timeoutN.setValue(num(seconds) or 0);
return me;
},
#enable counting
enable: func {
me._enableN.setValue(1);
me._start_time = systime();
return me;
},
#disable counting, write time and hit/s to property tree
disable: func {
me._enableN.setValue(0);
me._stop_time = systime();
me.generateStats();
return me;
},
generateStats: func {
if (me._start_time) {
if (me._enableN.getValue())
me._stop_time = systime();
var dt = me._stop_time - me._start_time;
me.node.getNode("time", 1).setValue(dt);
me.node.getNode("hps", 1).setValue(me.hits[0] / dt );
if (size(me.hits) == 2)
me.node.getNode("hitratio",1).setValue(me.hits[1] / me.hits[0] or 1);
}
},
getHits: func(counter_id = 0) {
return me.hits[counter_id];
},
# add secondary counter(s)
# returns counter id
addCounter: func {
append(me._hitsN, me.node.addChild("hits"));
append(me.hits, 0);
return size(me._hitsN) - 1;
},
# increase counter (if enabled)
# use addCounter() before using counter_id > 0
hit: func(counter_id = 0, callback = nil) {
if (me._enableN.getValue()) {
if (counter_id >= size(me._hitsN)) {
print("debug.Probe.hit(): Invalid counter_id");
me.disable();
return nil;
}
if (isa(me._timeoutN, props.Node)) {
var timeout = me._timeoutN.getValue();
if (timeout and systime() - me._start_time > timeout) {
return me.disable();
}
}
me.hits[counter_id] += 1;
me._hitsN[counter_id].setIntValue(me.hits[counter_id]);
if (typeof(callback) == "func")
callback(me.hits);
}
return me;
},
};
# Breakpoint (BP) - do conditional backtrace (BT) controlled via property tree # Breakpoint (BP) - do conditional backtrace (BT) controlled via property tree
# * count how often the BP was hit # * count how often the BP was hit
# * do only a limited number of BT, avoid flooding the log / console # * do only a limited number of BT, avoid flooding the log / console
@ -461,21 +614,13 @@ var Breakpoint = {
# dump_locals: bool passed to backtrace. Dump variables in BT. # dump_locals: bool passed to backtrace. Dump variables in BT.
new: func(label, dump_locals = 1) { new: func(label, dump_locals = 1) {
var obj = { var obj = {
parents: [Breakpoint], parents: [Breakpoint, Probe.new(label, "bp")],
_tokensN: nil,
tokens: 0, tokens: 0,
_hitsN: nil,
hits: 0,
label: "",
dump_locals: num(dump_locals), dump_locals: num(dump_locals),
}; };
label = globals.string.replace(label, " ", "_"); obj._enableN.remove();
obj.label = globals.string.replace(label, "/", "_"); obj._enableN = obj.node.getNode("tokens", 1);
var prop_path = "/_debug/nas/bp-"~obj.label~"/"; obj._enableN.setIntValue(0);
obj._tokensN = props.globals.getNode(prop_path~"token", 1);
obj._hitsN = props.globals.getNode(prop_path~"hits", 1);
obj._hitsN.setIntValue(0);
obj.disable();
return obj; return obj;
}, },
@ -485,40 +630,30 @@ var Breakpoint = {
if (num(tokens) == nil) tokens = 1; if (num(tokens) == nil) tokens = 1;
if (tokens < 0) tokens = 0; if (tokens < 0) tokens = 0;
me.tokens = tokens; me.tokens = tokens;
me._tokensN.setIntValue(tokens); me._enableN.setIntValue(tokens);
return me; return me;
}, },
# set tokens to zero, disables backtrace in hit()
disable: func {
me._tokensN.setIntValue(0);
return me;
},
# get total number of hits (not #backtraces done)
getHits: func {
return me.hits;
},
# hit the breakpoint, e.g. do backtrace if we have tokens available # hit the breakpoint, e.g. do backtrace if we have tokens available
hit: func(callback = nil) { hit: func(callback = nil) {
me.hits += 1; me.hits[0] += 1;
me._hitsN.setIntValue(me.hits); me._hitsN[0].setIntValue(me.hits[0]);
me.tokens = me._tokensN.getValue(); me.tokens = me._enableN.getValue();
if (me.tokens > 0) { if (me.tokens > 0) {
me.tokens -= 1; me.tokens -= 1;
if (typeof(callback) == "func") { if (typeof(callback) == "func") {
callback(me.hits, me.tokens); callback(me.hits[0], me.tokens);
} }
else { else {
debug.backtrace(me.label, me.dump_locals, 1); debug.backtrace(me.label, me.dump_locals, 1);
} }
me._tokensN.setValue(me.tokens); me._enableN.setValue(me.tokens);
} }
return me; return me;
}, },
}; };
# --prop:debug=1 enables debug mode with additional warnings # --prop:debug=1 enables debug mode with additional warnings
# #
_setlistener("sim/signals/nasal-dir-initialized", func { _setlistener("sim/signals/nasal-dir-initialized", func {