viztracer 1.1.0__cp314-cp314t-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of viztracer might be problematic. Click here for more details.
- viztracer/__init__.py +19 -0
- viztracer/__main__.py +8 -0
- viztracer/attach.py +67 -0
- viztracer/attach_process/LICENSE +203 -0
- viztracer/attach_process/__init__.py +0 -0
- viztracer/attach_process/add_code_to_python_process.py +582 -0
- viztracer/attach_process/attach_x86.dll +0 -0
- viztracer/attach_process/inject_dll_amd64.exe +0 -0
- viztracer/attach_process/linux_and_mac/lldb_prepare.py +54 -0
- viztracer/attach_process/run_code_on_dllmain_amd64.dll +0 -0
- viztracer/attach_process/run_code_on_dllmain_x86.dll +0 -0
- viztracer/cellmagic.py +70 -0
- viztracer/code_monkey.py +353 -0
- viztracer/decorator.py +164 -0
- viztracer/event_base.py +81 -0
- viztracer/functree.py +135 -0
- viztracer/html/flamegraph.html +34 -0
- viztracer/html/trace_viewer_embedder.html +203 -0
- viztracer/html/trace_viewer_full.html +10207 -0
- viztracer/main.py +699 -0
- viztracer/modules/eventnode.c +172 -0
- viztracer/modules/eventnode.h +73 -0
- viztracer/modules/pythoncapi_compat.h +1726 -0
- viztracer/modules/quicktime.c +177 -0
- viztracer/modules/quicktime.h +104 -0
- viztracer/modules/snaptrace.c +2205 -0
- viztracer/modules/snaptrace.h +134 -0
- viztracer/modules/snaptrace_member.c +483 -0
- viztracer/modules/util.c +45 -0
- viztracer/modules/util.h +22 -0
- viztracer/modules/vcompressor/vc_dump.c +1131 -0
- viztracer/modules/vcompressor/vc_dump.h +49 -0
- viztracer/modules/vcompressor/vcompressor.c +396 -0
- viztracer/modules/vcompressor/vcompressor.h +15 -0
- viztracer/patch.py +307 -0
- viztracer/report_builder.py +311 -0
- viztracer/snaptrace.cp314t-win_amd64.pyd +0 -0
- viztracer/snaptrace.pyi +77 -0
- viztracer/util.py +196 -0
- viztracer/vcompressor.cp314t-win_amd64.pyd +0 -0
- viztracer/vcompressor.pyi +10 -0
- viztracer/viewer.py +528 -0
- viztracer/vizcounter.py +20 -0
- viztracer/vizevent.py +31 -0
- viztracer/vizlogging.py +20 -0
- viztracer/vizobject.py +28 -0
- viztracer/vizplugin.py +143 -0
- viztracer/viztracer.py +472 -0
- viztracer/web_dist/LICENSE +189 -0
- viztracer/web_dist/index.html +127 -0
- viztracer/web_dist/service_worker.js +279 -0
- viztracer/web_dist/trace_processor +300 -0
- viztracer/web_dist/v52.0-6b9586def/assets/MaterialSymbolsOutlined.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-100.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-300.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-400.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-500.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Light.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Regular.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoMono-Regular.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/brand.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.html +3946 -0
- viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.js +7539 -0
- viztracer/web_dist/v52.0-6b9586def/assets/favicon.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/logo-128.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/logo-3d.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_atrace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_battery_counters.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_board_voltage.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_coarse.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_fine.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_freq.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_voltage.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_frame_timeline.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ftrace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_gpu_mem_total.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_java_heap_dump.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_lmk.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_logcat.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_long_trace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_mem_hifreq.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_meminfo.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_native_heap_profiler.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_one_shot.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_profiling.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ps_stats.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ring_buf.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_syscalls.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_vmstat.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/scheduling_latency.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/vscode-icon.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/engine_bundle.js +3 -0
- viztracer/web_dist/v52.0-6b9586def/frontend_bundle.js +5495 -0
- viztracer/web_dist/v52.0-6b9586def/index.html +127 -0
- viztracer/web_dist/v52.0-6b9586def/manifest.json +52 -0
- viztracer/web_dist/v52.0-6b9586def/perfetto.css +5737 -0
- viztracer/web_dist/v52.0-6b9586def/stdlib_docs.json +1 -0
- viztracer/web_dist/v52.0-6b9586def/trace_config_utils.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/trace_processor.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/trace_processor_memory64.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/traceconv.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/traceconv_bundle.js +2 -0
- viztracer-1.1.0.dist-info/METADATA +316 -0
- viztracer-1.1.0.dist-info/RECORD +109 -0
- viztracer-1.1.0.dist-info/WHEEL +5 -0
- viztracer-1.1.0.dist-info/entry_points.txt +3 -0
- viztracer-1.1.0.dist-info/licenses/LICENSE +222 -0
- viztracer-1.1.0.dist-info/licenses/NOTICE.txt +27 -0
- viztracer-1.1.0.dist-info/top_level.txt +1 -0
viztracer/main.py
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/gaogaotiantian/viztracer/blob/master/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import atexit
|
|
6
|
+
import base64
|
|
7
|
+
import builtins
|
|
8
|
+
import io
|
|
9
|
+
import json
|
|
10
|
+
import multiprocessing.util # type: ignore
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import shutil
|
|
14
|
+
import signal
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
import types
|
|
20
|
+
import re
|
|
21
|
+
from types import CodeType
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from . import __version__
|
|
25
|
+
from .code_monkey import CodeMonkey
|
|
26
|
+
from .patch import install_all_hooks
|
|
27
|
+
from .report_builder import ReportBuilder
|
|
28
|
+
from .util import color_print, frame_stack_has_func, pid_exists, same_line_print, time_str_to_us, unique_file_name
|
|
29
|
+
from .viztracer import VizTracer
|
|
30
|
+
|
|
31
|
+
# For all the procedures in VizUI, return a tuple as the result
|
|
32
|
+
# The first element bool indicates whether the procedure succeeds
|
|
33
|
+
# The second element is the error message if it fails.
|
|
34
|
+
VizProcedureResult = tuple[bool, str | None]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class VizUI:
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
self.tracer: VizTracer | None = None
|
|
40
|
+
self.parser: argparse.ArgumentParser = self.create_parser()
|
|
41
|
+
self.verbose: int = 1
|
|
42
|
+
self.ofile: str = "result.json"
|
|
43
|
+
self.options: argparse.Namespace = argparse.Namespace()
|
|
44
|
+
self.args: list[str] = []
|
|
45
|
+
self._exiting: bool = False
|
|
46
|
+
self.multiprocess_output_dir: str = tempfile.mkdtemp()
|
|
47
|
+
self.cwd: str = os.getcwd()
|
|
48
|
+
|
|
49
|
+
def create_parser(self) -> argparse.ArgumentParser:
|
|
50
|
+
parser = argparse.ArgumentParser(prog="python -m viztracer")
|
|
51
|
+
parser.add_argument("--version", action="store_true", default=False,
|
|
52
|
+
help="show version of viztracer")
|
|
53
|
+
parser.add_argument("-c", "--cmd_string", nargs="?", default=None,
|
|
54
|
+
help="program passed in as string")
|
|
55
|
+
parser.add_argument("--rcfile", nargs="?", default=None,
|
|
56
|
+
help="specify rcfile for viztracer")
|
|
57
|
+
parser.add_argument("--tracer_entries", nargs="?", type=int, default=1000000,
|
|
58
|
+
help="size of circular buffer. How many entries can it store")
|
|
59
|
+
filename_group = parser.add_mutually_exclusive_group()
|
|
60
|
+
filename_group.add_argument("--output_file", "-o", nargs="?", default=None,
|
|
61
|
+
help="output file path. End with .json or .html or .gz")
|
|
62
|
+
filename_group.add_argument("--unique_output_file", "-u", action="store_true", default=False,
|
|
63
|
+
help="Use a unique file name for each run")
|
|
64
|
+
parser.add_argument("--output_dir", nargs="?", default=None,
|
|
65
|
+
help="output directory. Should only be used when --pid_suffix is used")
|
|
66
|
+
parser.add_argument("--file_info", action="store_true", default=False,
|
|
67
|
+
help=argparse.SUPPRESS)
|
|
68
|
+
parser.add_argument("--quiet", action="store_true", default=False,
|
|
69
|
+
help="stop VizTracer from printing anything")
|
|
70
|
+
parser.add_argument("--trace_self", action="store_true", default=False,
|
|
71
|
+
help=argparse.SUPPRESS)
|
|
72
|
+
parser.add_argument("--plugins", nargs="*", default=[],
|
|
73
|
+
help="specify plugins for VizTracer")
|
|
74
|
+
parser.add_argument("--max_stack_depth", nargs="?", type=int, default=-1,
|
|
75
|
+
help="maximum stack depth you want to trace.")
|
|
76
|
+
parser.add_argument("--min_duration", nargs="?", default="0",
|
|
77
|
+
help="minimum duration of function to log")
|
|
78
|
+
parser.add_argument("--exclude_files", nargs="*", default=None,
|
|
79
|
+
help=("specify the files(directories) you want to exclude from tracing. "
|
|
80
|
+
"Can't be used with --include_files"))
|
|
81
|
+
parser.add_argument("--include_files", nargs="*", default=None,
|
|
82
|
+
help=("specify the only files(directories) you want to include from tracing. "
|
|
83
|
+
"Can't be used with --exclude_files"))
|
|
84
|
+
parser.add_argument("--ignore_c_function", action="store_true", default=False,
|
|
85
|
+
help="ignore all c functions including most builtin functions and libraries")
|
|
86
|
+
parser.add_argument("--ignore_frozen", action="store_true", default=False,
|
|
87
|
+
help="ignore all functions that are frozen(like import)")
|
|
88
|
+
parser.add_argument("--log_exit", action="store_true", default=False,
|
|
89
|
+
help="log functions in exit functions like atexit")
|
|
90
|
+
parser.add_argument("--log_func_retval", action="store_true", default=False,
|
|
91
|
+
help="log return value of the function in the report")
|
|
92
|
+
parser.add_argument("--log_func_with_objprint", action="store_true", default=False,
|
|
93
|
+
help="use objprint for function argument and return value")
|
|
94
|
+
parser.add_argument("--log_print", action="store_true", default=False,
|
|
95
|
+
help="replace all print() function to adding an event to the result")
|
|
96
|
+
parser.add_argument("--log_sparse", action="store_true", default=False,
|
|
97
|
+
help="log only selected functions with @log_sparse")
|
|
98
|
+
parser.add_argument("--log_func_args", action="store_true", default=False,
|
|
99
|
+
help="log all function arguments, this will introduce large overhead")
|
|
100
|
+
parser.add_argument("--log_gc", action="store_true", default=False,
|
|
101
|
+
help="log ref cycle garbage collection operations")
|
|
102
|
+
parser.add_argument("--log_torch", action="store_true", default=False,
|
|
103
|
+
help="log all the supported torch events together with the trace")
|
|
104
|
+
parser.add_argument("--log_var", nargs="*", default=None,
|
|
105
|
+
help="log variable with specified names")
|
|
106
|
+
parser.add_argument("--log_number", nargs="*", default=None,
|
|
107
|
+
help="log variable with specified names as a number(using VizCounter)")
|
|
108
|
+
parser.add_argument("--log_attr", nargs="*", default=None,
|
|
109
|
+
help="log attribute with specified names")
|
|
110
|
+
parser.add_argument("--log_audit", nargs="*", default=None,
|
|
111
|
+
help="log audit when audit event is raised, takes regex")
|
|
112
|
+
parser.add_argument("--log_func_exec", nargs="*", default=None,
|
|
113
|
+
help="log execution of function with specified names")
|
|
114
|
+
parser.add_argument("--log_func_entry", nargs="*", default=None,
|
|
115
|
+
help="log entry of the function with specified names")
|
|
116
|
+
parser.add_argument("--log_exception", action="store_true", default=False,
|
|
117
|
+
help="log all exception when it's raised")
|
|
118
|
+
parser.add_argument("--log_subprocess", action="store_true", default=False,
|
|
119
|
+
help=argparse.SUPPRESS)
|
|
120
|
+
parser.add_argument("--subprocess_child", action="store_true", default=False,
|
|
121
|
+
help=argparse.SUPPRESS)
|
|
122
|
+
parser.add_argument("--dump_raw", action="store_true", default=False,
|
|
123
|
+
help=argparse.SUPPRESS)
|
|
124
|
+
parser.add_argument("--sanitize_function_name", action="store_true", default=False,
|
|
125
|
+
help="Sanitize logged function names to enforce utf-8 encoding")
|
|
126
|
+
parser.add_argument("--log_multiprocess", action="store_true", default=False,
|
|
127
|
+
help=argparse.SUPPRESS)
|
|
128
|
+
parser.add_argument("--log_async", action="store_true", default=False,
|
|
129
|
+
help="log as async format")
|
|
130
|
+
parser.add_argument("--ignore_multiprocess", action="store_true", default=False,
|
|
131
|
+
help="Do not log any process other than the main process")
|
|
132
|
+
parser.add_argument("--magic_comment", action="store_true", default=False,
|
|
133
|
+
help="Process VizTracer specific comments")
|
|
134
|
+
parser.add_argument("--minimize_memory", action="store_true", default=False,
|
|
135
|
+
help="Use json.dump to dump chunks to file to save memory")
|
|
136
|
+
parser.add_argument("--pid_suffix", action="store_true", default=False,
|
|
137
|
+
help=("append pid to file name. "
|
|
138
|
+
"This should be used when you try to trace multi process programs. "
|
|
139
|
+
"Will by default generate json files"))
|
|
140
|
+
parser.add_argument("--module", "-m", nargs="?", default=None,
|
|
141
|
+
help="run module with VizTracer")
|
|
142
|
+
parser.add_argument("--patch_only", action="store_true", default=False,
|
|
143
|
+
help=argparse.SUPPRESS)
|
|
144
|
+
parser.add_argument("--compress", nargs="?", default=None,
|
|
145
|
+
help="Compress a json report to a compact cvf format")
|
|
146
|
+
parser.add_argument("--decompress", nargs="?", default=None,
|
|
147
|
+
help="Decompress a compressed cvf file to a json format")
|
|
148
|
+
parser.add_argument("--combine", nargs="*", default=[],
|
|
149
|
+
help=("combine all json reports to a single report. "
|
|
150
|
+
"Specify all the json reports you want to combine"))
|
|
151
|
+
parser.add_argument("--align_combine", nargs="*", default=[],
|
|
152
|
+
help=("combine all json reports to a single report and align them from the start "
|
|
153
|
+
"Specify all the json reports you want to combine"))
|
|
154
|
+
parser.add_argument("--open", action="store_true", default=False,
|
|
155
|
+
help="open the report in browser after saving")
|
|
156
|
+
parser.add_argument("--attach", type=int, nargs="?", default=-1,
|
|
157
|
+
help="pid of Python process to trace")
|
|
158
|
+
parser.add_argument("--attach_installed", type=int, nargs="?", default=-1,
|
|
159
|
+
help="pid of Python process with VizTracer installed")
|
|
160
|
+
parser.add_argument("--uninstall", type=int, nargs="?", default=-1,
|
|
161
|
+
help="pid of Python process with VizTracer to be uninstalled")
|
|
162
|
+
parser.add_argument("-t", type=float, nargs="?", default=-1,
|
|
163
|
+
help="time you want to trace the process")
|
|
164
|
+
return parser
|
|
165
|
+
|
|
166
|
+
def load_config_file(self, filename: str = ".viztracerrc") -> argparse.Namespace:
|
|
167
|
+
ret = argparse.Namespace()
|
|
168
|
+
if os.path.exists(filename):
|
|
169
|
+
import configparser
|
|
170
|
+
cfg_parser = configparser.ConfigParser()
|
|
171
|
+
cfg_parser.read(filename)
|
|
172
|
+
if "default" not in cfg_parser:
|
|
173
|
+
raise ValueError("Config file does not contain [default] section")
|
|
174
|
+
for action in self.parser._actions:
|
|
175
|
+
if hasattr(action, "dest") and action.dest in cfg_parser["default"]:
|
|
176
|
+
convert = action.type if action.type is not None else str
|
|
177
|
+
if not callable(convert):
|
|
178
|
+
# This only happens when action.type is not None but not a callable
|
|
179
|
+
# This should not happen in normal case
|
|
180
|
+
raise ValueError(f"Invalid action type {action.type}") # pragma: no cover
|
|
181
|
+
if action.nargs == 0:
|
|
182
|
+
setattr(ret, action.dest, action.const)
|
|
183
|
+
elif action.nargs is None or action.nargs == "?":
|
|
184
|
+
if action.type == bool: # pragma: no cover
|
|
185
|
+
# VizTracer does not have any option that belongs to this case
|
|
186
|
+
# store_true/store_false has nargs == 0, this only happens
|
|
187
|
+
# when it's a store, but with type == bool
|
|
188
|
+
setattr(ret, action.dest, cfg_parser["default"].getboolean(action.dest))
|
|
189
|
+
else:
|
|
190
|
+
setattr(ret, action.dest, convert(cfg_parser["default"][action.dest]))
|
|
191
|
+
else:
|
|
192
|
+
setattr(ret, action.dest, [convert(val) for val in cfg_parser["default"][action.dest].strip().split()])
|
|
193
|
+
else:
|
|
194
|
+
if filename != ".viztracerrc":
|
|
195
|
+
# User specified name, raise error
|
|
196
|
+
raise FileNotFoundError(f"{filename} does not exist")
|
|
197
|
+
return ret
|
|
198
|
+
|
|
199
|
+
def parse(self, argv: list[str]) -> VizProcedureResult:
|
|
200
|
+
# If -- or --run exists, all the commands after --/--run are the commands we need to run
|
|
201
|
+
# We need to filter those out, they might conflict with our arguments
|
|
202
|
+
idx: int | None = None
|
|
203
|
+
if "--" in argv[1:]:
|
|
204
|
+
idx = argv.index("--")
|
|
205
|
+
elif "--run" in argv[1:]:
|
|
206
|
+
idx = argv.index("--run")
|
|
207
|
+
|
|
208
|
+
rcfile_parser = argparse.ArgumentParser(add_help=False)
|
|
209
|
+
rcfile_parser.add_argument("--rcfile", nargs="?", default=".viztracerrc")
|
|
210
|
+
rc_options, _ = rcfile_parser.parse_known_args(argv[1:])
|
|
211
|
+
default_namespace = self.load_config_file(rc_options.rcfile)
|
|
212
|
+
|
|
213
|
+
if idx is not None:
|
|
214
|
+
options, command = self.parser.parse_args(argv[1:idx], namespace=default_namespace), argv[idx + 1:]
|
|
215
|
+
self.args = argv[1:idx]
|
|
216
|
+
else:
|
|
217
|
+
options, command = self.parser.parse_known_args(argv[1:], namespace=default_namespace)
|
|
218
|
+
self.args = [elem for elem in argv[1:] if elem not in command]
|
|
219
|
+
|
|
220
|
+
if options.quiet:
|
|
221
|
+
self.verbose = 0
|
|
222
|
+
|
|
223
|
+
if options.unique_output_file:
|
|
224
|
+
exec_name = "python"
|
|
225
|
+
if options.module:
|
|
226
|
+
exec_name = options.module
|
|
227
|
+
elif command:
|
|
228
|
+
exec_name = command[0]
|
|
229
|
+
self.ofile = unique_file_name(exec_name)
|
|
230
|
+
if options.output_file:
|
|
231
|
+
self.ofile = options.output_file
|
|
232
|
+
elif options.pid_suffix:
|
|
233
|
+
self.ofile = "result.json"
|
|
234
|
+
|
|
235
|
+
if options.output_dir:
|
|
236
|
+
if not os.path.exists(options.output_dir):
|
|
237
|
+
os.mkdir(options.output_dir)
|
|
238
|
+
self.ofile = os.path.join(options.output_dir, self.ofile)
|
|
239
|
+
|
|
240
|
+
if options.subprocess_child:
|
|
241
|
+
# If it's a subprocess, we need to store the FEE data to the
|
|
242
|
+
# directory from the parent process.
|
|
243
|
+
# It's not practical to cover this line as it requires coverage
|
|
244
|
+
# instrumentation on subprocess.
|
|
245
|
+
output_file = self.ofile # pragma: no cover
|
|
246
|
+
else:
|
|
247
|
+
output_file = os.path.join(self.multiprocess_output_dir, "result.json")
|
|
248
|
+
|
|
249
|
+
if options.log_multiprocess or options.log_subprocess: # pragma: no cover
|
|
250
|
+
color_print(
|
|
251
|
+
"WARNING",
|
|
252
|
+
"--log_multiprocess and --log_subprocess are no longer needed to trace multi-process program")
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
min_duration = time_str_to_us(options.min_duration)
|
|
256
|
+
except ValueError:
|
|
257
|
+
return False, f"Can't convert {options.min_duration} to time. Format should be 0.3ms or 13us"
|
|
258
|
+
|
|
259
|
+
if options.log_torch:
|
|
260
|
+
try:
|
|
261
|
+
import torch # type: ignore # noqa: F401
|
|
262
|
+
except ImportError:
|
|
263
|
+
return False, "torch is not installed"
|
|
264
|
+
|
|
265
|
+
self.options, self.command = options, command
|
|
266
|
+
self.init_kwargs = {
|
|
267
|
+
"tracer_entries": options.tracer_entries,
|
|
268
|
+
"verbose": 0,
|
|
269
|
+
"output_file": output_file,
|
|
270
|
+
"max_stack_depth": options.max_stack_depth,
|
|
271
|
+
"exclude_files": options.exclude_files,
|
|
272
|
+
"include_files": options.include_files,
|
|
273
|
+
"ignore_c_function": options.ignore_c_function,
|
|
274
|
+
"ignore_frozen": options.ignore_frozen,
|
|
275
|
+
"log_func_retval": options.log_func_retval,
|
|
276
|
+
"log_func_args": options.log_func_args,
|
|
277
|
+
"log_func_with_objprint": options.log_func_with_objprint,
|
|
278
|
+
"log_print": options.log_print,
|
|
279
|
+
"log_gc": options.log_gc,
|
|
280
|
+
"log_sparse": options.log_sparse,
|
|
281
|
+
"log_async": options.log_async,
|
|
282
|
+
"log_audit": options.log_audit,
|
|
283
|
+
"log_torch": options.log_torch,
|
|
284
|
+
"pid_suffix": True,
|
|
285
|
+
"file_info": False,
|
|
286
|
+
"register_global": True,
|
|
287
|
+
"plugins": options.plugins,
|
|
288
|
+
"trace_self": options.trace_self,
|
|
289
|
+
"min_duration": min_duration,
|
|
290
|
+
"sanitize_function_name": options.sanitize_function_name,
|
|
291
|
+
"dump_raw": True,
|
|
292
|
+
"minimize_memory": options.minimize_memory,
|
|
293
|
+
"process_name": None,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return True, None
|
|
297
|
+
|
|
298
|
+
def search_file(self, file_name: str) -> str | None:
|
|
299
|
+
if os.path.isfile(file_name):
|
|
300
|
+
return file_name
|
|
301
|
+
|
|
302
|
+
# search file in $PATH
|
|
303
|
+
if "PATH" in os.environ:
|
|
304
|
+
if sys.platform in ["linux", "linux2", "darwin"]:
|
|
305
|
+
path_sep = ":"
|
|
306
|
+
elif sys.platform in ["win32"]:
|
|
307
|
+
path_sep = ";"
|
|
308
|
+
else: # pragma: no cover
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
for dir_name in os.environ["PATH"].split(path_sep):
|
|
312
|
+
candidate = os.path.join(dir_name, file_name)
|
|
313
|
+
if os.path.isfile(candidate):
|
|
314
|
+
return candidate
|
|
315
|
+
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
def run(self) -> VizProcedureResult:
|
|
319
|
+
if self.options.version:
|
|
320
|
+
return self.show_version()
|
|
321
|
+
elif self.options.attach > 0:
|
|
322
|
+
return self.attach()
|
|
323
|
+
elif self.options.attach_installed > 0:
|
|
324
|
+
return self.attach_installed()
|
|
325
|
+
elif self.options.uninstall > 0:
|
|
326
|
+
return self.uninstall()
|
|
327
|
+
elif self.options.cmd_string is not None:
|
|
328
|
+
return self.run_string()
|
|
329
|
+
elif self.options.module is not None:
|
|
330
|
+
return self.run_module()
|
|
331
|
+
elif self.command:
|
|
332
|
+
return self.run_command()
|
|
333
|
+
elif self.options.compress:
|
|
334
|
+
return self.run_compress()
|
|
335
|
+
elif self.options.decompress:
|
|
336
|
+
return self.run_decompress()
|
|
337
|
+
elif self.options.combine:
|
|
338
|
+
return self.run_combine(files=self.options.combine)
|
|
339
|
+
elif self.options.align_combine:
|
|
340
|
+
return self.run_combine(files=self.options.align_combine, align=True)
|
|
341
|
+
else:
|
|
342
|
+
self.parser.print_help()
|
|
343
|
+
return True, None
|
|
344
|
+
|
|
345
|
+
def run_code(self, code: CodeType | str, global_dict: dict[str, Any]) -> VizProcedureResult:
|
|
346
|
+
options = self.options
|
|
347
|
+
self.parent_pid = os.getpid()
|
|
348
|
+
|
|
349
|
+
if options.subprocess_child:
|
|
350
|
+
if options.cmd_string is not None:
|
|
351
|
+
self.init_kwargs["process_name"] = "python -c"
|
|
352
|
+
else:
|
|
353
|
+
self.init_kwargs["process_name"] = sys.argv[0]
|
|
354
|
+
|
|
355
|
+
tracer = VizTracer(**self.init_kwargs)
|
|
356
|
+
self.tracer = tracer
|
|
357
|
+
|
|
358
|
+
install_all_hooks(tracer,
|
|
359
|
+
self.args,
|
|
360
|
+
patch_multiprocess=not options.ignore_multiprocess)
|
|
361
|
+
|
|
362
|
+
if options.patch_only:
|
|
363
|
+
exec(code, global_dict)
|
|
364
|
+
return True, None
|
|
365
|
+
|
|
366
|
+
def term_handler(signalnum, frame):
|
|
367
|
+
# Exit if we are not already doing exit routine
|
|
368
|
+
if not frame_stack_has_func(frame, (self.exit_routine,
|
|
369
|
+
tracer.exit_routine,
|
|
370
|
+
multiprocessing.util._exit_function)):
|
|
371
|
+
sys.exit(0)
|
|
372
|
+
|
|
373
|
+
signal.signal(signal.SIGTERM, term_handler)
|
|
374
|
+
|
|
375
|
+
if options.subprocess_child:
|
|
376
|
+
tracer.label_file_to_write()
|
|
377
|
+
multiprocessing.util.Finalize(tracer, tracer.exit_routine, exitpriority=-1)
|
|
378
|
+
else:
|
|
379
|
+
multiprocessing.util.Finalize(self, self.exit_routine, exitpriority=-1)
|
|
380
|
+
|
|
381
|
+
if not options.log_sparse:
|
|
382
|
+
tracer.start()
|
|
383
|
+
|
|
384
|
+
exec(code, global_dict)
|
|
385
|
+
|
|
386
|
+
if not options.log_exit:
|
|
387
|
+
tracer.stop(stop_option="flush_as_finish")
|
|
388
|
+
|
|
389
|
+
# issue141 - concurrent.future requires a proper release by executing
|
|
390
|
+
# threading._threading_atexits or it will deadlock if not explicitly
|
|
391
|
+
# release the resource in the code
|
|
392
|
+
# Python 3.9+ has this issue
|
|
393
|
+
if threading._threading_atexits: # type: ignore
|
|
394
|
+
for atexit_call in reversed(threading._threading_atexits): # type: ignore
|
|
395
|
+
atexit_call()
|
|
396
|
+
threading._threading_atexits = [] # type: ignore
|
|
397
|
+
|
|
398
|
+
return True, None
|
|
399
|
+
|
|
400
|
+
def run_module(self) -> VizProcedureResult:
|
|
401
|
+
import runpy
|
|
402
|
+
code = "run_module(modname, run_name='__main__', alter_sys=True)"
|
|
403
|
+
global_dict = {
|
|
404
|
+
"run_module": runpy.run_module,
|
|
405
|
+
"modname": self.options.module,
|
|
406
|
+
}
|
|
407
|
+
sys.argv = [self.options.module] + self.command[:]
|
|
408
|
+
sys.path.insert(0, os.getcwd())
|
|
409
|
+
return self.run_code(code, global_dict)
|
|
410
|
+
|
|
411
|
+
def run_string(self) -> VizProcedureResult:
|
|
412
|
+
cmd_string = self.options.cmd_string
|
|
413
|
+
main_mod = types.ModuleType("__main__")
|
|
414
|
+
setattr(main_mod, "__file__", "<string>")
|
|
415
|
+
setattr(main_mod, "__builtins__", globals()["__builtins__"])
|
|
416
|
+
|
|
417
|
+
# __mp_main__ should be a duplicate of __main__ for pickle
|
|
418
|
+
sys.modules["__main__"] = sys.modules["__mp_main__"] = main_mod
|
|
419
|
+
code = compile(cmd_string, "<string>", "exec")
|
|
420
|
+
sys.argv = ["-c"] + self.command[:]
|
|
421
|
+
return self.run_code(code, main_mod.__dict__)
|
|
422
|
+
|
|
423
|
+
def run_command(self) -> VizProcedureResult:
|
|
424
|
+
command = self.command
|
|
425
|
+
options = self.options
|
|
426
|
+
file_name = command[0]
|
|
427
|
+
search_result = self.search_file(file_name)
|
|
428
|
+
if not search_result:
|
|
429
|
+
return False, f"No such file as {file_name}"
|
|
430
|
+
if file_name.endswith(".json"):
|
|
431
|
+
return False, f"viztracer can't run json file, did you mean \"vizviewer {file_name}\"?"
|
|
432
|
+
file_name = search_result
|
|
433
|
+
|
|
434
|
+
with io.open_code(file_name) as f:
|
|
435
|
+
code_string = f.read()
|
|
436
|
+
if options.magic_comment or options.log_var or options.log_number or options.log_attr or \
|
|
437
|
+
options.log_func_exec or options.log_exception or options.log_func_entry:
|
|
438
|
+
monkey = CodeMonkey(file_name)
|
|
439
|
+
if options.magic_comment:
|
|
440
|
+
monkey.add_source_processor()
|
|
441
|
+
if options.log_var:
|
|
442
|
+
monkey.add_instrument("log_var", {"varnames": options.log_var})
|
|
443
|
+
if options.log_number:
|
|
444
|
+
monkey.add_instrument("log_number", {"varnames": options.log_number})
|
|
445
|
+
if options.log_attr:
|
|
446
|
+
monkey.add_instrument("log_attr", {"varnames": options.log_attr})
|
|
447
|
+
if options.log_func_exec:
|
|
448
|
+
monkey.add_instrument("log_func_exec", {"funcnames": options.log_func_exec})
|
|
449
|
+
if options.log_func_entry:
|
|
450
|
+
monkey.add_instrument("log_func_entry", {"funcnames": options.log_func_entry})
|
|
451
|
+
if options.log_exception:
|
|
452
|
+
monkey.add_instrument("log_exception", {})
|
|
453
|
+
builtins.compile = monkey.compile # type: ignore
|
|
454
|
+
|
|
455
|
+
main_mod = types.ModuleType("__main__")
|
|
456
|
+
setattr(main_mod, "__file__", os.path.abspath(file_name))
|
|
457
|
+
setattr(main_mod, "__builtins__", globals()["__builtins__"])
|
|
458
|
+
|
|
459
|
+
# __mp_main__ should be a duplicate of __main__ for pickle
|
|
460
|
+
sys.modules["__main__"] = sys.modules["__mp_main__"] = main_mod
|
|
461
|
+
code = compile(code_string, os.path.abspath(file_name), "exec")
|
|
462
|
+
sys.path.insert(0, os.path.dirname(file_name))
|
|
463
|
+
sys.argv = command[:]
|
|
464
|
+
return self.run_code(code, main_mod.__dict__)
|
|
465
|
+
|
|
466
|
+
def run_compress(self):
|
|
467
|
+
file_to_compress = self.options.compress
|
|
468
|
+
if not file_to_compress or not os.path.exists(file_to_compress):
|
|
469
|
+
return False, f"Unable to find file {file_to_compress}"
|
|
470
|
+
|
|
471
|
+
if not file_to_compress.endswith(".json"):
|
|
472
|
+
return False, "Only support compressing json report"
|
|
473
|
+
|
|
474
|
+
if not self.options.output_file:
|
|
475
|
+
output_file = "result.cvf"
|
|
476
|
+
else:
|
|
477
|
+
output_file = self.options.output_file
|
|
478
|
+
|
|
479
|
+
from viztracer.vcompressor import VCompressor
|
|
480
|
+
|
|
481
|
+
compressor = VCompressor()
|
|
482
|
+
|
|
483
|
+
with open(file_to_compress) as f:
|
|
484
|
+
data = json.load(f)
|
|
485
|
+
compressor.compress(data, output_file)
|
|
486
|
+
|
|
487
|
+
return True, None
|
|
488
|
+
|
|
489
|
+
def run_decompress(self):
|
|
490
|
+
file_to_decompress = self.options.decompress
|
|
491
|
+
if not file_to_decompress or not os.path.exists(file_to_decompress):
|
|
492
|
+
return False, f"Unable to find file {file_to_decompress}"
|
|
493
|
+
|
|
494
|
+
if not self.options.output_file:
|
|
495
|
+
output_file = "result.json"
|
|
496
|
+
else:
|
|
497
|
+
output_file = self.options.output_file
|
|
498
|
+
|
|
499
|
+
from viztracer.vcompressor import VCompressor
|
|
500
|
+
|
|
501
|
+
compressor = VCompressor()
|
|
502
|
+
|
|
503
|
+
data = compressor.decompress(file_to_decompress)
|
|
504
|
+
|
|
505
|
+
with open(output_file, "w") as f:
|
|
506
|
+
json.dump(data, f)
|
|
507
|
+
|
|
508
|
+
return True, None
|
|
509
|
+
|
|
510
|
+
def run_combine(self, files: list[str], align: bool = False) -> VizProcedureResult:
|
|
511
|
+
options = self.options
|
|
512
|
+
builder = ReportBuilder(files, align=align, minimize_memory=options.minimize_memory)
|
|
513
|
+
if options.output_file:
|
|
514
|
+
ofile = options.output_file
|
|
515
|
+
else:
|
|
516
|
+
ofile = "result.json"
|
|
517
|
+
builder.save(output_file=ofile)
|
|
518
|
+
|
|
519
|
+
return True, None
|
|
520
|
+
|
|
521
|
+
def show_version(self) -> VizProcedureResult:
|
|
522
|
+
print(__version__)
|
|
523
|
+
return True, None
|
|
524
|
+
|
|
525
|
+
def _check_attach_availability(self) -> tuple[bool, str | None]:
|
|
526
|
+
if sys.platform == "win32":
|
|
527
|
+
return False, "VizTracer does not support this feature on Windows"
|
|
528
|
+
|
|
529
|
+
if sys.platform == "darwin" and "arm" in platform.processor():
|
|
530
|
+
return False, "VizTracer does not support this feature on Apple Silicon"
|
|
531
|
+
|
|
532
|
+
if sys.platform == "darwin" and sys.version_info >= (3, 11):
|
|
533
|
+
color_print("WARNING", "Warning: attach may not work on 3.11+ on Mac due to hardened runtime")
|
|
534
|
+
|
|
535
|
+
return True, None
|
|
536
|
+
|
|
537
|
+
def attach(self) -> VizProcedureResult:
|
|
538
|
+
from .attach_process.add_code_to_python_process import run_python_code # type: ignore
|
|
539
|
+
|
|
540
|
+
pid = self.options.attach
|
|
541
|
+
interval = self.options.t
|
|
542
|
+
|
|
543
|
+
success, err_msg = self._check_attach_availability()
|
|
544
|
+
|
|
545
|
+
if not success:
|
|
546
|
+
return False, err_msg
|
|
547
|
+
|
|
548
|
+
if not pid_exists(pid):
|
|
549
|
+
return False, f"pid {pid} does not exist!"
|
|
550
|
+
|
|
551
|
+
# If we are doing attach, we need to clean init_kwargs first
|
|
552
|
+
self.init_kwargs.update({
|
|
553
|
+
"output_file": os.path.abspath(self.ofile),
|
|
554
|
+
"pid_suffix": False,
|
|
555
|
+
"file_info": True,
|
|
556
|
+
"register_global": True,
|
|
557
|
+
"dump_raw": False,
|
|
558
|
+
"verbose": 1 if self.verbose != 0 else 0,
|
|
559
|
+
})
|
|
560
|
+
b64s = base64.urlsafe_b64encode(json.dumps(self.init_kwargs).encode("ascii")).decode("ascii")
|
|
561
|
+
start_code = f"import viztracer.attach; viztracer.attach.start_attach(\\\"{b64s}\\\")"
|
|
562
|
+
stop_code = "viztracer.attach.stop_attach()"
|
|
563
|
+
retcode, _, _, = run_python_code(pid, start_code)
|
|
564
|
+
if retcode != 0: # pragma: no cover
|
|
565
|
+
return False, f"Failed to inject code [err {retcode}]"
|
|
566
|
+
|
|
567
|
+
self._wait_attach(interval)
|
|
568
|
+
|
|
569
|
+
retcode, _, _ = run_python_code(pid, stop_code)
|
|
570
|
+
if retcode != 0: # pragma: no cover
|
|
571
|
+
return False, f"Failed to inject code [err {retcode}]"
|
|
572
|
+
|
|
573
|
+
print("Use the following command to open the report:")
|
|
574
|
+
color_print("OKGREEN", f"vizviewer {self.init_kwargs['output_file']}")
|
|
575
|
+
|
|
576
|
+
return True, None
|
|
577
|
+
|
|
578
|
+
def uninstall(self) -> VizProcedureResult:
|
|
579
|
+
from .attach_process.add_code_to_python_process import run_python_code # type: ignore
|
|
580
|
+
|
|
581
|
+
pid = self.options.uninstall
|
|
582
|
+
|
|
583
|
+
success, err_msg = self._check_attach_availability()
|
|
584
|
+
|
|
585
|
+
if not success:
|
|
586
|
+
return False, err_msg
|
|
587
|
+
|
|
588
|
+
if not pid_exists(pid):
|
|
589
|
+
return False, f"pid {pid} does not exist!"
|
|
590
|
+
|
|
591
|
+
stop_code = "import viztracer.attach; viztracer.attach.uninstall_attach()"
|
|
592
|
+
|
|
593
|
+
retcode, _, _ = run_python_code(pid, stop_code)
|
|
594
|
+
if retcode != 0: # pragma: no cover
|
|
595
|
+
return False, f"Failed to inject code [err {retcode}]"
|
|
596
|
+
|
|
597
|
+
return True, None
|
|
598
|
+
|
|
599
|
+
def attach_installed(self) -> VizProcedureResult:
|
|
600
|
+
success, err_msg = self._check_attach_availability()
|
|
601
|
+
|
|
602
|
+
if not success:
|
|
603
|
+
return False, err_msg
|
|
604
|
+
|
|
605
|
+
pid = self.options.attach_installed
|
|
606
|
+
interval = self.options.t
|
|
607
|
+
try:
|
|
608
|
+
os.kill(pid, signal.SIGUSR1)
|
|
609
|
+
except OSError:
|
|
610
|
+
return False, f"pid {pid} does not exist"
|
|
611
|
+
|
|
612
|
+
self._wait_attach(interval)
|
|
613
|
+
|
|
614
|
+
try:
|
|
615
|
+
os.kill(pid, signal.SIGUSR2)
|
|
616
|
+
except OSError: # pragma: no cover
|
|
617
|
+
return False, f"pid {pid} already finished"
|
|
618
|
+
|
|
619
|
+
return True, None
|
|
620
|
+
|
|
621
|
+
def _wait_attach(self, interval: float) -> None:
|
|
622
|
+
# interval == 0 means waiting for CTRL+C
|
|
623
|
+
try:
|
|
624
|
+
if interval > 0:
|
|
625
|
+
print(f"Attach success, collect trace after {interval}s", flush=True)
|
|
626
|
+
time.sleep(interval)
|
|
627
|
+
else:
|
|
628
|
+
print("Attach success, press CTRL+C to stop and save report", flush=True)
|
|
629
|
+
while True:
|
|
630
|
+
time.sleep(0.1)
|
|
631
|
+
except KeyboardInterrupt:
|
|
632
|
+
pass
|
|
633
|
+
|
|
634
|
+
def save(self) -> None:
|
|
635
|
+
# This function will only be called from main process
|
|
636
|
+
options = self.options
|
|
637
|
+
ofile = self.ofile
|
|
638
|
+
if options.pid_suffix:
|
|
639
|
+
prefix, suffix = os.path.splitext(self.ofile)
|
|
640
|
+
prefix_pid = f"{prefix}_{os.getpid()}"
|
|
641
|
+
ofile = prefix_pid + suffix
|
|
642
|
+
else:
|
|
643
|
+
ofile = self.ofile
|
|
644
|
+
|
|
645
|
+
self.wait_children_finish()
|
|
646
|
+
builder = ReportBuilder(
|
|
647
|
+
[os.path.join(self.multiprocess_output_dir, f)
|
|
648
|
+
for f in os.listdir(self.multiprocess_output_dir) if f.endswith(".json")],
|
|
649
|
+
minimize_memory=options.minimize_memory,
|
|
650
|
+
verbose=self.verbose)
|
|
651
|
+
builder.save(output_file=ofile)
|
|
652
|
+
shutil.rmtree(self.multiprocess_output_dir)
|
|
653
|
+
|
|
654
|
+
def wait_children_finish(self) -> None:
|
|
655
|
+
try:
|
|
656
|
+
if any((f.endswith(".viztmp") for f in os.listdir(self.multiprocess_output_dir))):
|
|
657
|
+
same_line_print("Wait for child processes to finish, Ctrl+C to skip")
|
|
658
|
+
while True:
|
|
659
|
+
remain_viztmp = [f for f in os.listdir(self.multiprocess_output_dir) if f.endswith(".viztmp")]
|
|
660
|
+
for viztmp_file in remain_viztmp:
|
|
661
|
+
match = re.search(r'result_(\d+).json.viztmp', viztmp_file)
|
|
662
|
+
if match:
|
|
663
|
+
pid = int(match.group(1))
|
|
664
|
+
if pid_exists(pid):
|
|
665
|
+
break
|
|
666
|
+
else: # pragma: no cover
|
|
667
|
+
color_print("WARNING", f"Unknown viztmp file {viztmp_file}")
|
|
668
|
+
else:
|
|
669
|
+
break
|
|
670
|
+
time.sleep(0.5)
|
|
671
|
+
except KeyboardInterrupt:
|
|
672
|
+
pass
|
|
673
|
+
|
|
674
|
+
def exit_routine(self) -> None:
|
|
675
|
+
if self.tracer is not None:
|
|
676
|
+
if not self._exiting:
|
|
677
|
+
self._exiting = True
|
|
678
|
+
if self.verbose > 0:
|
|
679
|
+
same_line_print("Saving trace data, this could take a while")
|
|
680
|
+
self.tracer.exit_routine()
|
|
681
|
+
self.save()
|
|
682
|
+
if self.options.open: # pragma: no cover
|
|
683
|
+
import subprocess
|
|
684
|
+
subprocess.run([sys.executable, "-m", "viztracer.viewer", "--once", os.path.abspath(self.ofile)])
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def main():
|
|
688
|
+
ui = VizUI()
|
|
689
|
+
success, err_msg = ui.parse(sys.argv)
|
|
690
|
+
if not success:
|
|
691
|
+
print(err_msg)
|
|
692
|
+
sys.exit(1)
|
|
693
|
+
try:
|
|
694
|
+
success, err_msg = ui.run()
|
|
695
|
+
if not success:
|
|
696
|
+
print(err_msg)
|
|
697
|
+
sys.exit(1)
|
|
698
|
+
finally:
|
|
699
|
+
atexit._run_exitfuncs()
|