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/viewer.py ADDED
@@ -0,0 +1,529 @@
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 contextlib
7
+ import functools
8
+ import html
9
+ import http.server
10
+ import gzip
11
+ import io
12
+ import json
13
+ import os
14
+ import socket
15
+ import socketserver
16
+ import subprocess
17
+ import sys
18
+ import traceback
19
+ import threading
20
+ import time
21
+ import urllib.parse
22
+ from http import HTTPStatus
23
+ from typing import Any, Callable
24
+
25
+
26
+ dir_lock = threading.Lock()
27
+
28
+
29
+ @contextlib.contextmanager
30
+ def chdir_temp(d: str):
31
+ with dir_lock:
32
+ curr_cwd = os.getcwd()
33
+ os.chdir(d)
34
+ try:
35
+ yield
36
+ finally:
37
+ os.chdir(curr_cwd)
38
+
39
+
40
+ class HttpHandler(http.server.SimpleHTTPRequestHandler):
41
+ def end_headers(self):
42
+ self.send_header('Access-Control-Allow-Origin', '*')
43
+ self.send_header('Cache-Control', 'no-store')
44
+ return super().end_headers()
45
+
46
+ def log_message(self, format, *args):
47
+ # To quiet the http server
48
+ pass
49
+
50
+
51
+ class ExternalProcessorHandler(HttpHandler):
52
+ def __init__(
53
+ self,
54
+ server_thread: "ServerThread",
55
+ *args, **kwargs) -> None:
56
+ self.server_thread = server_thread
57
+ super().__init__(*args, **kwargs)
58
+
59
+ def do_GET(self):
60
+ self.server.last_request = self.path
61
+ self.server_thread.notify_active()
62
+ self.directory = os.path.join(os.path.dirname(__file__), "web_dist")
63
+ with chdir_temp(self.directory):
64
+ return super().do_GET()
65
+
66
+
67
+ class PerfettoHandler(HttpHandler):
68
+ def __init__(
69
+ self,
70
+ server_thread: "ServerThread",
71
+ *args, **kwargs) -> None:
72
+ self.server_thread = server_thread
73
+ super().__init__(*args, **kwargs)
74
+
75
+ def do_GET(self):
76
+ self.server.last_request = self.path
77
+ self.server_thread.notify_active()
78
+ if self.path.endswith("vizviewer_info"):
79
+ info = {}
80
+ self.send_response(200)
81
+ self.send_header("Content-type", "application/json")
82
+ self.end_headers()
83
+ self.wfile.write(json.dumps(info).encode("utf-8"))
84
+ self.wfile.flush()
85
+ elif self.path.endswith("file_info"):
86
+ self.send_response(200)
87
+ self.send_header("Content-type", "application/json")
88
+ self.end_headers()
89
+ self.wfile.write(json.dumps(self.server_thread.file_info).encode("utf-8"))
90
+ self.wfile.flush()
91
+ # Since v1.1, file_info is the last request from the frontend
92
+ self.server.trace_served = True
93
+ elif self.path.endswith("localtrace"):
94
+ # self.directory is used after 3.8
95
+ # os.getcwd() is used on 3.6
96
+ self.directory = os.path.dirname(self.server_thread.path)
97
+ with chdir_temp(self.directory):
98
+ filename = os.path.basename(self.server_thread.path)
99
+ self.path = f"/{filename}"
100
+ return super().do_GET()
101
+ else:
102
+ self.directory = os.path.join(os.path.dirname(__file__), "web_dist")
103
+ with chdir_temp(self.directory):
104
+ return super().do_GET()
105
+
106
+
107
+ class HtmlHandler(HttpHandler):
108
+ def __init__(self, server_thread: "ServerThread", *args, **kwargs) -> None:
109
+ self.server_thread = server_thread
110
+ super().__init__(*args, **kwargs)
111
+
112
+ def do_GET(self):
113
+ self.directory = os.path.dirname(self.server_thread.path)
114
+ with chdir_temp(self.directory):
115
+ filename = os.path.basename(self.server_thread.path)
116
+ self.path = f"/{filename}"
117
+ self.server.trace_served = True
118
+ return super().do_GET()
119
+
120
+
121
+ class DirectoryHandler(HttpHandler):
122
+ def __init__(self, directory_viewer: "DirectoryViewer", *args, **kwargs) -> None:
123
+ self.directory_viewer = directory_viewer
124
+ kwargs["directory"] = directory_viewer.base_path
125
+ super().__init__(*args, **kwargs)
126
+
127
+ def do_GET(self):
128
+ if self.path.endswith("json"):
129
+ # self.path starts with '/', we need to remove it
130
+ self.send_response(302)
131
+ self.send_header("Location", self.directory_viewer.get_link(self.path[1:]))
132
+ self.end_headers()
133
+ else:
134
+ with chdir_temp(self.directory):
135
+ super().do_GET()
136
+
137
+ def send_head(self): # pragma: no cover
138
+ """
139
+ Return list_directory even if there's an index.html in the dir
140
+ """
141
+ path = self.translate_path(self.path)
142
+ if os.path.isdir(path):
143
+ parts = urllib.parse.urlsplit(self.path)
144
+ if not parts.path.endswith('/'):
145
+ # redirect browser - doing basically what apache does
146
+ self.send_response(HTTPStatus.MOVED_PERMANENTLY)
147
+ new_parts = (parts[0], parts[1], parts[2] + '/',
148
+ parts[3], parts[4])
149
+ new_url = urllib.parse.urlunsplit(new_parts)
150
+ self.send_header("Location", new_url)
151
+ self.send_header("Content-Length", "0")
152
+ self.end_headers()
153
+ return None
154
+ else:
155
+ return self.list_directory(path)
156
+ return super().send_head()
157
+
158
+ def list_directory(self, path): # pragma: no cover
159
+ """
160
+ Almost the same as SimpleHTTPRequestHandler.list_directory, but
161
+ * Does not display file that does not end with json
162
+ * Created a new tab when click
163
+ """
164
+ try:
165
+ list = os.listdir(path)
166
+ except OSError:
167
+ self.send_error(
168
+ HTTPStatus.NOT_FOUND,
169
+ "No permission to list directory")
170
+ return None
171
+ list.sort(key=lambda a: a.lower())
172
+ r = []
173
+ try:
174
+ displaypath = urllib.parse.unquote(self.path,
175
+ errors='surrogatepass')
176
+ except UnicodeDecodeError:
177
+ displaypath = urllib.parse.unquote(path)
178
+ displaypath = html.escape(displaypath, quote=False)
179
+ enc = sys.getfilesystemencoding()
180
+ title = f'Directory listing for {displaypath}'
181
+ r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
182
+ '"http://www.w3.org/TR/html4/strict.dtd">')
183
+ r.append('<html>\n<head>')
184
+ r.append('<meta http-equiv="Content-Type" '
185
+ 'content="text/html; charset=%s">' % enc)
186
+ r.append(f'<title>{title}</title>\n</head>')
187
+ r.append(f'<body>\n<h1>{title}</h1>')
188
+ r.append('<hr>\n<ul>')
189
+ for name in list:
190
+ fullname = os.path.join(path, name)
191
+ displayname = linkname = name
192
+ # Append / for directories or @ for symbolic links
193
+ if os.path.isdir(fullname):
194
+ displayname = name + "/"
195
+ linkname = name + "/"
196
+ elif not name.endswith("json") and not name.endswith("html"):
197
+ # Do not display files that we can't handle
198
+ continue
199
+ if os.path.islink(fullname):
200
+ displayname = name + "@"
201
+ # Note: a link to a directory displays with @ and links with /
202
+ if os.path.isdir(fullname):
203
+ r.append('<li><a href="%s">%s</a></li>'
204
+ % (urllib.parse.quote(linkname,
205
+ errors='surrogatepass'),
206
+ html.escape(displayname, quote=False)))
207
+ else:
208
+ # Open a new tab
209
+ r.append('<li><a href="%s" target="_blank">%s</a></li>'
210
+ % (urllib.parse.quote(linkname,
211
+ errors='surrogatepass'),
212
+ html.escape(displayname, quote=False)))
213
+ r.append('</ul>\n<hr>\n</body>\n</html>\n')
214
+ encoded = '\n'.join(r).encode(enc, 'surrogateescape')
215
+ f = io.BytesIO()
216
+ f.write(encoded)
217
+ f.seek(0)
218
+ self.send_response(HTTPStatus.OK)
219
+ self.send_header("Content-type", f"text/html; charset={enc}")
220
+ self.send_header("Content-Length", str(len(encoded)))
221
+ self.end_headers()
222
+ return f
223
+
224
+
225
+ class ExternalProcessorProcess:
226
+ trace_processor_path = os.path.join(os.path.dirname(__file__), "web_dist", "trace_processor")
227
+
228
+ def __init__(self, path: str) -> None:
229
+ self.path = path
230
+ self._process = subprocess.Popen(
231
+ [
232
+ sys.executable,
233
+ self.trace_processor_path,
234
+ self.path,
235
+ "-D",
236
+ ],
237
+ stderr=subprocess.PIPE,
238
+ )
239
+ atexit.register(self.stop)
240
+ self._wait_start()
241
+
242
+ def _wait_start(self):
243
+ print("Loading and parsing trace data, this could take a while...")
244
+ assert self._process.stderr is not None
245
+ while True:
246
+ line = self._process.stderr.readline().decode("utf-8")
247
+ if "This server can be used" in line:
248
+ break
249
+
250
+ def stop(self):
251
+ self._process.terminate()
252
+ try:
253
+ self._process.wait(timeout=2)
254
+ except subprocess.TimeoutExpired: # pragma: no cover
255
+ self._process.kill()
256
+ atexit.unregister(self.stop)
257
+
258
+
259
+ class VizViewerTCPServer(socketserver.TCPServer):
260
+ def handle_timeout(self) -> None:
261
+ self.trace_served = True
262
+ return super().handle_timeout()
263
+
264
+
265
+ class ServerThread(threading.Thread):
266
+ def __init__(
267
+ self,
268
+ path: str,
269
+ port: int = 9001,
270
+ once: bool = False,
271
+ use_external_processor: bool = False,
272
+ timeout: float = 10,
273
+ quiet: bool = False) -> None:
274
+ self.path = path
275
+ self.port = port
276
+ self.once = once
277
+ self.timeout = timeout
278
+ self.quiet = quiet
279
+ self.link = f"http://127.0.0.1:{self.port}"
280
+ self.use_external_procesor = use_external_processor
281
+ self.externel_processor_process: ExternalProcessorProcess | None = None
282
+ self.fg_data: list[dict[str, Any]] | None = None
283
+ self.file_info = None
284
+ self.httpd: VizViewerTCPServer | None = None
285
+ self.last_active = time.time()
286
+ self.retcode: int | None = None
287
+ self.ready = threading.Event()
288
+ self.ready.clear()
289
+ super().__init__(daemon=True)
290
+
291
+ def run(self) -> None:
292
+ try:
293
+ self.retcode = self.view()
294
+ except Exception:
295
+ self.retcode = 1
296
+ traceback.print_exc()
297
+ finally:
298
+ # If it returns from view(), also set ready
299
+ self.ready.set()
300
+
301
+ def view(self) -> int:
302
+ # Get file data
303
+ filename = os.path.basename(self.path)
304
+
305
+ Handler: Callable[..., HttpHandler]
306
+ if filename.endswith("json") or filename.endswith("gz"):
307
+ trace_data = None
308
+ if self.use_external_procesor:
309
+ Handler = functools.partial(ExternalProcessorHandler, self)
310
+ self.externel_processor_process = ExternalProcessorProcess(self.path)
311
+ else:
312
+ if filename.endswith("gz"):
313
+ with gzip.open(self.path, "rt", encoding="utf-8", errors="ignore") as f:
314
+ trace_data = json.load(f)
315
+ else:
316
+ with open(self.path, encoding="utf-8", errors="ignore") as f:
317
+ trace_data = json.load(f)
318
+ self.file_info = trace_data.get("file_info", {})
319
+ Handler = functools.partial(PerfettoHandler, self)
320
+ elif filename.endswith("html"):
321
+ Handler = functools.partial(HtmlHandler, self)
322
+ else:
323
+ print(f"Do not support file type {filename}")
324
+ return 1
325
+
326
+ if self.is_port_in_use():
327
+ print(f'Error! Port {self.port} is already in use, try another port with "--port"')
328
+ return 1
329
+
330
+ socketserver.TCPServer.allow_reuse_address = True
331
+ with VizViewerTCPServer(('0.0.0.0', self.port), Handler) as self.httpd:
332
+ if not self.once and not self.quiet:
333
+ print("Running vizviewer")
334
+ print(f"You can also view your trace on http://localhost:{self.port}")
335
+ print("Press Ctrl+C to quit", flush=True)
336
+ self.ready.set()
337
+ if self.once:
338
+ self.httpd.timeout = self.timeout
339
+ while not self.httpd.__dict__.get("trace_served", False):
340
+ self.httpd.handle_request()
341
+ else:
342
+ self.httpd.serve_forever()
343
+
344
+ if self.externel_processor_process is not None:
345
+ self.externel_processor_process.stop()
346
+
347
+ return 0
348
+
349
+ def notify_active(self) -> None:
350
+ self.last_active = time.time()
351
+
352
+ def is_port_in_use(self) -> bool:
353
+ with contextlib.closing(
354
+ socket.socket(socket.AF_INET,
355
+ socket.SOCK_STREAM)) as sock:
356
+ return sock.connect_ex(('127.0.0.1', self.port)) == 0
357
+
358
+
359
+ class DirectoryViewer:
360
+ def __init__(
361
+ self,
362
+ path: str,
363
+ port: int,
364
+ server_only: bool,
365
+ timeout: int,
366
+ use_external_processor: bool) -> None:
367
+ self.base_path = os.path.abspath(path)
368
+ self.port = port
369
+ self.server_only = server_only
370
+ self.timeout = timeout
371
+ self.use_external_processor = use_external_processor
372
+ self.max_port_number = 10
373
+ self.servers: dict[str, ServerThread] = {}
374
+
375
+ def get_link(self, path: str) -> str:
376
+ path = os.path.join(self.base_path, path)
377
+ if path not in self.servers:
378
+ self.servers[path] = self.create_server(path)
379
+
380
+ server = self.servers[path]
381
+ return server.link
382
+
383
+ def create_server(self, path: str) -> ServerThread:
384
+ max_port_number = self.max_port_number
385
+ ports_used = set((serv.port for serv in self.servers.values()))
386
+ if len(ports_used) == max_port_number:
387
+ self.clean_servers(force=True)
388
+ else:
389
+ self.clean_servers(force=False)
390
+ ports_used = set((serv.port for serv in self.servers.values()))
391
+ for port in range(self.port + 1, self.port + max_port_number + 1):
392
+ if port not in ports_used:
393
+ t = ServerThread(
394
+ path,
395
+ port=port,
396
+ use_external_processor=self.use_external_processor,
397
+ quiet=True)
398
+ t.start()
399
+ t.ready.wait()
400
+ return t
401
+ assert False, "Should always have a port available" # pragma: no cover
402
+
403
+ def clean_servers(self, force: bool = False) -> None:
404
+ curr_time = time.time()
405
+ removed_path = []
406
+ for path, server in self.servers.items():
407
+ if curr_time - server.last_active > self.timeout:
408
+ if server.httpd is not None:
409
+ server.httpd.shutdown()
410
+ removed_path.append(path)
411
+ server.join()
412
+ for path in removed_path:
413
+ self.servers.pop(path)
414
+ if len(removed_path) == 0 and force:
415
+ max_idle_time, max_idle_path = max(
416
+ (curr_time - server.last_active, path)
417
+ for path, server in self.servers.items()
418
+ )
419
+ server = self.servers.pop(max_idle_path)
420
+ if server.httpd:
421
+ server.httpd.shutdown()
422
+ server.join()
423
+
424
+ def run(self) -> int:
425
+ Handler = functools.partial(DirectoryHandler, self)
426
+ socketserver.TCPServer.allow_reuse_address = True
427
+ with VizViewerTCPServer(('0.0.0.0', self.port), Handler) as httpd:
428
+ print("Running vizviewer")
429
+ print(f"You can also view your trace on http://localhost:{self.port}")
430
+ print("Press Ctrl+C to quit", flush=True)
431
+ if not self.server_only:
432
+ # import webbrowser only if necessary
433
+ import webbrowser
434
+ webbrowser.open_new_tab(f'http://127.0.0.1:{self.port}')
435
+ try:
436
+ httpd.serve_forever()
437
+ except KeyboardInterrupt:
438
+ for server in self.servers.values():
439
+ if server.httpd:
440
+ server.httpd.shutdown()
441
+ server.join()
442
+ self.servers = {}
443
+ return 0
444
+
445
+
446
+ def viewer_main() -> int:
447
+ parser = argparse.ArgumentParser()
448
+ parser.add_argument("file", nargs=1, help="html/json/gz file to open")
449
+ parser.add_argument("--server_only", "-s", default=False, action="store_true",
450
+ help="Only start the server, do not open webpage")
451
+ parser.add_argument("--port", "-p", nargs="?", type=int, default=9001,
452
+ help="Specify the port vizviewer will use")
453
+ parser.add_argument("--once", default=False, action="store_true",
454
+ help="Only serve trace data once, then exit.")
455
+ parser.add_argument("--timeout", nargs="?", type=int, default=10,
456
+ help="Timeout in seconds to stop the server without trace data requests")
457
+ parser.add_argument("--flamegraph", default=False, action="store_true",
458
+ help=argparse.SUPPRESS)
459
+ parser.add_argument("--use_external_processor", default=False, action="store_true",
460
+ help="Use the more powerful external trace processor instead of WASM")
461
+
462
+ options = parser.parse_args(sys.argv[1:])
463
+ f = options.file[0]
464
+
465
+ if options.flamegraph:
466
+ print("--flamegraph is removed because the front-end supports native flamegraph now.")
467
+ print("You can select slices in the UI and do 'Slice Flamegraph'.")
468
+ return 1
469
+
470
+ if options.use_external_processor:
471
+ # Perfetto trace processor only accepts requests from localhost:10000
472
+ options.port = 10000
473
+ # external trace process won't work with once or directory
474
+ if options.once:
475
+ print("You can't use --once with --use_external_processor")
476
+ return 1
477
+ if os.path.isdir(f):
478
+ print("You can't use --use_external_processor on a directory")
479
+ return 1
480
+
481
+ if os.path.isdir(f):
482
+ cwd = os.getcwd()
483
+ try:
484
+ directory_viewer = DirectoryViewer(
485
+ path=f,
486
+ port=options.port,
487
+ server_only=options.server_only,
488
+ timeout=options.timeout,
489
+ use_external_processor=options.use_external_processor,
490
+ )
491
+ directory_viewer.run()
492
+ finally:
493
+ os.chdir(cwd)
494
+ elif os.path.exists(f):
495
+ path = os.path.abspath(options.file[0])
496
+ cwd = os.getcwd()
497
+ try:
498
+ server = ServerThread(
499
+ path,
500
+ port=options.port,
501
+ once=options.once,
502
+ timeout=options.timeout,
503
+ use_external_processor=options.use_external_processor,
504
+ )
505
+ server.start()
506
+ server.ready.wait()
507
+ if server.retcode is not None:
508
+ return server.retcode
509
+ if not options.server_only:
510
+ # import webbrowser only if necessary
511
+ import webbrowser
512
+ webbrowser.open_new_tab(f'http://127.0.0.1:{options.port}')
513
+ while server.is_alive():
514
+ server.join(timeout=1)
515
+ except KeyboardInterrupt:
516
+ if server.httpd is not None:
517
+ server.httpd.shutdown()
518
+ server.join(timeout=2)
519
+ finally:
520
+ os.chdir(cwd)
521
+ else:
522
+ print(f"File {f} does not exist!")
523
+ return 1
524
+
525
+ return 0
526
+
527
+
528
+ if __name__ == "__main__":
529
+ sys.exit(viewer_main())
@@ -0,0 +1,20 @@
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
+ from .event_base import _EventBase
5
+
6
+
7
+ class VizCounter(_EventBase):
8
+ def _viztracer_log(self) -> None:
9
+ if not self._viztracer_tracer:
10
+ return
11
+ d = {}
12
+ for attr in self._viztracer_get_attr_list():
13
+ if hasattr(self, attr):
14
+ val = self.__getattribute__(attr)
15
+ if not callable(val):
16
+ if type(val) is int or type(val) is float:
17
+ d[attr] = val
18
+ else:
19
+ raise Exception("Counter can only take numeric values")
20
+ self._viztracer_tracer.add_counter(self._viztracer_name, d)
viztracer/vizevent.py ADDED
@@ -0,0 +1,31 @@
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
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from .viztracer import VizTracer # pragma: no cover
9
+
10
+
11
+ class VizEvent:
12
+ def __init__(self, tracer: "VizTracer", event_name: str, file_name: str, lineno: int) -> None:
13
+ self._tracer = tracer
14
+ self.event_name = event_name
15
+ self.file_name = file_name
16
+ self.lineno = lineno
17
+ self.start = 0.0
18
+
19
+ def __enter__(self) -> None:
20
+ self.start = self._tracer.getts()
21
+
22
+ def __exit__(self, type, value, trace) -> None:
23
+ dur = self._tracer.getts() - self.start
24
+ raw_data = {
25
+ "ph": "X",
26
+ "name": f"{self.event_name} ({self.file_name}:{self.lineno})",
27
+ "ts": self.start,
28
+ "dur": dur,
29
+ "cat": "FEE",
30
+ }
31
+ self._tracer.add_raw(raw_data)
@@ -0,0 +1,20 @@
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
+ from logging import Handler, LogRecord
5
+
6
+ from .viztracer import VizTracer
7
+
8
+
9
+ class VizLoggingHandler(Handler):
10
+ def __init__(self, *args, **kwargs) -> None:
11
+ super().__init__(*args, **kwargs)
12
+ self._tracer: VizTracer | None = None
13
+
14
+ def emit(self, record: LogRecord) -> None:
15
+ if not self._tracer:
16
+ return
17
+ self._tracer.add_instant(f"logging - {self.format(record)}", scope="p")
18
+
19
+ def setTracer(self, tracer: VizTracer) -> None:
20
+ self._tracer = tracer
viztracer/vizobject.py ADDED
@@ -0,0 +1,28 @@
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
+ from .event_base import _EventBase
5
+ from .viztracer import VizTracer
6
+
7
+
8
+ class VizObject(_EventBase):
9
+ def __init__(self, tracer: VizTracer, name: str, **kwargs) -> None:
10
+ super().__init__(tracer, name, **kwargs)
11
+ self._viztracer_id = str(id(self))
12
+ if self._viztracer_tracer:
13
+ self._viztracer_tracer.add_object("N", self._viztracer_id, self._viztracer_name)
14
+
15
+ def __del__(self) -> None:
16
+ if self._viztracer_tracer:
17
+ self._viztracer_tracer.add_object("D", self._viztracer_id, self._viztracer_name)
18
+
19
+ def _viztracer_log(self, ph: str = "O") -> None:
20
+ if not self._viztracer_tracer:
21
+ return
22
+ d = {}
23
+ for attr in self._viztracer_get_attr_list():
24
+ if hasattr(self, attr):
25
+ val = self.__getattribute__(attr)
26
+ if type(val) is list or type(val) is dict or type(val) is int or type(val) is float or type(val) is str:
27
+ d[attr] = val
28
+ self._viztracer_tracer.add_object(ph, self._viztracer_id, self._viztracer_name, {"snapshot": d})