Skip to main content

py_spy/
dump.rs

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            // child_processes() returns the whole process tree, since we're recursing here
75            // though we could end up printing grandchild processes multiple times. Limit down
76            // to just once
77            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    // Swallow write errors here to preserve the old println! behaviour.
90    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}