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.

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.cp314t-win_amd64.pyd +0 -0
  38. viztracer/snaptrace.pyi +77 -0
  39. viztracer/util.py +196 -0
  40. viztracer/vcompressor.cp314t-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/vizplugin.py ADDED
@@ -0,0 +1,143 @@
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
+
5
+ import sys
6
+ from typing import TYPE_CHECKING, Sequence
7
+
8
+ from . import __version__
9
+ from .util import color_print, compare_version
10
+
11
+ if TYPE_CHECKING:
12
+ from .viztracer import VizTracer # pragma: no cover
13
+
14
+
15
+ class VizPluginError(Exception):
16
+ pass
17
+
18
+
19
+ # A third party developer who wants to develop based on VizTracer can do a plugin
20
+ # Simply inherit VizPluginBase class and finish the methods. Then you can load it
21
+ # by VizTracer(plugins=[YourVizPlugin()])
22
+
23
+ class VizPluginBase:
24
+ def __init__(self) -> None:
25
+ pass
26
+
27
+ def support_version(self) -> str:
28
+ # You have to overload this to return the latest version of viztracer
29
+ # your plugin supports. This is for API backward compatibility.
30
+ # Simply return the version string
31
+ # For example:
32
+ # return "0.10.5"
33
+ raise NotImplementedError("Plugin of viztracer has to implement support_version method")
34
+
35
+ def message(self, m_type: str, payload: dict) -> dict:
36
+ """
37
+ This is the only logical interface with VizTracer. To make it simple and flexible,
38
+ we use m_type for message type, and the payload could be any json compatible
39
+ data. This is more extensible in the future
40
+ :param m_type str: the message type VizPlugin is receiving
41
+ :param payload dict: payload of the message
42
+
43
+ :return dict: always return a dict. Return None if nothing needs to be done
44
+ by VizTracer. Otherwise refer to the docs
45
+ """
46
+ if m_type == "command":
47
+ if payload["cmd_type"] == "terminate":
48
+ return {"success": True}
49
+
50
+ return {}
51
+
52
+
53
+ class VizPluginManager:
54
+ def __init__(self, tracer: "VizTracer", plugins: Sequence[VizPluginBase | str] | None):
55
+ self._tracer = tracer
56
+ self._plugins = []
57
+ if plugins:
58
+ for plugin in plugins:
59
+ if isinstance(plugin, VizPluginBase):
60
+ plugin_instance = plugin
61
+ elif isinstance(plugin, str):
62
+ plugin_instance = self._get_plugin_from_string(plugin)
63
+ else:
64
+ raise TypeError("Invalid plugin!")
65
+ self._plugins.append(plugin_instance)
66
+
67
+ support_version = plugin_instance.support_version()
68
+ if compare_version(support_version, __version__) > 0:
69
+ color_print("WARNING", "The plugin support version is higher than "
70
+ "viztracer version. Consider update your viztracer")
71
+ self._send_message(plugin_instance, "event", {"when": "initialize"})
72
+
73
+ def _get_plugin_from_string(self, plugin: str) -> VizPluginBase:
74
+ args = plugin.split()
75
+ module = args[0]
76
+ try:
77
+ package = __import__(module)
78
+ except (ImportError):
79
+ print(f"There's no module named {module}, maybe you need to install it")
80
+ sys.exit(1)
81
+
82
+ m = package
83
+ if "." in module:
84
+ # package.module
85
+ names = module.split(".")
86
+
87
+ try:
88
+ for mod in names[1:]:
89
+ m = m.__getattribute__(mod)
90
+ except AttributeError: # pragma: no cover
91
+ # This in theory should never happen
92
+ raise ImportError(f"Unable to import {module}, wrong path")
93
+ try:
94
+ m = m.__getattribute__("get_vizplugin")
95
+ except AttributeError:
96
+ print(f"Unable to find get_vizplugin in {module}. Incorrect plugin.")
97
+ sys.exit(1)
98
+
99
+ if callable(m):
100
+ return m(plugin)
101
+ else:
102
+ print(f"Unable to find get_vizplugin as a callable in {module}. Incorrect plugin.")
103
+ sys.exit(1)
104
+
105
+ def _send_message(self, plugin: VizPluginBase, m_type: str, payload: dict) -> None:
106
+ # this is the only interface to communicate with vizplugin
107
+ # in the future we may need to do version compatibility
108
+ # here
109
+ support_version = plugin.support_version()
110
+
111
+ ret = plugin.message(m_type, payload)
112
+ if m_type == "command":
113
+ self.assert_success(plugin, payload, ret)
114
+ else:
115
+ self.resolve(support_version, ret)
116
+
117
+ @property
118
+ def has_plugin(self) -> bool:
119
+ return len(self._plugins) > 0
120
+
121
+ def event(self, when: str) -> None:
122
+ for plugin in self._plugins:
123
+ self._send_message(plugin, "event", {"when": when})
124
+
125
+ def command(self, cmd: dict) -> None:
126
+ for plugin in self._plugins:
127
+ self._send_message(plugin, "command", cmd)
128
+
129
+ def terminate(self) -> None:
130
+ self.command({"cmd_type": "terminate"})
131
+ for plugin in self._plugins:
132
+ del plugin
133
+ self._plugins = []
134
+
135
+ def assert_success(self, plugin: VizPluginBase, cmd: dict, ret: dict | None) -> None:
136
+ if not ret or "success" not in ret or not ret["success"]:
137
+ raise VizPluginError(f"{plugin} failed to process {cmd}")
138
+
139
+ def resolve(self, version: str, ret: dict) -> None:
140
+ if not ret or "action" not in ret:
141
+ return
142
+ if ret["action"] == "handle_data":
143
+ ret["handler"](self._tracer.data)
viztracer/viztracer.py ADDED
@@ -0,0 +1,472 @@
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 builtins
5
+ import gc
6
+ import io
7
+ import multiprocessing
8
+ import os
9
+ import platform
10
+ import signal
11
+ import sys
12
+ from typing import Any, Callable, Literal, Sequence
13
+ from viztracer.snaptrace import Tracer
14
+
15
+ from . import __version__
16
+ from .report_builder import ReportBuilder
17
+ from .util import frame_stack_has_func
18
+ from .vizevent import VizEvent
19
+ from .vizplugin import VizPluginBase, VizPluginManager
20
+
21
+
22
+ # This is the interface of the package. Almost all user should use this
23
+ # class for the functions
24
+ class VizTracer(Tracer):
25
+ def __init__(self,
26
+ tracer_entries: int = 1000000,
27
+ verbose: int = 1,
28
+ max_stack_depth: int = -1,
29
+ include_files: list[str] | None = None,
30
+ exclude_files: list[str] | None = None,
31
+ ignore_c_function: bool = False,
32
+ ignore_frozen: bool = False,
33
+ log_func_retval: bool = False,
34
+ log_func_args: bool = False,
35
+ log_func_repr: Callable[..., str] | None = None,
36
+ log_func_with_objprint: bool = False,
37
+ log_print: bool = False,
38
+ log_gc: bool = False,
39
+ log_sparse: bool = False,
40
+ log_async: bool = False,
41
+ log_torch: bool = False,
42
+ log_audit: Sequence[str] | None = None,
43
+ pid_suffix: bool = False,
44
+ file_info: bool = True,
45
+ register_global: bool = True,
46
+ trace_self: bool = False,
47
+ min_duration: float = 0,
48
+ minimize_memory: bool = False,
49
+ dump_raw: bool = False,
50
+ sanitize_function_name: bool = False,
51
+ process_name: str | None = None,
52
+ output_file: str = "result.json",
53
+ plugins: Sequence[VizPluginBase | str] | None = None) -> None:
54
+ super().__init__(tracer_entries)
55
+
56
+ # Members of C Tracer object
57
+ self.verbose = verbose
58
+ self.max_stack_depth = max_stack_depth
59
+ self.ignore_c_function = ignore_c_function
60
+ self.ignore_frozen = ignore_frozen
61
+ self.log_func_args = log_func_args
62
+ self.log_func_retval = log_func_retval
63
+ self.log_async = log_async
64
+ self.log_gc = log_gc
65
+ self.log_print = log_print
66
+ self.trace_self = trace_self
67
+ self.lib_file_path = os.path.dirname(sys._getframe().f_code.co_filename)
68
+ self.process_name = process_name
69
+ self.min_duration = min_duration
70
+
71
+ if include_files is None:
72
+ self.include_files = include_files
73
+ else:
74
+ self.include_files = include_files[:] + [os.path.abspath(f) for f in include_files if not f.startswith("/")]
75
+
76
+ if exclude_files is None:
77
+ self.exclude_files = exclude_files
78
+ else:
79
+ self.exclude_files = exclude_files[:] + [os.path.abspath(f) for f in exclude_files if not f.startswith("/")]
80
+
81
+ if log_func_with_objprint:
82
+ import objprint # type: ignore
83
+ if log_func_repr:
84
+ raise ValueError("log_func_repr and log_func_with_objprint can't be both set")
85
+ log_func_repr = objprint.objstr
86
+ self.log_func_repr = log_func_repr
87
+
88
+ # Members of VizTracer object
89
+ self.pid_suffix = pid_suffix
90
+ self.file_info = file_info
91
+ self.output_file = output_file
92
+ self.log_sparse = log_sparse
93
+ self.log_audit = log_audit
94
+ self.log_torch = log_torch
95
+ self.torch_profile = None
96
+ self.dump_raw = dump_raw
97
+ self.sanitize_function_name = sanitize_function_name
98
+ self.minimize_memory = minimize_memory
99
+ self.system_print = builtins.print
100
+
101
+ # Members for the collected data
102
+ self.enable = False
103
+ self.parsed = False
104
+ self.tracer_entries = tracer_entries
105
+ self.data: dict[str, Any] = {}
106
+ self.total_entries = 0
107
+ self.gc_start_args: dict[str, int] = {}
108
+
109
+ self._exiting = False
110
+ if register_global:
111
+ self.register_global()
112
+
113
+ self.cwd = os.getcwd()
114
+
115
+ self.viztmp: str | None = None
116
+
117
+ self._afterfork_cb: Callable | None = None
118
+ self._afterfork_args: tuple = tuple()
119
+ self._afterfork_kwargs: dict = {}
120
+
121
+ # load in plugins
122
+ self._plugin_manager = VizPluginManager(self, plugins)
123
+
124
+ if log_torch:
125
+ # To generate an import error if torch is not installed
126
+ import torch # type: ignore # noqa: F401
127
+
128
+ @property
129
+ def pid_suffix(self) -> bool:
130
+ return self.__pid_suffix
131
+
132
+ @pid_suffix.setter
133
+ def pid_suffix(self, pid_suffix: bool) -> None:
134
+ if type(pid_suffix) is bool:
135
+ self.__pid_suffix = pid_suffix
136
+ else:
137
+ raise ValueError(f"pid_suffix needs to be a boolean, not {pid_suffix}")
138
+
139
+ @property
140
+ def init_kwargs(self) -> dict:
141
+ return {
142
+ "tracer_entries": self.tracer_entries,
143
+ "verbose": self.verbose,
144
+ "output_file": self.output_file,
145
+ "max_stack_depth": self.max_stack_depth,
146
+ "exclude_files": self.exclude_files,
147
+ "include_files": self.include_files,
148
+ "ignore_c_function": self.ignore_c_function,
149
+ "ignore_frozen": self.ignore_frozen,
150
+ "log_func_retval": self.log_func_retval,
151
+ "log_func_args": self.log_func_args,
152
+ "log_print": self.log_print,
153
+ "log_gc": self.log_gc,
154
+ "log_sparse": self.log_sparse,
155
+ "log_async": self.log_async,
156
+ "log_audit": self.log_audit,
157
+ "log_torch": self.log_torch,
158
+ "pid_suffix": self.pid_suffix,
159
+ "min_duration": self.min_duration,
160
+ "dump_raw": self.dump_raw,
161
+ "minimize_memory": self.minimize_memory,
162
+ }
163
+
164
+ def __enter__(self) -> "VizTracer":
165
+ if not self.log_sparse:
166
+ self.start()
167
+ return self
168
+
169
+ def __exit__(self, type, value, trace) -> None:
170
+ if not self.log_sparse:
171
+ self.stop()
172
+ self.save()
173
+ self.terminate()
174
+
175
+ def register_global(self) -> None:
176
+ builtins.__dict__["__viz_tracer__"] = self
177
+
178
+ def install(self) -> None:
179
+ if (sys.platform == "win32"
180
+ or (sys.platform == "darwin" and "arm" in platform.processor())):
181
+ print("remote install is not supported on this platform!")
182
+ sys.exit(1)
183
+
184
+ def signal_start(signum, frame):
185
+ self.start()
186
+
187
+ def signal_stop(signum, frame):
188
+ self.stop()
189
+ self.save()
190
+
191
+ signal.signal(signal.SIGUSR1, signal_start)
192
+ signal.signal(signal.SIGUSR2, signal_stop)
193
+
194
+ def log_instant(self, name: str, args: Any = None, scope: Literal["g", "p", "t"] = "t", cond: bool = True) -> None:
195
+ if cond:
196
+ self.add_instant(name, args=args, scope=scope)
197
+
198
+ def log_var(self, name: str, var: Any, cond: bool = True) -> None:
199
+ if cond:
200
+ if isinstance(var, (float, int)):
201
+ self.add_counter(name, {"value": var})
202
+ else:
203
+ import objprint # type: ignore
204
+ self.add_instant(name, args={"object": objprint.objstr(var, color=False)}, scope="t")
205
+
206
+ def log_event(self, event_name: str) -> VizEvent:
207
+ call_frame = sys._getframe(1)
208
+ return VizEvent(self, event_name, call_frame.f_code.co_filename, call_frame.f_lineno)
209
+
210
+ def shield_ignore(self, func: Callable, *args, **kwargs):
211
+ prev_ignore_stack = self.setignorestackcounter(0)
212
+ res = func(*args, **kwargs)
213
+ self.setignorestackcounter(prev_ignore_stack)
214
+ return res
215
+
216
+ def set_afterfork(self, callback: Callable, *args, **kwargs) -> None:
217
+ self._afterfork_cb = callback
218
+ self._afterfork_args = args
219
+ self._afterfork_kwargs = kwargs
220
+
221
+ def start(self) -> None:
222
+ if not self.enable:
223
+ self.enable = True
224
+ self.parsed = False
225
+ if self.log_torch:
226
+ from torch.profiler import profile, supported_activities # type: ignore
227
+ self.torch_profile = profile(activities=supported_activities()).__enter__()
228
+ if self.log_print:
229
+ self.overload_print()
230
+ if self.include_files is not None and self.exclude_files is not None:
231
+ raise Exception("include_files and exclude_files can't be both specified!")
232
+ self._plugin_manager.event("pre-start")
233
+ super().start()
234
+
235
+ def stop(self, stop_option: str | None = None) -> None:
236
+ if self.enable:
237
+ self.enable = False
238
+ if self.log_print:
239
+ self.restore_print()
240
+ super().stop(stop_option)
241
+ if self.torch_profile is not None:
242
+ self.torch_profile.__exit__(None, None, None)
243
+ self._plugin_manager.event("post-stop")
244
+
245
+ def parse(self) -> int:
246
+ # parse() is also performance sensitive. We could have a lot of entries
247
+ # in buffer, so try not to add any overhead when parsing
248
+ # We parse the buffer into Chrome Trace Event Format
249
+ self.stop()
250
+ if not self.parsed:
251
+ self.data = {
252
+ "traceEvents": self.load(),
253
+ "viztracer_metadata": {
254
+ "version": __version__,
255
+ "overflow": False,
256
+ },
257
+ }
258
+ sync_marker = self.get_sync_marker()
259
+ if sync_marker is not None:
260
+ self.data['viztracer_metadata']['sync_marker'] = sync_marker
261
+
262
+ metadata_count = 0
263
+ for d in self.data["traceEvents"]:
264
+ if d["ph"] == "M":
265
+ metadata_count += 1
266
+ else:
267
+ break
268
+ self.total_entries = len(self.data["traceEvents"]) - metadata_count
269
+ if self.total_entries == self.tracer_entries:
270
+ self.data["viztracer_metadata"]["overflow"] = True
271
+ self.parsed = True
272
+
273
+ return self.total_entries
274
+
275
+ def run(self, command: str, output_file: str | None = None) -> None:
276
+ self.start()
277
+ exec(command)
278
+ self.stop()
279
+ self.save(output_file)
280
+
281
+ def save(
282
+ self,
283
+ output_file: str | None = None,
284
+ file_info: bool | None = None,
285
+ verbose: int | None = None) -> None:
286
+ if file_info is None:
287
+ file_info = self.file_info
288
+ enabled = False
289
+ if output_file is None:
290
+ output_file = self.output_file
291
+ if verbose is None:
292
+ verbose = self.verbose
293
+ if self.pid_suffix:
294
+ output_file_parts = output_file.split(".")
295
+ output_file_parts[-2] = output_file_parts[-2] + "_" + str(os.getpid())
296
+ output_file = ".".join(output_file_parts)
297
+
298
+ if isinstance(output_file, str):
299
+ output_file = os.path.abspath(output_file)
300
+ if not os.path.isdir(os.path.dirname(output_file)):
301
+ os.makedirs(os.path.dirname(output_file), exist_ok=True)
302
+
303
+ if self.enable:
304
+ enabled = True
305
+ self.stop()
306
+
307
+ # If there are plugins, we can't do dump raw because it will skip the data
308
+ # manipulation phase
309
+ # If we want to dump torch profile, we can't do dump raw either
310
+ if not self._plugin_manager.has_plugin and not self.log_torch and self.dump_raw:
311
+ self.dump(output_file, sanitize_function_name=self.sanitize_function_name)
312
+ else:
313
+ if not self.parsed:
314
+ self.parse()
315
+
316
+ self._plugin_manager.event("pre-save")
317
+
318
+ if self.log_torch and self.torch_profile is not None:
319
+ import tempfile
320
+ with tempfile.NamedTemporaryFile(suffix=".json") as tmpfile:
321
+ self.torch_profile.export_chrome_trace(tmpfile.name)
322
+ rb = ReportBuilder([(tmpfile.name, {'type': 'torch', 'base_offset': self.get_base_time()}), self.data],
323
+ verbose, minimize_memory=self.minimize_memory, base_time=self.get_base_time())
324
+ rb.save(output_file=output_file, file_info=file_info)
325
+ else:
326
+ rb = ReportBuilder(self.data, verbose, minimize_memory=self.minimize_memory, base_time=self.get_base_time())
327
+ rb.save(output_file=output_file, file_info=file_info)
328
+
329
+ if enabled:
330
+ self.start()
331
+
332
+ def fork_save(self, output_file: str | None = None) -> multiprocessing.Process:
333
+ if multiprocessing.get_start_method() != "fork":
334
+ raise RuntimeError("fork_save is only supported in fork start method")
335
+
336
+ # Fix the current pid so it won't give new pid when parsing
337
+ self.setpid()
338
+
339
+ p = multiprocessing.Process(target=self.save, daemon=False,
340
+ kwargs={"output_file": output_file})
341
+ p.start()
342
+
343
+ # Revert to the normal pid mode
344
+ self.setpid(0)
345
+
346
+ return p
347
+
348
+ def label_file_to_write(self) -> None:
349
+ output_file = self.output_file
350
+ if self.pid_suffix:
351
+ output_file_parts = output_file.split(".")
352
+ output_file_parts[-2] = output_file_parts[-2] + "_" + str(os.getpid())
353
+ output_file = ".".join(output_file_parts) + ".viztmp"
354
+
355
+ with open(output_file, "w") as _:
356
+ # create an empty file
357
+ pass
358
+ self.viztmp = output_file
359
+
360
+ def terminate(self) -> None:
361
+ self._plugin_manager.terminate()
362
+
363
+ def register_exit(self) -> None:
364
+ self.cwd = os.getcwd()
365
+
366
+ def term_handler(sig, frame):
367
+ # For multiprocessing.pool, it's possible we receive SIGTERM
368
+ # in util._exit_function(), but before tracer.exit_routine()
369
+ # executes. In this case, we can just let the exit finish
370
+ if not frame_stack_has_func(frame, (self.exit_routine,
371
+ multiprocessing.util._exit_function)):
372
+ sys.exit(0)
373
+
374
+ self.label_file_to_write()
375
+
376
+ signal.signal(signal.SIGTERM, term_handler)
377
+
378
+ from multiprocessing.util import Finalize # type: ignore
379
+ Finalize(self, self.exit_routine, exitpriority=-1)
380
+
381
+ def exit_routine(self) -> None:
382
+ self.stop(stop_option="flush_as_finish")
383
+ if not self._exiting:
384
+ self._exiting = True
385
+ os.chdir(self.cwd)
386
+ try:
387
+ self.save()
388
+ finally:
389
+ if self.viztmp is not None and os.path.exists(self.viztmp):
390
+ os.remove(self.viztmp)
391
+ self.terminate()
392
+
393
+ def enable_thread_tracing(self) -> None:
394
+ if sys.version_info < (3, 12):
395
+ sys.setprofile(self.threadtracefunc)
396
+
397
+ def add_variable(self, name: str, var: Any, event: str = "instant") -> None:
398
+ if self.enable:
399
+ if event == "instant":
400
+ self.add_instant(f"{name} = {repr(var)}", scope="p")
401
+ elif event == "counter":
402
+ if isinstance(var, (int, float)):
403
+ self.add_counter(name, {name: var})
404
+ else:
405
+ raise ValueError(f"{name}({var}) is not a number")
406
+ else:
407
+ raise ValueError(f"{event} is not supported")
408
+
409
+ def overload_print(self) -> None:
410
+ self.system_print = builtins.print
411
+
412
+ def new_print(*args, **kwargs):
413
+ self.pause()
414
+ file = io.StringIO()
415
+ kwargs["file"] = file
416
+ self.system_print(*args, **kwargs)
417
+ self.add_instant(f"print - {file.getvalue()}")
418
+ self.resume()
419
+ builtins.print = new_print
420
+
421
+ def restore_print(self) -> None:
422
+ builtins.print = self.system_print
423
+
424
+ def add_func_exec(self, name: str, val: Any, lineno: int) -> None:
425
+ exec_line = f"({lineno}) {name} = {val}"
426
+ curr_args = self.get_func_args()
427
+ if not curr_args:
428
+ self.add_func_args("exec_steps", [exec_line])
429
+ else:
430
+ if "exec_steps" in curr_args:
431
+ curr_args["exec_steps"].append(exec_line)
432
+ else:
433
+ curr_args["exec_steps"] = [exec_line]
434
+
435
+ @property
436
+ def log_gc(self) -> bool:
437
+ return self.__log_gc
438
+
439
+ @log_gc.setter
440
+ def log_gc(self, log_gc: bool) -> None:
441
+ if isinstance(log_gc, bool):
442
+ self.__log_gc = log_gc
443
+ if log_gc:
444
+ gc.callbacks.append(self.add_garbage_collection)
445
+ elif self.add_garbage_collection in gc.callbacks:
446
+ gc.callbacks.remove(self.add_garbage_collection)
447
+ else:
448
+ raise TypeError(f"log_gc needs to be True or False, not {log_gc}")
449
+
450
+ def add_garbage_collection(self, phase: str, info: dict[str, Any]) -> None:
451
+ if self.enable:
452
+ if phase == "start":
453
+ args = {
454
+ "collecting": 1,
455
+ "collected": 0,
456
+ "uncollectable": 0,
457
+ }
458
+ self.add_counter("garbage collection", args)
459
+ self.gc_start_args = args
460
+ if phase == "stop" and self.gc_start_args:
461
+ self.gc_start_args["collected"] = info["collected"]
462
+ self.gc_start_args["uncollectable"] = info["uncollectable"]
463
+ self.gc_start_args = {}
464
+ self.add_counter("garbage collection", {
465
+ "collecting": 0,
466
+ "collected": 0,
467
+ "uncollectable": 0,
468
+ })
469
+
470
+
471
+ def get_tracer() -> VizTracer | None:
472
+ return builtins.__dict__.get("__viz_tracer__", None)