viztracer 1.1.0__cp314-cp314-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.
Potentially problematic release.
This version of viztracer might be problematic. Click here for more details.
- viztracer/__init__.py +19 -0
- viztracer/__main__.py +8 -0
- viztracer/attach.py +67 -0
- viztracer/attach_process/LICENSE +203 -0
- viztracer/attach_process/__init__.py +0 -0
- viztracer/attach_process/add_code_to_python_process.py +582 -0
- viztracer/attach_process/attach_x86.dll +0 -0
- viztracer/attach_process/inject_dll_amd64.exe +0 -0
- viztracer/attach_process/linux_and_mac/lldb_prepare.py +54 -0
- viztracer/attach_process/run_code_on_dllmain_amd64.dll +0 -0
- viztracer/attach_process/run_code_on_dllmain_x86.dll +0 -0
- viztracer/cellmagic.py +70 -0
- viztracer/code_monkey.py +353 -0
- viztracer/decorator.py +164 -0
- viztracer/event_base.py +81 -0
- viztracer/functree.py +135 -0
- viztracer/html/flamegraph.html +34 -0
- viztracer/html/trace_viewer_embedder.html +203 -0
- viztracer/html/trace_viewer_full.html +10207 -0
- viztracer/main.py +699 -0
- viztracer/modules/eventnode.c +172 -0
- viztracer/modules/eventnode.h +73 -0
- viztracer/modules/pythoncapi_compat.h +1726 -0
- viztracer/modules/quicktime.c +177 -0
- viztracer/modules/quicktime.h +104 -0
- viztracer/modules/snaptrace.c +2205 -0
- viztracer/modules/snaptrace.h +134 -0
- viztracer/modules/snaptrace_member.c +483 -0
- viztracer/modules/util.c +45 -0
- viztracer/modules/util.h +22 -0
- viztracer/modules/vcompressor/vc_dump.c +1131 -0
- viztracer/modules/vcompressor/vc_dump.h +49 -0
- viztracer/modules/vcompressor/vcompressor.c +396 -0
- viztracer/modules/vcompressor/vcompressor.h +15 -0
- viztracer/patch.py +307 -0
- viztracer/report_builder.py +311 -0
- viztracer/snaptrace.cp314-win32.pyd +0 -0
- viztracer/snaptrace.pyi +77 -0
- viztracer/util.py +196 -0
- viztracer/vcompressor.cp314-win32.pyd +0 -0
- viztracer/vcompressor.pyi +10 -0
- viztracer/viewer.py +528 -0
- viztracer/vizcounter.py +20 -0
- viztracer/vizevent.py +31 -0
- viztracer/vizlogging.py +20 -0
- viztracer/vizobject.py +28 -0
- viztracer/vizplugin.py +143 -0
- viztracer/viztracer.py +472 -0
- viztracer/web_dist/LICENSE +189 -0
- viztracer/web_dist/index.html +127 -0
- viztracer/web_dist/service_worker.js +279 -0
- viztracer/web_dist/trace_processor +300 -0
- viztracer/web_dist/v52.0-6b9586def/assets/MaterialSymbolsOutlined.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-100.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-300.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-400.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/Roboto-500.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Light.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoCondensed-Regular.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/RobotoMono-Regular.woff2 +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/brand.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.html +3946 -0
- viztracer/web_dist/v52.0-6b9586def/assets/catapult_trace_viewer.js +7539 -0
- viztracer/web_dist/v52.0-6b9586def/assets/favicon.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/logo-128.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/logo-3d.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_atrace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_battery_counters.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_board_voltage.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_coarse.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_fine.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_freq.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_cpu_voltage.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_frame_timeline.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ftrace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_gpu_mem_total.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_java_heap_dump.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_lmk.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_logcat.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_long_trace.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_mem_hifreq.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_meminfo.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_native_heap_profiler.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_one_shot.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_profiling.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ps_stats.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_ring_buf.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_syscalls.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/rec_vmstat.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/scheduling_latency.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/assets/vscode-icon.png +0 -0
- viztracer/web_dist/v52.0-6b9586def/engine_bundle.js +3 -0
- viztracer/web_dist/v52.0-6b9586def/frontend_bundle.js +5495 -0
- viztracer/web_dist/v52.0-6b9586def/index.html +127 -0
- viztracer/web_dist/v52.0-6b9586def/manifest.json +52 -0
- viztracer/web_dist/v52.0-6b9586def/perfetto.css +5737 -0
- viztracer/web_dist/v52.0-6b9586def/stdlib_docs.json +1 -0
- viztracer/web_dist/v52.0-6b9586def/trace_config_utils.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/trace_processor.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/trace_processor_memory64.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/traceconv.wasm +0 -0
- viztracer/web_dist/v52.0-6b9586def/traceconv_bundle.js +2 -0
- viztracer-1.1.0.dist-info/METADATA +316 -0
- viztracer-1.1.0.dist-info/RECORD +109 -0
- viztracer-1.1.0.dist-info/WHEEL +5 -0
- viztracer-1.1.0.dist-info/entry_points.txt +3 -0
- viztracer-1.1.0.dist-info/licenses/LICENSE +222 -0
- viztracer-1.1.0.dist-info/licenses/NOTICE.txt +27 -0
- viztracer-1.1.0.dist-info/top_level.txt +1 -0
viztracer/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)
|