viztracer 1.1.0__cp314-cp314-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.

Files changed (109) hide show
  1. viztracer/__init__.py +19 -0
  2. viztracer/__main__.py +8 -0
  3. viztracer/attach.py +67 -0
  4. viztracer/attach_process/LICENSE +203 -0
  5. viztracer/attach_process/__init__.py +0 -0
  6. viztracer/attach_process/add_code_to_python_process.py +582 -0
  7. viztracer/attach_process/attach_x86.dll +0 -0
  8. viztracer/attach_process/inject_dll_amd64.exe +0 -0
  9. viztracer/attach_process/linux_and_mac/lldb_prepare.py +54 -0
  10. viztracer/attach_process/run_code_on_dllmain_amd64.dll +0 -0
  11. viztracer/attach_process/run_code_on_dllmain_x86.dll +0 -0
  12. viztracer/cellmagic.py +70 -0
  13. viztracer/code_monkey.py +353 -0
  14. viztracer/decorator.py +164 -0
  15. viztracer/event_base.py +81 -0
  16. viztracer/functree.py +135 -0
  17. viztracer/html/flamegraph.html +34 -0
  18. viztracer/html/trace_viewer_embedder.html +203 -0
  19. viztracer/html/trace_viewer_full.html +10207 -0
  20. viztracer/main.py +699 -0
  21. viztracer/modules/eventnode.c +172 -0
  22. viztracer/modules/eventnode.h +73 -0
  23. viztracer/modules/pythoncapi_compat.h +1726 -0
  24. viztracer/modules/quicktime.c +177 -0
  25. viztracer/modules/quicktime.h +104 -0
  26. viztracer/modules/snaptrace.c +2205 -0
  27. viztracer/modules/snaptrace.h +134 -0
  28. viztracer/modules/snaptrace_member.c +483 -0
  29. viztracer/modules/util.c +45 -0
  30. viztracer/modules/util.h +22 -0
  31. viztracer/modules/vcompressor/vc_dump.c +1131 -0
  32. viztracer/modules/vcompressor/vc_dump.h +49 -0
  33. viztracer/modules/vcompressor/vcompressor.c +396 -0
  34. viztracer/modules/vcompressor/vcompressor.h +15 -0
  35. viztracer/patch.py +307 -0
  36. viztracer/report_builder.py +311 -0
  37. viztracer/snaptrace.cp314-win_amd64.pyd +0 -0
  38. viztracer/snaptrace.pyi +77 -0
  39. viztracer/util.py +196 -0
  40. viztracer/vcompressor.cp314-win_amd64.pyd +0 -0
  41. viztracer/vcompressor.pyi +10 -0
  42. viztracer/viewer.py +528 -0
  43. viztracer/vizcounter.py +20 -0
  44. viztracer/vizevent.py +31 -0
  45. viztracer/vizlogging.py +20 -0
  46. viztracer/vizobject.py +28 -0
  47. viztracer/vizplugin.py +143 -0
  48. viztracer/viztracer.py +472 -0
  49. viztracer/web_dist/LICENSE +189 -0
  50. viztracer/web_dist/index.html +127 -0
  51. viztracer/web_dist/service_worker.js +279 -0
  52. viztracer/web_dist/trace_processor +300 -0
  53. viztracer/web_dist/v52.0-6b9586def/assets/MaterialSymbolsOutlined.woff2 +0 -0
  54. viztracer/web_dist/v52.0-6b9586def/assets/Roboto-100.woff2 +0 -0
  55. viztracer/web_dist/v52.0-6b9586def/assets/Roboto-300.woff2 +0 -0
  56. viztracer/web_dist/v52.0-6b9586def/assets/Roboto-400.woff2 +0 -0
  57. viztracer/web_dist/v52.0-6b9586def/assets/Roboto-500.woff2 +0 -0
  58. viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Light.woff2 +0 -0
  59. viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Regular.woff2 +0 -0
  60. viztracer/web_dist/v52.0-6b9586def/assets/RobotoMono-Regular.woff2 +0 -0
  61. viztracer/web_dist/v52.0-6b9586def/assets/brand.png +0 -0
  62. viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.html +3946 -0
  63. viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.js +7539 -0
  64. viztracer/web_dist/v52.0-6b9586def/assets/favicon.png +0 -0
  65. viztracer/web_dist/v52.0-6b9586def/assets/logo-128.png +0 -0
  66. viztracer/web_dist/v52.0-6b9586def/assets/logo-3d.png +0 -0
  67. viztracer/web_dist/v52.0-6b9586def/assets/rec_atrace.png +0 -0
  68. viztracer/web_dist/v52.0-6b9586def/assets/rec_battery_counters.png +0 -0
  69. viztracer/web_dist/v52.0-6b9586def/assets/rec_board_voltage.png +0 -0
  70. viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_coarse.png +0 -0
  71. viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_fine.png +0 -0
  72. viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_freq.png +0 -0
  73. viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_voltage.png +0 -0
  74. viztracer/web_dist/v52.0-6b9586def/assets/rec_frame_timeline.png +0 -0
  75. viztracer/web_dist/v52.0-6b9586def/assets/rec_ftrace.png +0 -0
  76. viztracer/web_dist/v52.0-6b9586def/assets/rec_gpu_mem_total.png +0 -0
  77. viztracer/web_dist/v52.0-6b9586def/assets/rec_java_heap_dump.png +0 -0
  78. viztracer/web_dist/v52.0-6b9586def/assets/rec_lmk.png +0 -0
  79. viztracer/web_dist/v52.0-6b9586def/assets/rec_logcat.png +0 -0
  80. viztracer/web_dist/v52.0-6b9586def/assets/rec_long_trace.png +0 -0
  81. viztracer/web_dist/v52.0-6b9586def/assets/rec_mem_hifreq.png +0 -0
  82. viztracer/web_dist/v52.0-6b9586def/assets/rec_meminfo.png +0 -0
  83. viztracer/web_dist/v52.0-6b9586def/assets/rec_native_heap_profiler.png +0 -0
  84. viztracer/web_dist/v52.0-6b9586def/assets/rec_one_shot.png +0 -0
  85. viztracer/web_dist/v52.0-6b9586def/assets/rec_profiling.png +0 -0
  86. viztracer/web_dist/v52.0-6b9586def/assets/rec_ps_stats.png +0 -0
  87. viztracer/web_dist/v52.0-6b9586def/assets/rec_ring_buf.png +0 -0
  88. viztracer/web_dist/v52.0-6b9586def/assets/rec_syscalls.png +0 -0
  89. viztracer/web_dist/v52.0-6b9586def/assets/rec_vmstat.png +0 -0
  90. viztracer/web_dist/v52.0-6b9586def/assets/scheduling_latency.png +0 -0
  91. viztracer/web_dist/v52.0-6b9586def/assets/vscode-icon.png +0 -0
  92. viztracer/web_dist/v52.0-6b9586def/engine_bundle.js +3 -0
  93. viztracer/web_dist/v52.0-6b9586def/frontend_bundle.js +5495 -0
  94. viztracer/web_dist/v52.0-6b9586def/index.html +127 -0
  95. viztracer/web_dist/v52.0-6b9586def/manifest.json +52 -0
  96. viztracer/web_dist/v52.0-6b9586def/perfetto.css +5737 -0
  97. viztracer/web_dist/v52.0-6b9586def/stdlib_docs.json +1 -0
  98. viztracer/web_dist/v52.0-6b9586def/trace_config_utils.wasm +0 -0
  99. viztracer/web_dist/v52.0-6b9586def/trace_processor.wasm +0 -0
  100. viztracer/web_dist/v52.0-6b9586def/trace_processor_memory64.wasm +0 -0
  101. viztracer/web_dist/v52.0-6b9586def/traceconv.wasm +0 -0
  102. viztracer/web_dist/v52.0-6b9586def/traceconv_bundle.js +2 -0
  103. viztracer-1.1.0.dist-info/METADATA +316 -0
  104. viztracer-1.1.0.dist-info/RECORD +109 -0
  105. viztracer-1.1.0.dist-info/WHEEL +5 -0
  106. viztracer-1.1.0.dist-info/entry_points.txt +3 -0
  107. viztracer-1.1.0.dist-info/licenses/LICENSE +222 -0
  108. viztracer-1.1.0.dist-info/licenses/NOTICE.txt +27 -0
  109. 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()