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.
- xprof/__init__.py +22 -0
- xprof/convert/_pywrap_profiler_plugin.so +0 -0
- xprof/convert/csv_writer.py +87 -0
- xprof/convert/raw_to_tool_data.py +232 -0
- xprof/convert/trace_events_json.py +105 -0
- xprof/integration_tests/tf_mnist.py +100 -0
- xprof/integration_tests/tf_profiler_session.py +40 -0
- xprof/integration_tests/tpu/tensorflow/tpu_tf2_keras_test.py +183 -0
- xprof/profile_plugin.py +1521 -0
- xprof/profile_plugin_loader.py +82 -0
- xprof/protobuf/dcn_collective_info_pb2.py +44 -0
- xprof/protobuf/dcn_slack_analysis_pb2.py +42 -0
- xprof/protobuf/diagnostics_pb2.py +36 -0
- xprof/protobuf/event_time_fraction_analyzer_pb2.py +42 -0
- xprof/protobuf/hardware_types_pb2.py +40 -0
- xprof/protobuf/hlo_stats_pb2.py +39 -0
- xprof/protobuf/inference_stats_pb2.py +86 -0
- xprof/protobuf/input_pipeline_pb2.py +52 -0
- xprof/protobuf/kernel_stats_pb2.py +38 -0
- xprof/protobuf/memory_profile_pb2.py +54 -0
- xprof/protobuf/memory_viewer_preprocess_pb2.py +49 -0
- xprof/protobuf/op_metrics_pb2.py +65 -0
- xprof/protobuf/op_profile_pb2.py +49 -0
- xprof/protobuf/op_stats_pb2.py +71 -0
- xprof/protobuf/overview_page_pb2.py +64 -0
- xprof/protobuf/pod_stats_pb2.py +45 -0
- xprof/protobuf/pod_viewer_pb2.py +61 -0
- xprof/protobuf/power_metrics_pb2.py +38 -0
- xprof/protobuf/roofline_model_pb2.py +42 -0
- xprof/protobuf/smart_suggestion_pb2.py +38 -0
- xprof/protobuf/source_info_pb2.py +36 -0
- xprof/protobuf/source_stats_pb2.py +48 -0
- xprof/protobuf/steps_db_pb2.py +76 -0
- xprof/protobuf/task_pb2.py +37 -0
- xprof/protobuf/tf_data_stats_pb2.py +72 -0
- xprof/protobuf/tf_function_pb2.py +52 -0
- xprof/protobuf/tf_stats_pb2.py +40 -0
- xprof/protobuf/tfstreamz_pb2.py +40 -0
- xprof/protobuf/topology_pb2.py +50 -0
- xprof/protobuf/tpu_input_pipeline_pb2.py +43 -0
- xprof/protobuf/trace_events_old_pb2.py +54 -0
- xprof/protobuf/trace_events_pb2.py +64 -0
- xprof/protobuf/trace_events_raw_pb2.py +45 -0
- xprof/protobuf/trace_filter_config_pb2.py +40 -0
- xprof/server.py +319 -0
- xprof/standalone/base_plugin.py +52 -0
- xprof/standalone/context.py +22 -0
- xprof/standalone/data_provider.py +32 -0
- xprof/standalone/plugin_asset_util.py +131 -0
- xprof/standalone/plugin_event_multiplexer.py +185 -0
- xprof/standalone/tensorboard_shim.py +31 -0
- xprof/static/bundle.js +130500 -0
- xprof/static/index.html +64 -0
- xprof/static/index.js +3 -0
- xprof/static/materialicons.woff2 +0 -0
- xprof/static/styles.css +1 -0
- xprof/static/trace_viewer_index.html +3929 -0
- xprof/static/trace_viewer_index.js +15906 -0
- xprof/static/zone.js +3558 -0
- xprof/version.py +17 -0
- xprof_nightly-2.22.3a20251208.dist-info/METADATA +301 -0
- xprof_nightly-2.22.3a20251208.dist-info/RECORD +65 -0
- xprof_nightly-2.22.3a20251208.dist-info/WHEEL +5 -0
- xprof_nightly-2.22.3a20251208.dist-info/entry_points.txt +5 -0
- 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 []
|