///|
/// Aggregated execution data for one node.
pub(all) struct NodeProfile {
node_id : String
mut kind : String
mut total : Int
mut success : Int
mut failure : Int
mut running : Int
mut first_tick : Int
mut last_tick : Int
} derive(Debug, Eq)
///|
pub fn NodeProfile::dominant_status(self : NodeProfile) -> BtStatus {
if self.failure >= self.success && self.failure >= self.running {
Failure
} else if self.running >= self.success && self.running >= self.failure {
Running
} else {
Success
}
}
///|
pub fn NodeProfile::to_line(self : NodeProfile) -> String {
self.node_id +
": total=" +
self.total.to_string() +
", success=" +
self.success.to_string() +
", failure=" +
self.failure.to_string() +
", running=" +
self.running.to_string() +
", ticks=" +
self.first_tick.to_string() +
".." +
self.last_tick.to_string() +
", dominant=" +
self.dominant_status().to_text()
}
///|
pub(all) struct ProfileReport {
name : String
profiles : Array[NodeProfile]
total_events : Int
total_ticks : Int
} derive(Debug, Eq)
///|
pub fn ProfileReport::summary(self : ProfileReport) -> String {
self.name +
": nodes=" +
self.profiles.length().to_string() +
", events=" +
self.total_events.to_string() +
", ticks=" +
self.total_ticks.to_string()
}
///|
pub fn ProfileReport::lines(self : ProfileReport) -> Array[String] {
let out = Array::new()
out.push(self.summary())
let mut i = 0
while i < self.profiles.length() {
out.push(self.profiles[i].to_line())
i = i + 1
}
out
}
///|
pub fn ProfileReport::hotspots(self : ProfileReport, limit : Int) -> Array[NodeProfile] {
let sorted = clone_profiles(self.profiles)
sort_profiles_by_total(sorted)
let out = Array::new()
let mut i = 0
while i < sorted.length() && i < limit {
out.push(sorted[i])
i = i + 1
}
out
}
///|
pub fn ProfileReport::hotspot_lines(self : ProfileReport, limit : Int) -> Array[String] {
let hot = self.hotspots(limit)
let out = Array::new()
let mut i = 0
while i < hot.length() {
out.push((i + 1).to_string() + ". " + hot[i].to_line())
i = i + 1
}
out
}
///|
pub fn profile_trace(name : String, events : Array[TickEvent]) -> ProfileReport {
let profiles = Array::new()
let mut max_tick = 0
let mut i = 0
while i < events.length() {
let event = events[i]
if event.tick > max_tick {
max_tick = event.tick
}
match profile_index(profiles, event.node_id) {
Some(idx) => update_profile(profiles[idx], event)
None => profiles.push(new_profile(event))
}
i = i + 1
}
{ name, profiles, total_events: events.length(), total_ticks: max_tick }
}
///|
pub fn profile_fixture(name : String) -> Result[ProfileReport, BtError] {
match load_fixture(name) {
Ok(doc) => {
let engine = new_engine(doc.tree, blackboard=doc.blackboard)
match engine.run_until_done(max_ticks=32) {
Ok(_) => Ok(profile_trace(name, engine.trace))
Err(err) => Err(InvalidTree(err.message()))
}
}
Err(err) => Err(err)
}
}
///|
pub fn fixture_profile_report() -> Array[String] {
let specs = fixture_catalog()
let out = Array::new()
out.push("MoonBTKit fixture profile")
let mut i = 0
while i < specs.length() {
match profile_fixture(specs[i].name) {
Ok(report) => out.push(report.summary())
Err(err) => out.push(specs[i].name + ": error " + err.message())
}
i = i + 1
}
out
}
///|
pub fn profile_markdown(report : ProfileReport, hotspot_limit? : Int) -> String {
let limit = hotspot_limit.unwrap_or(5)
let lines = Array::new()
lines.push("## Profile: " + report.name)
lines.push("")
lines.push("- " + report.summary())
lines.push("")
lines.push("### Hotspots")
lines.push("")
let hot = report.hotspot_lines(limit)
let mut i = 0
while i < hot.length() {
lines.push("- " + hot[i])
i = i + 1
}
join_strings(lines, "\n")
}
///|
fn new_profile(event : TickEvent) -> NodeProfile {
let p = {
node_id: event.node_id,
kind: event.kind,
total: 0,
success: 0,
failure: 0,
running: 0,
first_tick: event.tick,
last_tick: event.tick,
}
update_profile(p, event)
p
}
///|
fn update_profile(profile : NodeProfile, event : TickEvent) -> Unit {
profile.kind = event.kind
profile.total = profile.total + 1
if event.tick < profile.first_tick {
profile.first_tick = event.tick
}
if event.tick > profile.last_tick {
profile.last_tick = event.tick
}
match event.status {
Success => profile.success = profile.success + 1
Failure => profile.failure = profile.failure + 1
Running => profile.running = profile.running + 1
}
}
///|
fn profile_index(profiles : Array[NodeProfile], node_id : String) -> Int? {
let mut i = 0
while i < profiles.length() {
if profiles[i].node_id == node_id {
return Some(i)
}
i = i + 1
}
None
}
///|
fn clone_profiles(values : Array[NodeProfile]) -> Array[NodeProfile] {
let out = Array::new()
let mut i = 0
while i < values.length() {
out.push(values[i])
i = i + 1
}
out
}
///|
fn sort_profiles_by_total(values : Array[NodeProfile]) -> Unit {
let mut i = 0
while i < values.length() {
let mut best = i
let mut j = i + 1
while j < values.length() {
if values[j].total > values[best].total {
best = j
}
j = j + 1
}
if best != i {
let tmp = values[i]
values[i] = values[best]
values[best] = tmp
}
i = i + 1
}
}