viztracer 1.1.1__cp313-cp313-win32.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.
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 +701 -0
  21. viztracer/modules/eventnode.c +188 -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 +2207 -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 +317 -0
  36. viztracer/report_builder.py +311 -0
  37. viztracer/snaptrace.cp313-win32.pyd +0 -0
  38. viztracer/snaptrace.pyi +77 -0
  39. viztracer/util.py +196 -0
  40. viztracer/vcompressor.cp313-win32.pyd +0 -0
  41. viztracer/vcompressor.pyi +10 -0
  42. viztracer/viewer.py +529 -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.1.dist-info/METADATA +326 -0
  104. viztracer-1.1.1.dist-info/RECORD +109 -0
  105. viztracer-1.1.1.dist-info/WHEEL +5 -0
  106. viztracer-1.1.1.dist-info/entry_points.txt +3 -0
  107. viztracer-1.1.1.dist-info/licenses/LICENSE +222 -0
  108. viztracer-1.1.1.dist-info/licenses/NOTICE.txt +27 -0
  109. viztracer-1.1.1.dist-info/top_level.txt +1 -0
viztracer/main.py ADDED
@@ -0,0 +1,701 @@
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
+ if not options.compress and not options.output_file.endswith((".json", ".html", ".gz")):
232
+ return False, "Only html, json and gz are supported"
233
+ self.ofile = options.output_file
234
+ elif options.pid_suffix:
235
+ self.ofile = "result.json"
236
+
237
+ if options.output_dir:
238
+ if not os.path.exists(options.output_dir):
239
+ os.mkdir(options.output_dir)
240
+ self.ofile = os.path.join(options.output_dir, self.ofile)
241
+
242
+ if options.subprocess_child:
243
+ # If it's a subprocess, we need to store the FEE data to the
244
+ # directory from the parent process.
245
+ # It's not practical to cover this line as it requires coverage
246
+ # instrumentation on subprocess.
247
+ output_file = self.ofile # pragma: no cover
248
+ else:
249
+ output_file = os.path.join(self.multiprocess_output_dir, "result.json")
250
+
251
+ if options.log_multiprocess or options.log_subprocess: # pragma: no cover
252
+ color_print(
253
+ "WARNING",
254
+ "--log_multiprocess and --log_subprocess are no longer needed to trace multi-process program")
255
+
256
+ try:
257
+ min_duration = time_str_to_us(options.min_duration)
258
+ except ValueError:
259
+ return False, f"Can't convert {options.min_duration} to time. Format should be 0.3ms or 13us"
260
+
261
+ if options.log_torch:
262
+ try:
263
+ import torch # type: ignore # noqa: F401
264
+ except ImportError:
265
+ return False, "torch is not installed"
266
+
267
+ self.options, self.command = options, command
268
+ self.init_kwargs = {
269
+ "tracer_entries": options.tracer_entries,
270
+ "verbose": 0,
271
+ "output_file": output_file,
272
+ "max_stack_depth": options.max_stack_depth,
273
+ "exclude_files": options.exclude_files,
274
+ "include_files": options.include_files,
275
+ "ignore_c_function": options.ignore_c_function,
276
+ "ignore_frozen": options.ignore_frozen,
277
+ "log_func_retval": options.log_func_retval,
278
+ "log_func_args": options.log_func_args,
279
+ "log_func_with_objprint": options.log_func_with_objprint,
280
+ "log_print": options.log_print,
281
+ "log_gc": options.log_gc,
282
+ "log_sparse": options.log_sparse,
283
+ "log_async": options.log_async,
284
+ "log_audit": options.log_audit,
285
+ "log_torch": options.log_torch,
286
+ "pid_suffix": True,
287
+ "file_info": False,
288
+ "register_global": True,
289
+ "plugins": options.plugins,
290
+ "trace_self": options.trace_self,
291
+ "min_duration": min_duration,
292
+ "sanitize_function_name": options.sanitize_function_name,
293
+ "dump_raw": True,
294
+ "minimize_memory": options.minimize_memory,
295
+ "process_name": None,
296
+ }
297
+
298
+ return True, None
299
+
300
+ def search_file(self, file_name: str) -> str | None:
301
+ if os.path.isfile(file_name):
302
+ return file_name
303
+
304
+ # search file in $PATH
305
+ if "PATH" in os.environ:
306
+ if sys.platform in ["linux", "linux2", "darwin"]:
307
+ path_sep = ":"
308
+ elif sys.platform in ["win32"]:
309
+ path_sep = ";"
310
+ else: # pragma: no cover
311
+ return None
312
+
313
+ for dir_name in os.environ["PATH"].split(path_sep):
314
+ candidate = os.path.join(dir_name, file_name)
315
+ if os.path.isfile(candidate):
316
+ return candidate
317
+
318
+ return None
319
+
320
+ def run(self) -> VizProcedureResult:
321
+ if self.options.version:
322
+ return self.show_version()
323
+ elif self.options.attach > 0:
324
+ return self.attach()
325
+ elif self.options.attach_installed > 0:
326
+ return self.attach_installed()
327
+ elif self.options.uninstall > 0:
328
+ return self.uninstall()
329
+ elif self.options.cmd_string is not None:
330
+ return self.run_string()
331
+ elif self.options.module is not None:
332
+ return self.run_module()
333
+ elif self.command:
334
+ return self.run_command()
335
+ elif self.options.compress:
336
+ return self.run_compress()
337
+ elif self.options.decompress:
338
+ return self.run_decompress()
339
+ elif self.options.combine:
340
+ return self.run_combine(files=self.options.combine)
341
+ elif self.options.align_combine:
342
+ return self.run_combine(files=self.options.align_combine, align=True)
343
+ else:
344
+ self.parser.print_help()
345
+ return True, None
346
+
347
+ def run_code(self, code: CodeType | str, global_dict: dict[str, Any]) -> VizProcedureResult:
348
+ options = self.options
349
+ self.parent_pid = os.getpid()
350
+
351
+ if options.subprocess_child:
352
+ if options.cmd_string is not None:
353
+ self.init_kwargs["process_name"] = "python -c"
354
+ else:
355
+ self.init_kwargs["process_name"] = sys.argv[0]
356
+
357
+ tracer = VizTracer(**self.init_kwargs)
358
+ self.tracer = tracer
359
+
360
+ install_all_hooks(tracer,
361
+ self.args,
362
+ patch_multiprocess=not options.ignore_multiprocess)
363
+
364
+ if options.patch_only:
365
+ exec(code, global_dict)
366
+ return True, None
367
+
368
+ def term_handler(signalnum, frame):
369
+ # Exit if we are not already doing exit routine
370
+ if not frame_stack_has_func(frame, (self.exit_routine,
371
+ tracer.exit_routine,
372
+ multiprocessing.util._exit_function)):
373
+ sys.exit(0)
374
+
375
+ signal.signal(signal.SIGTERM, term_handler)
376
+
377
+ if options.subprocess_child:
378
+ tracer.label_file_to_write()
379
+ multiprocessing.util.Finalize(tracer, tracer.exit_routine, exitpriority=-1)
380
+ else:
381
+ multiprocessing.util.Finalize(self, self.exit_routine, exitpriority=-1)
382
+
383
+ if not options.log_sparse:
384
+ tracer.start()
385
+
386
+ exec(code, global_dict)
387
+
388
+ if not options.log_exit:
389
+ tracer.stop(stop_option="flush_as_finish")
390
+
391
+ # issue141 - concurrent.future requires a proper release by executing
392
+ # threading._threading_atexits or it will deadlock if not explicitly
393
+ # release the resource in the code
394
+ # Python 3.9+ has this issue
395
+ if threading._threading_atexits: # type: ignore
396
+ for atexit_call in reversed(threading._threading_atexits): # type: ignore
397
+ atexit_call()
398
+ threading._threading_atexits = [] # type: ignore
399
+
400
+ return True, None
401
+
402
+ def run_module(self) -> VizProcedureResult:
403
+ import runpy
404
+ code = "run_module(modname, run_name='__main__', alter_sys=True)"
405
+ global_dict = {
406
+ "run_module": runpy.run_module,
407
+ "modname": self.options.module,
408
+ }
409
+ sys.argv = [self.options.module] + self.command[:]
410
+ sys.path.insert(0, os.getcwd())
411
+ return self.run_code(code, global_dict)
412
+
413
+ def run_string(self) -> VizProcedureResult:
414
+ cmd_string = self.options.cmd_string
415
+ main_mod = types.ModuleType("__main__")
416
+ setattr(main_mod, "__file__", "<string>")
417
+ setattr(main_mod, "__builtins__", globals()["__builtins__"])
418
+
419
+ # __mp_main__ should be a duplicate of __main__ for pickle
420
+ sys.modules["__main__"] = sys.modules["__mp_main__"] = main_mod
421
+ code = compile(cmd_string, "<string>", "exec")
422
+ sys.argv = ["-c"] + self.command[:]
423
+ return self.run_code(code, main_mod.__dict__)
424
+
425
+ def run_command(self) -> VizProcedureResult:
426
+ command = self.command
427
+ options = self.options
428
+ file_name = command[0]
429
+ search_result = self.search_file(file_name)
430
+ if not search_result:
431
+ return False, f"No such file as {file_name}"
432
+ if file_name.endswith(".json"):
433
+ return False, f"viztracer can't run json file, did you mean \"vizviewer {file_name}\"?"
434
+ file_name = search_result
435
+
436
+ with io.open_code(file_name) as f:
437
+ code_string = f.read()
438
+ if options.magic_comment or options.log_var or options.log_number or options.log_attr or \
439
+ options.log_func_exec or options.log_exception or options.log_func_entry:
440
+ monkey = CodeMonkey(file_name)
441
+ if options.magic_comment:
442
+ monkey.add_source_processor()
443
+ if options.log_var:
444
+ monkey.add_instrument("log_var", {"varnames": options.log_var})
445
+ if options.log_number:
446
+ monkey.add_instrument("log_number", {"varnames": options.log_number})
447
+ if options.log_attr:
448
+ monkey.add_instrument("log_attr", {"varnames": options.log_attr})
449
+ if options.log_func_exec:
450
+ monkey.add_instrument("log_func_exec", {"funcnames": options.log_func_exec})
451
+ if options.log_func_entry:
452
+ monkey.add_instrument("log_func_entry", {"funcnames": options.log_func_entry})
453
+ if options.log_exception:
454
+ monkey.add_instrument("log_exception", {})
455
+ builtins.compile = monkey.compile # type: ignore
456
+
457
+ main_mod = types.ModuleType("__main__")
458
+ setattr(main_mod, "__file__", os.path.abspath(file_name))
459
+ setattr(main_mod, "__builtins__", globals()["__builtins__"])
460
+
461
+ # __mp_main__ should be a duplicate of __main__ for pickle
462
+ sys.modules["__main__"] = sys.modules["__mp_main__"] = main_mod
463
+ code = compile(code_string, os.path.abspath(file_name), "exec")
464
+ sys.path.insert(0, os.path.dirname(file_name))
465
+ sys.argv = command[:]
466
+ return self.run_code(code, main_mod.__dict__)
467
+
468
+ def run_compress(self):
469
+ file_to_compress = self.options.compress
470
+ if not file_to_compress or not os.path.exists(file_to_compress):
471
+ return False, f"Unable to find file {file_to_compress}"
472
+
473
+ if not file_to_compress.endswith(".json"):
474
+ return False, "Only support compressing json report"
475
+
476
+ if not self.options.output_file:
477
+ output_file = "result.cvf"
478
+ else:
479
+ output_file = self.options.output_file
480
+
481
+ from viztracer.vcompressor import VCompressor
482
+
483
+ compressor = VCompressor()
484
+
485
+ with open(file_to_compress) as f:
486
+ data = json.load(f)
487
+ compressor.compress(data, output_file)
488
+
489
+ return True, None
490
+
491
+ def run_decompress(self):
492
+ file_to_decompress = self.options.decompress
493
+ if not file_to_decompress or not os.path.exists(file_to_decompress):
494
+ return False, f"Unable to find file {file_to_decompress}"
495
+
496
+ if not self.options.output_file:
497
+ output_file = "result.json"
498
+ else:
499
+ output_file = self.options.output_file
500
+
501
+ from viztracer.vcompressor import VCompressor
502
+
503
+ compressor = VCompressor()
504
+
505
+ data = compressor.decompress(file_to_decompress)
506
+
507
+ with open(output_file, "w") as f:
508
+ json.dump(data, f)
509
+
510
+ return True, None
511
+
512
+ def run_combine(self, files: list[str], align: bool = False) -> VizProcedureResult:
513
+ options = self.options
514
+ builder = ReportBuilder(files, align=align, minimize_memory=options.minimize_memory)
515
+ if options.output_file:
516
+ ofile = options.output_file
517
+ else:
518
+ ofile = "result.json"
519
+ builder.save(output_file=ofile)
520
+
521
+ return True, None
522
+
523
+ def show_version(self) -> VizProcedureResult:
524
+ print(__version__)
525
+ return True, None
526
+
527
+ def _check_attach_availability(self) -> tuple[bool, str | None]:
528
+ if sys.platform == "win32":
529
+ return False, "VizTracer does not support this feature on Windows"
530
+
531
+ if sys.platform == "darwin" and "arm" in platform.processor():
532
+ return False, "VizTracer does not support this feature on Apple Silicon"
533
+
534
+ if sys.platform == "darwin" and sys.version_info >= (3, 11):
535
+ color_print("WARNING", "Warning: attach may not work on 3.11+ on Mac due to hardened runtime")
536
+
537
+ return True, None
538
+
539
+ def attach(self) -> VizProcedureResult:
540
+ from .attach_process.add_code_to_python_process import run_python_code # type: ignore
541
+
542
+ pid = self.options.attach
543
+ interval = self.options.t
544
+
545
+ success, err_msg = self._check_attach_availability()
546
+
547
+ if not success:
548
+ return False, err_msg
549
+
550
+ if not pid_exists(pid):
551
+ return False, f"pid {pid} does not exist!"
552
+
553
+ # If we are doing attach, we need to clean init_kwargs first
554
+ self.init_kwargs.update({
555
+ "output_file": os.path.abspath(self.ofile),
556
+ "pid_suffix": False,
557
+ "file_info": True,
558
+ "register_global": True,
559
+ "dump_raw": False,
560
+ "verbose": 1 if self.verbose != 0 else 0,
561
+ })
562
+ b64s = base64.urlsafe_b64encode(json.dumps(self.init_kwargs).encode("ascii")).decode("ascii")
563
+ start_code = f"import viztracer.attach; viztracer.attach.start_attach(\\\"{b64s}\\\")"
564
+ stop_code = "viztracer.attach.stop_attach()"
565
+ retcode, _, _, = run_python_code(pid, start_code)
566
+ if retcode != 0: # pragma: no cover
567
+ return False, f"Failed to inject code [err {retcode}]"
568
+
569
+ self._wait_attach(interval)
570
+
571
+ retcode, _, _ = run_python_code(pid, stop_code)
572
+ if retcode != 0: # pragma: no cover
573
+ return False, f"Failed to inject code [err {retcode}]"
574
+
575
+ print("Use the following command to open the report:")
576
+ color_print("OKGREEN", f"vizviewer {self.init_kwargs['output_file']}")
577
+
578
+ return True, None
579
+
580
+ def uninstall(self) -> VizProcedureResult:
581
+ from .attach_process.add_code_to_python_process import run_python_code # type: ignore
582
+
583
+ pid = self.options.uninstall
584
+
585
+ success, err_msg = self._check_attach_availability()
586
+
587
+ if not success:
588
+ return False, err_msg
589
+
590
+ if not pid_exists(pid):
591
+ return False, f"pid {pid} does not exist!"
592
+
593
+ stop_code = "import viztracer.attach; viztracer.attach.uninstall_attach()"
594
+
595
+ retcode, _, _ = run_python_code(pid, stop_code)
596
+ if retcode != 0: # pragma: no cover
597
+ return False, f"Failed to inject code [err {retcode}]"
598
+
599
+ return True, None
600
+
601
+ def attach_installed(self) -> VizProcedureResult:
602
+ success, err_msg = self._check_attach_availability()
603
+
604
+ if not success:
605
+ return False, err_msg
606
+
607
+ pid = self.options.attach_installed
608
+ interval = self.options.t
609
+ try:
610
+ os.kill(pid, signal.SIGUSR1)
611
+ except OSError:
612
+ return False, f"pid {pid} does not exist"
613
+
614
+ self._wait_attach(interval)
615
+
616
+ try:
617
+ os.kill(pid, signal.SIGUSR2)
618
+ except OSError: # pragma: no cover
619
+ return False, f"pid {pid} already finished"
620
+
621
+ return True, None
622
+
623
+ def _wait_attach(self, interval: float) -> None:
624
+ # interval == 0 means waiting for CTRL+C
625
+ try:
626
+ if interval > 0:
627
+ print(f"Attach success, collect trace after {interval}s", flush=True)
628
+ time.sleep(interval)
629
+ else:
630
+ print("Attach success, press CTRL+C to stop and save report", flush=True)
631
+ while True:
632
+ time.sleep(0.1)
633
+ except KeyboardInterrupt:
634
+ pass
635
+
636
+ def save(self) -> None:
637
+ # This function will only be called from main process
638
+ options = self.options
639
+ ofile = self.ofile
640
+ if options.pid_suffix:
641
+ prefix, suffix = os.path.splitext(self.ofile)
642
+ prefix_pid = f"{prefix}_{os.getpid()}"
643
+ ofile = prefix_pid + suffix
644
+ else:
645
+ ofile = self.ofile
646
+
647
+ self.wait_children_finish()
648
+ builder = ReportBuilder(
649
+ [os.path.join(self.multiprocess_output_dir, f)
650
+ for f in os.listdir(self.multiprocess_output_dir) if f.endswith(".json")],
651
+ minimize_memory=options.minimize_memory,
652
+ verbose=self.verbose)
653
+ builder.save(output_file=ofile)
654
+ shutil.rmtree(self.multiprocess_output_dir)
655
+
656
+ def wait_children_finish(self) -> None:
657
+ try:
658
+ if any((f.endswith(".viztmp") for f in os.listdir(self.multiprocess_output_dir))):
659
+ same_line_print("Wait for child processes to finish, Ctrl+C to skip")
660
+ while True:
661
+ remain_viztmp = [f for f in os.listdir(self.multiprocess_output_dir) if f.endswith(".viztmp")]
662
+ for viztmp_file in remain_viztmp:
663
+ match = re.search(r'result_(\d+).json.viztmp', viztmp_file)
664
+ if match:
665
+ pid = int(match.group(1))
666
+ if pid_exists(pid):
667
+ break
668
+ else: # pragma: no cover
669
+ color_print("WARNING", f"Unknown viztmp file {viztmp_file}")
670
+ else:
671
+ break
672
+ time.sleep(0.5)
673
+ except KeyboardInterrupt:
674
+ pass
675
+
676
+ def exit_routine(self) -> None:
677
+ if self.tracer is not None:
678
+ if not self._exiting:
679
+ self._exiting = True
680
+ if self.verbose > 0:
681
+ same_line_print("Saving trace data, this could take a while")
682
+ self.tracer.exit_routine()
683
+ self.save()
684
+ if self.options.open: # pragma: no cover
685
+ import subprocess
686
+ subprocess.run([sys.executable, "-m", "viztracer.viewer", "--once", os.path.abspath(self.ofile)])
687
+
688
+
689
+ def main():
690
+ ui = VizUI()
691
+ success, err_msg = ui.parse(sys.argv)
692
+ if not success:
693
+ print(err_msg)
694
+ sys.exit(1)
695
+ try:
696
+ success, err_msg = ui.run()
697
+ if not success:
698
+ print(err_msg)
699
+ sys.exit(1)
700
+ finally:
701
+ atexit._run_exitfuncs()