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