Skip to content

Commit 1cf0393

Browse files
committed
core: dump some debug info in /data/environment
Summary: In #4751, we implemented `data_location` support for RustBoard, which means that the location no longer includes the gRPC address. But while this is an improvement for users, the address was convenient for development, and there’s no other way to get it from the UI. This patch augments `/data/environment` to dump some basic debug info: the TensorBoard version, the data provider, and the command-line flags. This behavior can be turned off. It’s enabled in `tensorboard(1)`, but disabled by default on the `CorePlugin` and `CorePluginLoader` APIs. Thus, users who build their own TensorBoards will not find themselves unexpectedly leaking information about their setup. For normal users of `tensorboard(1)`, the flags are fine to render; the only potentially sensitive thing is the logdir path, which is of course already prominent. Sample response, passed through `jq .`: ``` { "data_location": "logs/mnist/", "window_title": "", "debug": { "version": "2.5.0a0", "data_provider": "GrpcDataProvider(addr='localhost:42127')", "flags": { "logdir": "logs/mnist/", "logdir_spec": "", "host": null, "bind_all": true, "port": null, "reuse_port": false, "load_fast": true, "grpc_creds_type": "local", "grpc_data_provider": "", "purge_orphaned_data": true, "db": "", "db_import": false, "inspect": false, "version_tb": false, "tag": "", "event_file": "", "path_prefix": "", "window_title": "", "max_reload_threads": 1, "reload_interval": 5, "reload_task": "auto", "reload_multifile": null, "reload_multifile_inactive_secs": 86400, "generic_data": "auto", "samples_per_plugin": { "scalars": 0, "images": 0 }, "custom_predict_fn": "", "wit_data_dir": "", "__tensorboard_subcommand": "serve" } } } ``` Test Plan: Manually navigated to `/data/environment` and verified that the flags render and the data provider appears for both `--load_fast` and the legacy multiplexer. Checked that when the change to `default.py` is reverted, no debug data shows up. wchargin-branch: environment-debug-info wchargin-source: 772a3783d6f19e4060b37fe43076b963cfd8d96e
1 parent 7258367 commit 1cf0393

File tree

6 files changed

+68
-10
lines changed

6 files changed

+68
-10
lines changed

tensorboard/backend/event_processing/data_provider.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def __init__(self, multiplexer, logdir):
4141
self._multiplexer = multiplexer
4242
self._logdir = logdir
4343

44+
def __str__(self):
45+
return "MultiplexerDataProvider(logdir=%r)" % self._logdir
46+
4447
def _validate_context(self, ctx):
4548
if type(ctx).__name__ != "RequestContext":
4649
raise TypeError("ctx must be a RequestContext; got: %r" % (ctx,))

tensorboard/data/grpc_provider.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def __init__(self, addr, stub):
4646
self._addr = addr
4747
self._stub = stub
4848

49+
def __str__(self):
50+
return "GrpcDataProvider(addr=%r)" % self._addr
51+
4952
def data_location(self, ctx, *, experiment_id):
5053
req = data_provider_pb2.GetExperimentRequest()
5154
req.experiment_id = experiment_id

tensorboard/default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class ExperimentalNpmiPlugin(
7171
# Ordering matters. The order in which these lines appear determines the
7272
# ordering of tabs in TensorBoard's GUI.
7373
_PLUGINS = [
74-
core_plugin.CorePluginLoader,
74+
core_plugin.CorePluginLoader(include_debug_info=True),
7575
scalars_plugin.ScalarsPlugin,
7676
custom_scalars_plugin.CustomScalarsPlugin,
7777
images_plugin.ImagesPlugin,

tensorboard/plugins/core/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ py_library(
1515
"//tensorboard/plugins:base_plugin",
1616
"//tensorboard/util:grpc_util",
1717
"//tensorboard/util:tb_logging",
18+
"//tensorboard:version",
1819
"@org_pocoo_werkzeug",
1920
],
2021
)

tensorboard/plugins/core/core_plugin.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from tensorboard.plugins import base_plugin
3232
from tensorboard.util import grpc_util
3333
from tensorboard.util import tb_logging
34+
from tensorboard import version
3435

3536
logger = tb_logging.get_logger()
3637

@@ -50,18 +51,24 @@ class CorePlugin(base_plugin.TBPlugin):
5051

5152
plugin_name = "core"
5253

53-
def __init__(self, context):
54+
def __init__(self, context, include_debug_info=None):
5455
"""Instantiates CorePlugin.
5556
5657
Args:
5758
context: A base_plugin.TBContext instance.
59+
include_debug_info: If true, `/data/environment` will include some
60+
basic information like the TensorBoard server version. Disabled by
61+
default to prevent surprising information leaks in custom builds of
62+
TensorBoard.
5863
"""
64+
self._flags = context.flags
5965
logdir_spec = context.flags.logdir_spec if context.flags else ""
6066
self._logdir = context.logdir or logdir_spec
6167
self._window_title = context.window_title
6268
self._path_prefix = context.flags.path_prefix if context.flags else None
6369
self._assets_zip_provider = context.assets_zip_provider
6470
self._data_provider = context.data_provider
71+
self._include_debug_info = bool(include_debug_info)
6572

6673
def is_active(self):
6774
return True
@@ -165,13 +172,7 @@ def _serve_index(self, index_asset_bytes, request):
165172

166173
@wrappers.Request.application
167174
def _serve_environment(self, request):
168-
"""Serve a JSON object containing some base properties used by the
169-
frontend.
170-
171-
* data_location is either a path to a directory or an address to a
172-
database (depending on which mode TensorBoard is running in).
173-
* window_title is the title of the TensorBoard web page.
174-
"""
175+
"""Serve a JSON object describing the TensorBoard parameters."""
175176
ctx = plugin_util.context(request.environ)
176177
experiment = plugin_util.experiment_id(request.environ)
177178
data_location = self._data_provider.data_location(
@@ -193,12 +194,38 @@ def _serve_environment(self, request):
193194
"creation_time": experiment_metadata.creation_time,
194195
}
195196
)
197+
if self._include_debug_info:
198+
environment["debug"] = {
199+
"version": version.VERSION,
200+
"data_provider": str(self._data_provider),
201+
"flags": self._render_flags(),
202+
}
196203
return http_util.Respond(
197204
request,
198205
environment,
199206
"application/json",
200207
)
201208

209+
def _render_flags(self):
210+
"""Return a JSON-and-human-friendly version of `self._flags`.
211+
212+
Like `json.loads(json.dumps(self._flags, default=str))` but
213+
without the wasteful serialization overhead.
214+
"""
215+
if self._flags is None:
216+
return None
217+
218+
def go(x):
219+
if isinstance(x, (type(None), str, int, float)):
220+
return x
221+
if isinstance(x, (list, tuple)):
222+
return [go(v) for v in x]
223+
if isinstance(x, dict):
224+
return {str(k): go(v) for (k, v) in x.items()}
225+
return str(x)
226+
227+
return go(vars(self._flags))
228+
202229
@wrappers.Request.application
203230
def _serve_logdir(self, request):
204231
"""Respond with a JSON object containing this TensorBoard's logdir."""
@@ -270,6 +297,9 @@ def _serve_experiment_runs(self, request):
270297
class CorePluginLoader(base_plugin.TBLoader):
271298
"""CorePlugin factory."""
272299

300+
def __init__(self, include_debug_info=None):
301+
self._include_debug_info = include_debug_info
302+
273303
def define_flags(self, parser):
274304
"""Adds standard TensorBoard CLI flags to parser."""
275305
parser.add_argument(
@@ -640,7 +670,7 @@ def fix_flags(self, flags):
640670

641671
def load(self, context):
642672
"""Creates CorePlugin instance."""
643-
return CorePlugin(context)
673+
return CorePlugin(context, include_debug_info=self._include_debug_info)
644674

645675

646676
def _gzip(bytestring):

tensorboard/plugins/core/core_plugin_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,27 @@ def testEnvironmentForLogdir(self):
190190
parsed_object = self._get_json(self.server, "/data/environment")
191191
self.assertEqual(parsed_object["data_location"], self.get_temp_dir())
192192

193+
def testEnvironmentDebugOffByDefault(self):
194+
parsed_object = self._get_json(self.server, "/data/environment")
195+
self.assertNotIn("debug", parsed_object)
196+
197+
def testEnvironmentDebugOnExplicitly(self):
198+
multiplexer = event_multiplexer.EventMultiplexer()
199+
logdir = self.get_temp_dir()
200+
provider = data_provider.MultiplexerDataProvider(multiplexer, logdir)
201+
context = base_plugin.TBContext(
202+
assets_zip_provider=get_test_assets_zip_provider(),
203+
logdir=logdir,
204+
data_provider=provider,
205+
window_title="title foo",
206+
)
207+
plugin = core_plugin.CorePlugin(context, include_debug_info=True)
208+
app = application.TensorBoardWSGI([plugin])
209+
server = werkzeug_test.Client(app, wrappers.BaseResponse)
210+
211+
parsed_object = self._get_json(server, "/data/environment")
212+
self.assertIn("debug", parsed_object)
213+
193214
def testLogdir(self):
194215
"""Test the format of the data/logdir endpoint."""
195216
parsed_object = self._get_json(self.server, "/data/logdir")

0 commit comments

Comments
 (0)