Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions docs/WIKI.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ help [command]
View Python execution stack information for all threads currently running in the process, and support analyzing native stacks and exporting to files.

```shell
stack [pid] [-f <value>] [--native]
stack [pid] [-f <value>] [--native] [-a|--async]
```

##### Parameter Analysis
Expand All @@ -53,6 +53,7 @@ stack [pid] [-f <value>] [--native]
| pid | No | Process ID to analyze, defaults to the injected process ID | 3303 |
| -f, --filepath | No | File path to export thread stacks to | /home/admin/stack.log |
| --native | No | Whether to analyze native stacks of Python threads, defaults to False | --native |
| -a, --async | No | Whether to display async coroutine/task stacks | -a |

##### Output Display
Command examples:
Expand All @@ -64,6 +65,10 @@ stack
# View native stacks of Python threads
stack --native

# View async coroutine/task stacks
stack -a
stack --async

# Export execution stack information to a file
stack -f ./stack.log
```
Expand All @@ -80,19 +85,58 @@ Analyzing native thread stacks:
View Python execution stack information for all threads currently running in the process, and support exporting to files.

```shell
stack [filepath]
stack [filepath] [-a|--async]
```

##### Parameter Analysis
| Parameter | Required | Meaning | Example |
| --- | --- | --- | --- |
| filepath | No | File path to export thread stacks to | /home/admin/stack.log |
| -a, --async | No | Whether to display async coroutine/task stacks | -a |

##### Output Display
Executing the `stack` command will display stack information for all threads in the console.

![img.png](https://raw.githubusercontent.com/alibaba/PyFlightProfiler/refs/heads/main/docs/images/stack_mac.png)

### Async Coroutine Stack: stack --async
View all async coroutines/tasks running across all event loops in all threads.

```shell
stack -a
stack --async
```

The async stack display shows:
- **Thread**: The thread where the event loop is running
- **Task Name**: The asyncio Task name
- **State**: Task state (PENDING, WAITING, FINISHED, CANCELLED, FAILED)
- **Coroutine**: The coroutine function name
- **Stack**: Full coroutine call chain following the `cr_await` chain

Example output:
```
============================================================
Async Coroutine/Task Stacks
============================================================

Thread: AsyncEventLoop (tid: 0x16f8a7000)
--------------------------------------------------

Task #1: FetchAPI1
State: WAITING
Coroutine: fetch_data_loop
Stack (3 frames):
File "/app/demo.py", line 95, in fetch_data_loop
await fetch_data(name, delay)
File "/app/demo.py", line 26, in fetch_data
await asyncio.sleep(delay)
File "/usr/lib/python3.9/asyncio/tasks.py", line 649, in sleep
return await future

============================================================
```


## Method Execution Observation: watch
### Observing Method Input, Output, and Time Consumption
Expand Down
48 changes: 46 additions & 2 deletions docs/WIKI_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ help [command]
查看进程当前运行的所有线程的Python执行栈信息,并支持分析native栈以及导出到文件。

```shell
stack [pid] [-f <value>] [--native]
stack [pid] [-f <value>] [--native] [-a|--async]
```

##### 参数解析
Expand All @@ -53,6 +53,7 @@ stack [pid] [-f <value>] [--native]
| pid | 否 | 分析的进程ID,默认为被注入进程的ID | 3303 |
| -f, --filepath | 否 | 线程栈导出到的文件位置 | /home/admin/stack.log |
| --native | 否 | 是否分析Python线程的本地栈,默认为False | --native |
| -a, --async | 否 | 是否展示异步协程/任务调用栈 | -a |

##### 输出展示
命令示例:
Expand All @@ -64,6 +65,10 @@ stack
# 查看Python线程的native栈
stack --native

# 查看异步协程/任务调用栈
stack -a
stack --async

# 导出执行栈信息到文件中
stack -f ./stack.log
```
Expand All @@ -80,19 +85,58 @@ stack -f ./stack.log
查看进程当前运行的所有线程的Python执行栈信息,并支持导出到文件。

```shell
stack [filepath]
stack [filepath] [-a|--async]
```

##### 参数解析
| 参数 | 必填 | 含义 | 示例 |
| --- | --- | --- | --- |
| filepath | 否 | 线程栈导出到的文件位置 | /home/admin/stack.log |
| -a, --async | 否 | 是否展示异步协程/任务调用栈 | -a |

##### 输出展示
执行`stack`命令可在控制台展示所有线程的栈信息。

![img.png](https://raw.githubusercontent.com/alibaba/PyFlightProfiler/refs/heads/main/docs/images/stack_mac.png)

### 异步协程栈: stack --async
查看进程中所有线程、所有事件循环中运行的异步协程/任务。

```shell
stack -a
stack --async
```

异步协程栈展示内容包括:
- **Thread**: 事件循环所在的线程
- **Task Name**: asyncio Task 的名称
- **State**: 任务状态(PENDING, WAITING, FINISHED, CANCELLED, FAILED)
- **Coroutine**: 协程函数名
- **Stack**: 完整的协程调用链(通过 `cr_await` 链递归追踪)

输出示例:
```
============================================================
Async Coroutine/Task Stacks
============================================================

Thread: AsyncEventLoop (tid: 0x16f8a7000)
--------------------------------------------------

Task #1: FetchAPI1
State: WAITING
Coroutine: fetch_data_loop
Stack (3 frames):
File "/app/demo.py", line 95, in fetch_data_loop
await fetch_data(name, delay)
File "/app/demo.py", line 26, in fetch_data
await asyncio.sleep(delay)
File "/usr/lib/python3.9/asyncio/tasks.py", line 649, in sleep
return await future

============================================================
```


## 方法执行观测watch
### 观察执行方法输入、输出及耗时
Expand Down
2 changes: 1 addition & 1 deletion flight_profiler/code_inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ def run_app():
loop.run_until_complete(asyncio.wait(tasks))


profile_thread = threading.Thread(target=run_app)
profile_thread = threading.Thread(target=run_app, name="flight-profiler-injector")
profile_thread.start()
logger.info("pyFlightProfiler: start code inject successfully")
14 changes: 9 additions & 5 deletions flight_profiler/help_descriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,9 @@ def _build_help_msg(self) -> str:

if is_linux():
STACK_COMMAND_DESCRIPTION = CommandDescription(
usage=["stack [pid] [-f <value>] [--native]"],
usage=["stack [pid] [-f <value>] [--native] [-a|--async]"],
summary="Inspect stack frames of current running process.",
examples=["stack", "stack --native", "stack -f ./stack.log"],
examples=["stack", "stack --native", "stack -a", "stack --async", "stack -f ./stack.log"],
wiki="https://github.com/alibaba/PyFlightProfiler/blob/main/docs/WIKI.md",
options=[
(
Expand All @@ -222,15 +222,19 @@ def _build_help_msg(self) -> str:
),
("-f, --filepath", "redirect thread stack to filepath."),
("--native", "display native stack frames."),
("-a, --async", "display async coroutine/task stacks."),
],
)
else:
STACK_COMMAND_DESCRIPTION = CommandDescription(
usage=["stack [filepath]"],
usage=["stack [filepath] [-a|--async]"],
summary="Inspect stack frames of current running process.",
examples=["stack", "stack ./stack.log"],
examples=["stack", "stack -a", "stack --async", "stack ./stack.log"],
wiki="https://github.com/alibaba/PyFlightProfiler/blob/main/docs/WIKI.md",
options=[("<filepath>", "redirect thread stack to filepath.")],
options=[
("<filepath>", "redirect thread stack to filepath."),
("-a, --async", "display async coroutine/task stacks."),
],
)

TRACE_COMMAND_DESCRIPTION = CommandDescription(
Expand Down
36 changes: 36 additions & 0 deletions flight_profiler/plugins/stack/cli_plugin_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,48 @@ def __analyze_under_linux(self, params: StackParams):
f"{COLOR_GREEN}write {stack_literal} to {params.filepath} successfully!{COLOR_END}"
)

def __show_coroutine_stacks(self, params: StackParams):
"""
Show async coroutine stacks via server communication.
Coroutines must be fetched from the target process's event loop.
"""
body = {"target": "stack", "param": "async"}
try:
client = FlightClient(host="localhost", port=self.port)
except:
show_error_info("Target process exited!")
return
try:
coro_lines: List[str] = []
for line in client.request_stream(body):
if line:
line = line.decode("utf-8")
if params.filepath is not None:
coro_lines.append(line)
else:
print(line)
if params.filepath is not None:
with open(params.filepath, "w") as f:
for line in coro_lines:
print(line, file=f, flush=True)
print(
f"{COLOR_GREEN}write coroutine stacks to {params.filepath} successfully!{COLOR_END}"
)
finally:
client.close()

def do_action(self, cmd):
try:
stack_param: StackParams = global_stack_parser.parse_stack_params(cmd)
except:
print(self.get_help())
return

# Handle async coroutine stack display
if stack_param.async_stack:
self.__show_coroutine_stacks(stack_param)
return

if is_linux():
try:
self.__analyze_under_linux(stack_param)
Expand Down
Loading