xprof-nightly 2.22.3a20251208__cp311-none-manylinux2014_x86_64.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 (65) hide show
  1. xprof/__init__.py +22 -0
  2. xprof/convert/_pywrap_profiler_plugin.so +0 -0
  3. xprof/convert/csv_writer.py +87 -0
  4. xprof/convert/raw_to_tool_data.py +232 -0
  5. xprof/convert/trace_events_json.py +105 -0
  6. xprof/integration_tests/tf_mnist.py +100 -0
  7. xprof/integration_tests/tf_profiler_session.py +40 -0
  8. xprof/integration_tests/tpu/tensorflow/tpu_tf2_keras_test.py +183 -0
  9. xprof/profile_plugin.py +1521 -0
  10. xprof/profile_plugin_loader.py +82 -0
  11. xprof/protobuf/dcn_collective_info_pb2.py +44 -0
  12. xprof/protobuf/dcn_slack_analysis_pb2.py +42 -0
  13. xprof/protobuf/diagnostics_pb2.py +36 -0
  14. xprof/protobuf/event_time_fraction_analyzer_pb2.py +42 -0
  15. xprof/protobuf/hardware_types_pb2.py +40 -0
  16. xprof/protobuf/hlo_stats_pb2.py +39 -0
  17. xprof/protobuf/inference_stats_pb2.py +86 -0
  18. xprof/protobuf/input_pipeline_pb2.py +52 -0
  19. xprof/protobuf/kernel_stats_pb2.py +38 -0
  20. xprof/protobuf/memory_profile_pb2.py +54 -0
  21. xprof/protobuf/memory_viewer_preprocess_pb2.py +49 -0
  22. xprof/protobuf/op_metrics_pb2.py +65 -0
  23. xprof/protobuf/op_profile_pb2.py +49 -0
  24. xprof/protobuf/op_stats_pb2.py +71 -0
  25. xprof/protobuf/overview_page_pb2.py +64 -0
  26. xprof/protobuf/pod_stats_pb2.py +45 -0
  27. xprof/protobuf/pod_viewer_pb2.py +61 -0
  28. xprof/protobuf/power_metrics_pb2.py +38 -0
  29. xprof/protobuf/roofline_model_pb2.py +42 -0
  30. xprof/protobuf/smart_suggestion_pb2.py +38 -0
  31. xprof/protobuf/source_info_pb2.py +36 -0
  32. xprof/protobuf/source_stats_pb2.py +48 -0
  33. xprof/protobuf/steps_db_pb2.py +76 -0
  34. xprof/protobuf/task_pb2.py +37 -0
  35. xprof/protobuf/tf_data_stats_pb2.py +72 -0
  36. xprof/protobuf/tf_function_pb2.py +52 -0
  37. xprof/protobuf/tf_stats_pb2.py +40 -0
  38. xprof/protobuf/tfstreamz_pb2.py +40 -0
  39. xprof/protobuf/topology_pb2.py +50 -0
  40. xprof/protobuf/tpu_input_pipeline_pb2.py +43 -0
  41. xprof/protobuf/trace_events_old_pb2.py +54 -0
  42. xprof/protobuf/trace_events_pb2.py +64 -0
  43. xprof/protobuf/trace_events_raw_pb2.py +45 -0
  44. xprof/protobuf/trace_filter_config_pb2.py +40 -0
  45. xprof/server.py +319 -0
  46. xprof/standalone/base_plugin.py +52 -0
  47. xprof/standalone/context.py +22 -0
  48. xprof/standalone/data_provider.py +32 -0
  49. xprof/standalone/plugin_asset_util.py +131 -0
  50. xprof/standalone/plugin_event_multiplexer.py +185 -0
  51. xprof/standalone/tensorboard_shim.py +31 -0
  52. xprof/static/bundle.js +130500 -0
  53. xprof/static/index.html +64 -0
  54. xprof/static/index.js +3 -0
  55. xprof/static/materialicons.woff2 +0 -0
  56. xprof/static/styles.css +1 -0
  57. xprof/static/trace_viewer_index.html +3929 -0
  58. xprof/static/trace_viewer_index.js +15906 -0
  59. xprof/static/zone.js +3558 -0
  60. xprof/version.py +17 -0
  61. xprof_nightly-2.22.3a20251208.dist-info/METADATA +301 -0
  62. xprof_nightly-2.22.3a20251208.dist-info/RECORD +65 -0
  63. xprof_nightly-2.22.3a20251208.dist-info/WHEEL +5 -0
  64. xprof_nightly-2.22.3a20251208.dist-info/entry_points.txt +5 -0
  65. xprof_nightly-2.22.3a20251208.dist-info/top_level.txt +1 -0
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: plugin/xprof/protobuf/trace_events_raw.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'plugin/xprof/protobuf/trace_events_raw.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n,plugin/xprof/protobuf/trace_events_raw.proto\x12\x13tensorflow.profiler\"\xc0\x01\n\x07RawData\x12\x38\n\x0c\x64ma_activity\x18\x01 \x01(\x0b\x32 .tensorflow.profiler.DmaActivityH\x00\x12\x38\n\x04\x61rgs\x18\x02 \x01(\x0b\x32(.tensorflow.profiler.TraceEventArgumentsH\x00\x12\x35\n\x08tpu_data\x18\x03 \x01(\x0b\x32!.tensorflow.profiler.TpuTraceDataH\x00\x42\n\n\x08raw_data\"\xcf\x01\n\x0b\x44maActivity\x12\x19\n\x11start_time_cycles\x18\x01 \x01(\x04\x12\x17\n\x0f\x65nd_time_cycles\x18\x02 \x01(\x04\x12\x11\n\tkilobytes\x18\x04 \x01(\x04\x12\x14\n\x0cmesh_chip_id\x18\x05 \x01(\r\x12\x0f\n\x07\x63ore_id\x18\x0b \x01(\r\x12\x13\n\x0b\x64ma_address\x18\x06 \x01(\x04\x12\x11\n\tmulticast\x18\x08 \x01(\r\x12\x11\n\tsegmented\x18\t \x01(\r\x12\x11\n\ttemporary\x18\n \x01(\x04J\x04\x08\x03\x10\x04\"\xe6\x01\n\x13TraceEventArguments\x12>\n\x03\x61rg\x18\x01 \x03(\x0b\x32\x31.tensorflow.profiler.TraceEventArguments.Argument\x1a\x8e\x01\n\x08\x41rgument\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\tstr_value\x18\x02 \x01(\tH\x00\x12\x14\n\nuint_value\x18\x03 \x01(\x04H\x00\x12\x13\n\tint_value\x18\x05 \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00\x12\x13\n\tref_value\x18\x06 \x01(\x06H\x00\x42\x07\n\x05value\"\x1d\n\x0cTpuTraceData\x12\r\n\x05\x64ummy\x18\x01 \x01(\rB\x03\xf8\x01\x01')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'plugin.xprof.protobuf.trace_events_raw_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'\370\001\001'
35
+ _globals['_RAWDATA']._serialized_start=70
36
+ _globals['_RAWDATA']._serialized_end=262
37
+ _globals['_DMAACTIVITY']._serialized_start=265
38
+ _globals['_DMAACTIVITY']._serialized_end=472
39
+ _globals['_TRACEEVENTARGUMENTS']._serialized_start=475
40
+ _globals['_TRACEEVENTARGUMENTS']._serialized_end=705
41
+ _globals['_TRACEEVENTARGUMENTS_ARGUMENT']._serialized_start=563
42
+ _globals['_TRACEEVENTARGUMENTS_ARGUMENT']._serialized_end=705
43
+ _globals['_TPUTRACEDATA']._serialized_start=707
44
+ _globals['_TPUTRACEDATA']._serialized_end=736
45
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,40 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: plugin/xprof/protobuf/trace_filter_config.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'plugin/xprof/protobuf/trace_filter_config.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n/plugin/xprof/protobuf/trace_filter_config.proto\x12\x13tensorflow.profiler\"\xc0\x02\n\x10TraceEventFilter\x12\x12\n\nfield_name\x18\x05 \x01(\t\x12=\n\x05op_id\x18\x06 \x01(\x0e\x32..tensorflow.profiler.TraceEventFilter.Operator\x12\x10\n\x08negation\x18\x07 \x01(\x08\x12\x13\n\tstr_value\x18\x08 \x01(\tH\x00\x12\x15\n\x0bregex_value\x18\t \x01(\tH\x00\x12\x14\n\nuint_value\x18\n \x01(\x04H\x00\x12\x13\n\tint_value\x18\x0c \x01(\x03H\x00\x12\x16\n\x0c\x64ouble_value\x18\x0b \x01(\x01H\x00\"O\n\x08Operator\x12\t\n\x05OP_EQ\x10\x00\x12\t\n\x05OP_LT\x10\x01\x12\t\n\x05OP_GT\x10\x02\x12\t\n\x05OP_LE\x10\x03\x12\t\n\x05OP_GE\x10\x04\x12\x0c\n\x08OP_REGEX\x10\x05\x42\x07\n\x05value\"\xe3\x01\n\x11TraceFilterConfig\x12\x16\n\x0e\x64\x65vice_regexes\x18\x01 \x03(\t\x12\x18\n\x10resource_regexes\x18\x02 \x03(\t\x12\x42\n\x13trace_event_filters\x18\x03 \x03(\x0b\x32%.tensorflow.profiler.TraceEventFilter\x12\x46\n\x17trace_event_arg_filters\x18\x04 \x03(\x0b\x32%.tensorflow.profiler.TraceEventFilter\x12\x10\n\x08negation\x18\x05 \x01(\x08\x62\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'plugin.xprof.protobuf.trace_filter_config_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ DESCRIPTOR._loaded_options = None
34
+ _globals['_TRACEEVENTFILTER']._serialized_start=73
35
+ _globals['_TRACEEVENTFILTER']._serialized_end=393
36
+ _globals['_TRACEEVENTFILTER_OPERATOR']._serialized_start=305
37
+ _globals['_TRACEEVENTFILTER_OPERATOR']._serialized_end=384
38
+ _globals['_TRACEFILTERCONFIG']._serialized_start=396
39
+ _globals['_TRACEFILTERCONFIG']._serialized_end=623
40
+ # @@protoc_insertion_point(module_scope)
xprof/server.py ADDED
@@ -0,0 +1,319 @@
1
+ # Copyright 2025 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Utilities to start up a standalone webserver."""
16
+
17
+ import argparse
18
+ import collections
19
+ import dataclasses
20
+ import socket
21
+ import sys
22
+ from typing import Optional
23
+
24
+ from cheroot import wsgi
25
+ from etils import epath
26
+
27
+ from xprof import profile_plugin_loader
28
+ from xprof.standalone import base_plugin
29
+ from xprof.standalone import plugin_event_multiplexer
30
+ from xprof.convert import _pywrap_profiler_plugin
31
+
32
+ DataProvider = plugin_event_multiplexer.DataProvider
33
+ TBContext = base_plugin.TBContext
34
+ ProfilePluginLoader = profile_plugin_loader.ProfilePluginLoader
35
+
36
+
37
+ _DEFAULT_GRPC_PORT = 50051
38
+
39
+
40
+ @dataclasses.dataclass(frozen=True)
41
+ class ServerConfig:
42
+ """Configuration parameters for launching the XProf server.
43
+
44
+ This dataclass holds all the settings required to initialize and run the XProf
45
+ profiling server, including network ports, log locations, and feature flags.
46
+ """
47
+
48
+ logdir: Optional[str]
49
+ port: int
50
+ grpc_port: int
51
+ worker_service_address: str
52
+ hide_capture_profile_button: bool
53
+
54
+
55
+ def make_wsgi_app(plugin):
56
+ """Create a WSGI application for the standalone server."""
57
+
58
+ apps = plugin.get_plugin_apps()
59
+
60
+ prefix = "/data/plugin/profile"
61
+
62
+ def application(environ, start_response):
63
+ path = environ["PATH_INFO"]
64
+ if path.startswith(prefix):
65
+ path = path[len(prefix) :]
66
+ if path != "/" and path.endswith("/"):
67
+ path = path[:-1]
68
+ handler = apps.get(path, plugin.default_handler)
69
+ return handler(environ, start_response)
70
+
71
+ return application
72
+
73
+
74
+ def run_server(plugin, host, port):
75
+ """Starts a webserver for the standalone server."""
76
+
77
+ app = make_wsgi_app(plugin)
78
+
79
+ server = wsgi.Server((host, port), app)
80
+
81
+ try:
82
+ print(f"XProf at http://localhost:{port}/ (Press CTRL+C to quit)")
83
+ server.start()
84
+ except KeyboardInterrupt:
85
+ server.stop()
86
+
87
+
88
+ def _get_wildcard_address(port) -> str:
89
+ """Returns a wildcard address for the port in question.
90
+
91
+ This will attempt to follow the best practice of calling
92
+ getaddrinfo() with a null host and AI_PASSIVE to request a
93
+ server-side socket wildcard address. If that succeeds, this
94
+ returns the first IPv6 address found, or if none, then returns
95
+ the first IPv4 address. If that fails, then this returns the
96
+ hardcoded address "::" if socket.has_ipv6 is True, else
97
+ "0.0.0.0".
98
+
99
+ Args:
100
+ port: The port number.
101
+
102
+ Returns:
103
+ The wildcard address.
104
+ """
105
+ fallback_address = "::" if socket.has_ipv6 else "0.0.0.0"
106
+ if hasattr(socket, "AI_PASSIVE"):
107
+ try:
108
+ addrinfos = socket.getaddrinfo(
109
+ None,
110
+ port,
111
+ socket.AF_UNSPEC,
112
+ socket.SOCK_STREAM,
113
+ socket.IPPROTO_TCP,
114
+ socket.AI_PASSIVE,
115
+ )
116
+ except socket.gaierror:
117
+ return fallback_address
118
+ addrs_by_family = collections.defaultdict(list)
119
+ for family, _, _, _, sockaddr in addrinfos:
120
+ # Format of the "sockaddr" socket address varies by address family,
121
+ # but [0] is always the IP address portion.
122
+ addrs_by_family[family].append(sockaddr[0])
123
+ if hasattr(socket, "AF_INET6") and addrs_by_family[socket.AF_INET6]:
124
+ return addrs_by_family[socket.AF_INET6][0]
125
+ if hasattr(socket, "AF_INET") and addrs_by_family[socket.AF_INET]:
126
+ return addrs_by_family[socket.AF_INET][0]
127
+ return fallback_address
128
+
129
+
130
+ def _launch_server(
131
+ config: ServerConfig,
132
+ ):
133
+ """Initializes and launches the main XProf server.
134
+
135
+ This function sets up the necessary components for the XProf server based on
136
+ the provided configuration. It starts the gRPC worker service if distributed
137
+ processing is enabled, creates the TensorBoard context, loads the profile
138
+ plugin, and finally starts the web server to handle HTTP requests.
139
+
140
+ Args:
141
+ config: The ServerConfig object containing all server settings.
142
+ """
143
+ _pywrap_profiler_plugin.initialize_stubs(config.worker_service_address)
144
+ _pywrap_profiler_plugin.start_grpc_server(config.grpc_port)
145
+
146
+ context = TBContext(
147
+ config.logdir, DataProvider(config.logdir), TBContext.Flags(False)
148
+ )
149
+ context.hide_capture_profile_button = config.hide_capture_profile_button
150
+ loader = ProfilePluginLoader()
151
+ plugin = loader.load(context)
152
+ run_server(plugin, _get_wildcard_address(config.port), config.port)
153
+
154
+
155
+ def get_abs_path(logdir: str) -> str:
156
+ """Gets the absolute path for a given log directory string.
157
+
158
+ This function correctly handles both Google Cloud Storage (GCS) paths and
159
+ local filesystem paths.
160
+
161
+ - GCS paths (e.g., "gs://bucket/log") are returned as is.
162
+ - Local filesystem paths (e.g., "~/logs", "log", ".") are made absolute.
163
+
164
+ Args:
165
+ logdir: The path string.
166
+
167
+ Returns:
168
+ The corresponding absolute path as a string.
169
+ """
170
+ if logdir.startswith("gs://"):
171
+ return logdir
172
+
173
+ return str(epath.Path(logdir).expanduser().resolve())
174
+
175
+
176
+ def _create_argument_parser() -> argparse.ArgumentParser:
177
+ """Creates and configures the argument parser for the XProf server CLI.
178
+
179
+ This function sets up argparse to handle command-line flags for specifying
180
+ the log directory, server port, and other operational modes.
181
+
182
+ Returns:
183
+ The configured argument parser.
184
+ """
185
+ parser = argparse.ArgumentParser(
186
+ prog="xprof",
187
+ description="Launch the XProf profiling server.",
188
+ formatter_class=argparse.RawDescriptionHelpFormatter,
189
+ epilog=(
190
+ "Examples:\n"
191
+ "\txprof ~/jax/profile-logs -p 8080\n"
192
+ "\txprof --logdir ~/jax/profile-logs -p 8080"
193
+ ),
194
+ )
195
+
196
+ logdir_group = parser.add_mutually_exclusive_group(required=False)
197
+
198
+ logdir_group.add_argument(
199
+ "-l",
200
+ "--logdir",
201
+ dest="logdir_opt",
202
+ metavar="<logdir>",
203
+ type=str,
204
+ help="The directory where profile files will be stored.",
205
+ )
206
+
207
+ logdir_group.add_argument(
208
+ "logdir_pos",
209
+ nargs="?",
210
+ metavar="logdir",
211
+ type=str,
212
+ default=None,
213
+ help="Positional argument for the profile log directory.",
214
+ )
215
+
216
+ parser.add_argument(
217
+ "-p",
218
+ "--port",
219
+ metavar="<port>",
220
+ type=int,
221
+ default=8791,
222
+ help="The port number for the server (default: %(default)s).",
223
+ )
224
+
225
+ parser.add_argument(
226
+ "-hcpb",
227
+ "--hide_capture_profile_button",
228
+ action="store_true",
229
+ default=False,
230
+ help="Hides the 'Capture Profile' button in the UI.",
231
+ )
232
+
233
+ parser.add_argument(
234
+ "-wsa",
235
+ "--worker_service_address",
236
+ type=str,
237
+ default=None,
238
+ help=(
239
+ "A comma-separated list of worker service addresses (IPs or FQDNs)"
240
+ " with their gRPC ports, used in distributed profiling. Example:"
241
+ " 'worker-a.project.internal:50051,worker-b.project.internal:50051'."
242
+ " If not provided, it will use 0.0.0.0 with the gRPC port."
243
+ ),
244
+ )
245
+
246
+ parser.add_argument(
247
+ "-gp",
248
+ "--grpc_port",
249
+ type=int,
250
+ default=_DEFAULT_GRPC_PORT,
251
+ help=(
252
+ "The port for the gRPC server, which runs alongside the main HTTP"
253
+ " server for distributed profiling. This must be different from the"
254
+ " main server port (--port)."
255
+ ),
256
+ )
257
+ return parser
258
+
259
+
260
+ def main() -> int:
261
+ """Parses command-line arguments and launches the XProf server.
262
+
263
+ This is the main entry point for the XProf server application. It parses
264
+ command-line arguments, creates a ServerConfig, and then launches the
265
+ server.
266
+
267
+ Returns:
268
+ An exit code, 0 for success and non-zero for errors.
269
+ """
270
+ parser = _create_argument_parser()
271
+ try:
272
+ args = parser.parse_args()
273
+ except SystemExit as e:
274
+ return e.code
275
+
276
+ logdir = (
277
+ get_abs_path(args.logdir_opt or args.logdir_pos)
278
+ if args.logdir_opt or args.logdir_pos
279
+ else None
280
+ )
281
+
282
+ worker_service_address = args.worker_service_address
283
+ if worker_service_address is None:
284
+ worker_service_address = f"0.0.0.0:{args.grpc_port}"
285
+
286
+ config = ServerConfig(
287
+ logdir=logdir,
288
+ port=args.port,
289
+ grpc_port=args.grpc_port,
290
+ worker_service_address=worker_service_address,
291
+ hide_capture_profile_button=args.hide_capture_profile_button,
292
+ )
293
+
294
+ print("Attempting to start XProf server:")
295
+ print(f" Log Directory: {logdir}")
296
+ print(f" Port: {config.port}")
297
+ print(f" Worker Service Address: {config.worker_service_address}")
298
+ print(f" Hide Capture Button: {config.hide_capture_profile_button}")
299
+
300
+ if logdir and not epath.Path(logdir).exists():
301
+ print(
302
+ f"Error: Log directory '{logdir}' does not exist or is not a"
303
+ " directory.",
304
+ file=sys.stderr,
305
+ )
306
+ return 1
307
+
308
+ if config.port == config.grpc_port:
309
+ print(
310
+ "Error: The main server port (--port) and the gRPC port (--grpc_port)"
311
+ " must be different.",
312
+ file=sys.stderr,
313
+ )
314
+ return 1
315
+
316
+ _launch_server(
317
+ config,
318
+ )
319
+ return 0
@@ -0,0 +1,52 @@
1
+ # Copyright 2025 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Base plugin classes and context for the TensorBoard free plugin."""
16
+
17
+ from typing import Any
18
+
19
+ from xprof.standalone import plugin_event_multiplexer
20
+
21
+
22
+ class TBPlugin:
23
+ pass
24
+
25
+
26
+ class FrontendMetadata:
27
+ def __init__(self, es_module_path: str):
28
+ self.es_module_path = es_module_path
29
+
30
+
31
+ class TBLoader:
32
+ pass
33
+
34
+
35
+ class TBContext():
36
+ """https://github.com/tensorflow/tensorboard/blob/a7178f4f622a786463d23ef645e0f16f6ea7a1cb/tensorboard/plugins/base_plugin.py#L229."""
37
+
38
+ class Flags():
39
+ def __init__(self, master_tpu_unsecure_channel):
40
+ self.master_tpu_unsecure_channel = master_tpu_unsecure_channel
41
+
42
+ def __init__(
43
+ self,
44
+ logdir: str,
45
+ data_provider: plugin_event_multiplexer.DataProvider,
46
+ flags: dict[str, Any],
47
+ multiplexer=None,
48
+ ):
49
+ self.logdir = logdir
50
+ self.data_provider = data_provider
51
+ self.flags = flags
52
+ self.multiplexer = multiplexer
@@ -0,0 +1,22 @@
1
+ # Copyright 2025 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """A standalone version of Tensorboard's context and utils."""
16
+
17
+
18
+ class RequestContext:
19
+ """Overload of tensorboard/context.py:RequestContext."""
20
+
21
+ def __init__(self):
22
+ pass
@@ -0,0 +1,32 @@
1
+ # Copyright 2025 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """A standalone version of Tensorboard's context and utils."""
16
+
17
+
18
+ class MultiplexerDataProvider:
19
+ """Data provider that reads data from a multiplexer."""
20
+
21
+ def __init__(self, multiplexer, logdir):
22
+ """Creates a new MultiplexerDataProvider.
23
+
24
+ Args:
25
+ multiplexer: A multiplexer object.
26
+ logdir: The log directory.
27
+ """
28
+ self.multiplexer = multiplexer
29
+ self.logdir = logdir
30
+
31
+ def list_runs(self, *args, **kwargs): # pylint: disable=invalid-name
32
+ return self.multiplexer.list_runs(*args, **kwargs)
@@ -0,0 +1,131 @@
1
+ # Copyright 2025 The TensorFlow Authors. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+ """Utility functions for plugin assets."""
16
+
17
+ import os
18
+ import urllib.parse
19
+ import fsspec
20
+
21
+ _PLUGINS_DIR = "plugins"
22
+ # In the future, if S3 support is needed, "s3://" can be added here.
23
+ _PROTOCOL_PREFIXES = ("gs://",)
24
+
25
+
26
+ def _get_protocol_from_parsed_url_scheme(scheme: str) -> str:
27
+ """Returns the protocol from the parsed URL scheme.
28
+
29
+ If the scheme is not recognized, returns "file" as the default protocol.
30
+
31
+ Args:
32
+ scheme: The parsed URL scheme.
33
+
34
+ Returns:
35
+ The protocol from the parsed URL scheme.
36
+ """
37
+ if not scheme:
38
+ return "file"
39
+ for prefix in _PROTOCOL_PREFIXES:
40
+ if prefix.startswith(scheme):
41
+ return scheme
42
+ return "file"
43
+
44
+
45
+ def get_fs_protocol(path):
46
+ string_path = str(path)
47
+ parsed_url = urllib.parse.urlparse(string_path)
48
+ protocol = _get_protocol_from_parsed_url_scheme(parsed_url.scheme)
49
+ return fsspec.filesystem(protocol)
50
+
51
+
52
+ def PluginDirectory(logdir, plugin_name): # pylint: disable=invalid-name
53
+ return os.path.join(logdir, _PLUGINS_DIR, plugin_name)
54
+
55
+
56
+ def walk_with_fsspec(top):
57
+ """Mimics os.walk using fsspec."""
58
+ try:
59
+ parsed_url = urllib.parse.urlparse(top)
60
+ protocol = parsed_url.scheme or "file"
61
+ fs = fsspec.filesystem(protocol)
62
+
63
+ # Use a queue for breadth-first traversal
64
+ queue = [top]
65
+
66
+ while queue:
67
+ current_dir = queue.pop(0)
68
+ dirnames = []
69
+ filenames = []
70
+
71
+ try:
72
+ items = fs.listdir(current_dir)
73
+ except FileNotFoundError:
74
+ continue # skip if the directory is not found.
75
+
76
+ for item in items:
77
+ full_path = os.path.join(current_dir, item["name"])
78
+ if item["type"] == "directory":
79
+ dirnames.append(item["name"])
80
+ queue.append(full_path)
81
+ else:
82
+ filenames.append(item["name"])
83
+
84
+ yield (current_dir, dirnames, filenames)
85
+
86
+ except OSError as e:
87
+ print(f"An error occurred: {e}")
88
+ return
89
+
90
+
91
+ def _reconstruct_path(base_path: str, item_path: str) -> str:
92
+ """Prepends a protocol to the item path if the base path has one.
93
+
94
+ This handles inconsistencies in fsspec's listdir output:
95
+ - GCS: returns paths without the 'gs://' protocol.
96
+ - Local: returns absolute paths.
97
+ This function reconstructs the full path by adding the protocol if needed.
98
+
99
+ Args:
100
+ base_path: The base path that may have a protocol (e.g., 'gs://...').
101
+ item_path: The path to an item, which may be missing the protocol.
102
+
103
+ Returns:
104
+ The item path, with the protocol prepended if necessary.
105
+ """
106
+ prefix = next((p for p in _PROTOCOL_PREFIXES if base_path.startswith(p)), "")
107
+ return prefix + item_path
108
+
109
+
110
+ def iterate_directory_with_fsspec(plugin_dir):
111
+ """Replaces upath.UPath(plugin_dir).iterdir() with fsspec."""
112
+ try:
113
+ fs = get_fs_protocol(plugin_dir)
114
+ for item in fs.listdir(plugin_dir):
115
+ yield _reconstruct_path(plugin_dir, item["name"])
116
+ except FileNotFoundError:
117
+ # Handle cases where the directory doesn't exist.
118
+ print(f"Directory not found: {plugin_dir}")
119
+ return
120
+
121
+
122
+ def ListAssets(logdir, plugin_name): # pylint: disable=invalid-name
123
+ plugin_dir = PluginDirectory(logdir, plugin_name)
124
+ try:
125
+ # Strip trailing slashes, which listdir() includes for some filesystems.
126
+ return [
127
+ x.rstrip("/")[len(plugin_dir) + 1 :]
128
+ for x in iterate_directory_with_fsspec(plugin_dir)
129
+ ]
130
+ except FileNotFoundError:
131
+ return []