Analyzing processes

The remote command is used to analyze the status of a running (remote) process. The analysis is always done in a safe and non-intrusive way, as no code is loaded in the memory space of the process under analysis and no memory is modified in the remote process. This makes analysis using PyStack a great option even for those services and applications that are running in environments where the running process must not be impacted in any way (other than being temporarily paused, though --no-block can avoid even that). There are several options available:

usage: pystack remote [-h] [-v] [--no-color] [--no-block] [--native]
                      [--native-all] [--locals] [--exhaustive]
                      pid

Positional Arguments

pid

The PID of the remote process

Named Arguments

-v, --verbose

Default: 0

--no-color

Deactivate colored output

Default: False

--no-block

do not block the process when inspecting its memory

Default: True

--native

Include the native (C) frames in the resulting stack trace

Default: NativeReportingMode.OFF

--native-all

Include native (C) frames from threads not registered with the interpreter (implies --native)

Default: NativeReportingMode.OFF

--locals

Show local variables for each frame in the stack trace

Default: False

--exhaustive

Use all possible methods to obtain the Python stack info (may be slow)

Default: False

To use PyStack, you just need to provide the PID of the process:

$ pystack remote 112
Traceback for thread 112 [] (most recent call last):
    (Python) File "/test.py", line 17, in <module>
        first_func()
    (Python) File "/test.py", line 6, in first_func
        second_func()
    (Python) File "/test.py", line 10, in second_func
        third_func()
    (Python) File "/test.py", line 14, in third_func
        time.sleep(1000)

To learn more about the different options you can use to customize what is reported, check the Customizing the reports section.

Operation modes

There are two main operation modes when analyzing the status of a remote process:

  • The blocking mode (default) will stop the process, reads its memory to produce the report and then continue its execution. The overhead of this operations is very small (normally is under 10ms) but it can be undesirable in some scenarios such as services and applications running in production environments. The advantage of this mode is that the memory retrieved is fully coherent which guarantees that the resulting report will always be correct.

  • The non blocking mode (activated using --no-block) will analyze the process memory to produce a report as the program is running. Although the memory retrieval normally is orders of magnitude faster than the rate at which the interpreter switches state, is still possible that the resulting report is not correct or that it cannot be produced due to incoherent results caused by the intepreter state changing before we’ve finished reading it.

Note

In general, users should prefer blocking mode (the default) because of its correctness unless stopping the process even momentarily is not acceptable, in which case non blocking mode can be used.

Interpreter finalization

By default, pystack will try to only fetch the Python stack trace but there are some scenarios where there will be no active Python stack trace even if the process is running. The most typical scenario is that the process is shutting down and the Python interpreter is in the process of being destroyed. In this case you will see the following error message with a warning explaining the situation:

$ pystack remote 140
WARNING(process_remote): The interpreter is shutting itself down so it is possible that no Python stack trace is
available for inspection. You can still use --native-all  to force displaying all the threads.

💀 Could not gather enough information to extract the Python frame information 💀

...

As the warning indicates, you can use --native-all to force displaying all the threads, even if there is no active Python stack trace:

$ pystack remote 140 --native-all
Traceback for thread 140 [Garbage collecting] (most recent call last):
    (C) File "???", line 0, in _start ()
    (C) File "???", line 0, in __libc_start_main ()
    (C) File "Modules/main.c", line 743, in Py_BytesMain (/usr/lib/libpython3.8.so.1.0)
    (C) File "Modules/main.c", line 691, in Py_RunMain (/usr/lib/libpython3.8.so.1.0)
    (C) File "Python/pylifecycle.c", line 1219, in Py_FinalizeEx (/usr/lib/libpython3.8.so.1.0)
    (C) File "Modules/gcmodule.c", line 1844, in PyGC_Collect (/usr/lib/libpython3.8.so.1.0)
    (C) File "Modules/gcmodule.c", line 1240, in collect_with_callback.constprop.33 (/usr/lib/libpython3.8.so.1.0)
    (C) File "Modules/gcmodule.c", line 1063, in collect.constprop.34 (/usr/lib/libpython3.8.so.1.0)
    (C) File "Modules/gcmodule.c", line 495, in move_unreachable (inlined) (/usr/lib/libpython3.8.so.1.0)

Here you can see that the process seems to be stuck while garbage collecting some object at interpreter finalization.

Tip

In some cases, is still possible to retrieve the Python stack trace by using the --exhaustive command line option.