///|
fn md(text : String) -> String {
let mut result = ""
for c in text {
if c.to_int() < 32 || c.to_int() == 127 {
result += " "
} else {
match c {
'<' => result += "<"
'>' => result += ">"
'&' => result += "&"
'|' | '`' | '[' | ']' | '*' | '_' | '\\' =>
result += "\\" + c.to_string()
_ => result += c.to_string()
}
}
}
result
}
///|
pub fn TaskReport::to_markdown(self : TaskReport) -> String {
let mut out = "# MoonLockLens task incident\n\nRoot PID: \{self.root_pid}; start ticks: \{self.root_start_ticks}.\n\nCaptured at: \{md(self.started_at)}. Samples: \{self.sample_count}; observed span: \{self.observed_span_ms} ms.\n\nMaximum consecutive samples with a confirmed file-lock wait: \{self.max_consecutive_wait_samples}; sampled span: \{self.max_sampled_wait_span_ms} ms. This does not prove an uninterrupted wait between samples.\n\n"
for frame in self.frames {
out += "## Sample +\{frame.elapsed_ms} ms - \{frame.root_state}\n\n"
out += "| PID / start | Parent | Name | Leader state | Finding | Wait channel |\n|---|---|---|---|---|---|\n"
for p in frame.processes {
out += "| \{p.info.pid} / \{p.info.start_ticks} | \{p.info.parent_pid} | \{md(p.info.name)} | \{md(p.info.state)} | \{p.finding} | \{md(p.info.wchan)} (\{p.info.wchan_status}) |\n"
}
out += "\n"
for p in frame.processes {
for request in p.requests {
let parent = match request.parent_line {
Some(line) => "L\{line}"
None => "unavailable"
}
out += "- Process \{p.info.pid}: request L\{request.line}, direct blocker \{parent}.\n"
}
for b in p.blockers {
let key = match b.file {
Some(k) =>
"\{k.major.to_string(radix=16)}:\{k.minor.to_string(radix=16)}:\{k.inode}"
None => "no inode"
}
let end = match b.range.end {
Some(n) => n.to_string()
None => "EOF"
}
let owner = match b.owner {
Process(pid) => "PID \{pid}"
OpenDescription => "OFD owner unknown"
NotVisible => "PID not visible"
}
let parent = match b.parent_line {
Some(line) => "waits for L\{line}"
None => "root blocker"
}
out += "\n- Process \{p.info.pid}: blocker context L\{b.line}, \{owner}, file \{key}, bytes \{b.range.start}..\{end}, \{parent}.\n"
}
}
out += "\n"
for p in frame.blocker_processes {
out += "- External blocker process: PID \{p.pid}, \{md(p.name)}, start \{p.start_ticks}.\n"
}
for issue in frame.issues {
out += "- Evidence gap: \{md(issue)}\n"
}
out += "\n"
}
out += "## Observed transitions\n\n"
for t in self.transitions {
out += "- +\{t.at_ms} ms: PID \{t.pid}/\{t.start_ticks}, \{t.change}: \{md(t.before)} -> \{md(t.after)}.\n"
}
out += "\n## Next checks\n\n"
for advice in self.next_steps {
out += "- \{advice}\n"
}
out +
"\nScope: visible procfs, process leaders and current descendants. No thread-wide root-cause or deadlock claim; snapshot collection is not atomic.\n"
}