1use std::io::Write;
2
3use anyhow::{Context, Error};
4use console::{style, Term};
5
6use crate::config::Config;
7use crate::python_spy::PythonSpy;
8use crate::stack_trace::StackTrace;
9
10use remoteprocess::Pid;
11
12pub fn print_traces(pid: Pid, config: &Config, parent: Option<Pid>) -> Result<(), Error> {
13 let stdout = std::io::stdout();
14 let mut out = stdout.lock();
15 write_traces(&mut out, pid, config, parent)
16}
17
18pub fn write_traces<W: Write>(
19 out: &mut W,
20 pid: Pid,
21 config: &Config,
22 parent: Option<Pid>,
23) -> Result<(), Error> {
24 let mut process = PythonSpy::new(pid, config)?;
25 if config.dump_json {
26 let traces = process
27 .get_stack_traces()
28 .context("Failed to get stack traces")?;
29 writeln!(out, "{}", serde_json::to_string_pretty(&traces)?)?;
30 return Ok(());
31 }
32
33 writeln!(
34 out,
35 "Process {}: {}",
36 style(process.pid).bold().yellow(),
37 process.process.cmdline()?.join(" ")
38 )?;
39
40 writeln!(
41 out,
42 "Python v{} ({})",
43 style(&process.version).bold(),
44 style(process.process.exe()?).dim()
45 )?;
46
47 if let Some(parentpid) = parent {
48 let parentprocess = remoteprocess::Process::new(parentpid)?;
49 writeln!(
50 out,
51 "Parent Process {}: {}",
52 style(parentpid).bold().yellow(),
53 parentprocess.cmdline()?.join(" ")
54 )?;
55 }
56 writeln!(out)?;
57 let traces = process
58 .get_stack_traces()
59 .context("Failed to get stack traces")?;
60 for trace in traces.iter().rev() {
61 write_trace(out, trace, true)?;
62 }
63
64 if config.subprocesses {
65 for (childpid, parentpid) in process
66 .process
67 .child_processes()
68 .expect("failed to get subprocesses")
69 {
70 let term = Term::stdout();
71 let (_, width) = term.size();
72
73 writeln!(out, "\n{}", &style("-".repeat(width as usize)).dim())?;
74 if parentpid == pid {
78 write_traces(out, childpid, config, Some(parentpid))?;
79 }
80 }
81 }
82 Ok(())
83}
84
85#[cfg(target_os = "linux")]
86pub fn print_trace(trace: &StackTrace, include_activity: bool) {
87 let stdout = std::io::stdout();
88 let mut out = stdout.lock();
89 let _ = write_trace(&mut out, trace, include_activity);
91}
92
93pub fn write_trace<W: Write>(
94 out: &mut W,
95 trace: &StackTrace,
96 include_activity: bool,
97) -> Result<(), Error> {
98 let thread_id = trace.format_threadid();
99
100 let status = if include_activity {
101 format!(" ({})", trace.status_str())
102 } else if trace.owns_gil {
103 " (gil)".to_owned()
104 } else {
105 "".to_owned()
106 };
107
108 match trace.thread_name.as_ref() {
109 Some(name) => {
110 writeln!(
111 out,
112 "Thread {}{}: \"{}\"",
113 style(thread_id).bold().yellow(),
114 status,
115 name
116 )?;
117 }
118 None => {
119 writeln!(out, "Thread {}{}", style(thread_id).bold().yellow(), status)?;
120 }
121 };
122
123 for frame in &trace.frames {
124 let filename = match &frame.short_filename {
125 Some(f) => f,
126 None => &frame.filename,
127 };
128 if frame.line != 0 {
129 writeln!(
130 out,
131 " {} ({}:{})",
132 style(&frame.name).green(),
133 style(&filename).cyan(),
134 style(frame.line).dim()
135 )?;
136 } else {
137 writeln!(
138 out,
139 " {} ({})",
140 style(&frame.name).green(),
141 style(&filename).cyan()
142 )?;
143 }
144
145 if let Some(locals) = &frame.locals {
146 let mut shown_args = false;
147 let mut shown_locals = false;
148 for local in locals {
149 if local.arg && !shown_args {
150 writeln!(out, " {}", style("Arguments:").dim())?;
151 shown_args = true;
152 } else if !local.arg && !shown_locals {
153 writeln!(out, " {}", style("Locals:").dim())?;
154 shown_locals = true;
155 }
156
157 let repr = local.repr.as_deref().unwrap_or("?");
158 writeln!(out, " {}: {}", local.name, repr)?;
159 }
160 }
161 }
162 Ok(())
163}