From 1cf0393cfbfdcd3fe006eeca6986f4f0788bf5f2 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 8 Mar 2021 21:46:56 -0800 Subject: [PATCH 1/3] core: dump some debug info in `/data/environment` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../backend/event_processing/data_provider.py | 3 ++ tensorboard/data/grpc_provider.py | 3 ++ tensorboard/default.py | 2 +- tensorboard/plugins/core/BUILD | 1 + tensorboard/plugins/core/core_plugin.py | 48 +++++++++++++++---- tensorboard/plugins/core/core_plugin_test.py | 21 ++++++++ 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/tensorboard/backend/event_processing/data_provider.py b/tensorboard/backend/event_processing/data_provider.py index a391ef1a45..5c29f55701 100644 --- a/tensorboard/backend/event_processing/data_provider.py +++ b/tensorboard/backend/event_processing/data_provider.py @@ -41,6 +41,9 @@ def __init__(self, multiplexer, logdir): self._multiplexer = multiplexer self._logdir = logdir + def __str__(self): + return "MultiplexerDataProvider(logdir=%r)" % self._logdir + def _validate_context(self, ctx): if type(ctx).__name__ != "RequestContext": raise TypeError("ctx must be a RequestContext; got: %r" % (ctx,)) diff --git a/tensorboard/data/grpc_provider.py b/tensorboard/data/grpc_provider.py index f83b60c98e..e84d4350e0 100644 --- a/tensorboard/data/grpc_provider.py +++ b/tensorboard/data/grpc_provider.py @@ -46,6 +46,9 @@ def __init__(self, addr, stub): self._addr = addr self._stub = stub + def __str__(self): + return "GrpcDataProvider(addr=%r)" % self._addr + def data_location(self, ctx, *, experiment_id): req = data_provider_pb2.GetExperimentRequest() req.experiment_id = experiment_id diff --git a/tensorboard/default.py b/tensorboard/default.py index cdd72ba65d..3efebced1e 100644 --- a/tensorboard/default.py +++ b/tensorboard/default.py @@ -71,7 +71,7 @@ class ExperimentalNpmiPlugin( # Ordering matters. The order in which these lines appear determines the # ordering of tabs in TensorBoard's GUI. _PLUGINS = [ - core_plugin.CorePluginLoader, + core_plugin.CorePluginLoader(include_debug_info=True), scalars_plugin.ScalarsPlugin, custom_scalars_plugin.CustomScalarsPlugin, images_plugin.ImagesPlugin, diff --git a/tensorboard/plugins/core/BUILD b/tensorboard/plugins/core/BUILD index b8a4b7feb9..9ed42af7c9 100644 --- a/tensorboard/plugins/core/BUILD +++ b/tensorboard/plugins/core/BUILD @@ -15,6 +15,7 @@ py_library( "//tensorboard/plugins:base_plugin", "//tensorboard/util:grpc_util", "//tensorboard/util:tb_logging", + "//tensorboard:version", "@org_pocoo_werkzeug", ], ) diff --git a/tensorboard/plugins/core/core_plugin.py b/tensorboard/plugins/core/core_plugin.py index c43a4636fc..9d7a9e7f60 100644 --- a/tensorboard/plugins/core/core_plugin.py +++ b/tensorboard/plugins/core/core_plugin.py @@ -31,6 +31,7 @@ from tensorboard.plugins import base_plugin from tensorboard.util import grpc_util from tensorboard.util import tb_logging +from tensorboard import version logger = tb_logging.get_logger() @@ -50,18 +51,24 @@ class CorePlugin(base_plugin.TBPlugin): plugin_name = "core" - def __init__(self, context): + def __init__(self, context, include_debug_info=None): """Instantiates CorePlugin. Args: context: A base_plugin.TBContext instance. + include_debug_info: If true, `/data/environment` will include some + basic information like the TensorBoard server version. Disabled by + default to prevent surprising information leaks in custom builds of + TensorBoard. """ + self._flags = context.flags logdir_spec = context.flags.logdir_spec if context.flags else "" self._logdir = context.logdir or logdir_spec self._window_title = context.window_title self._path_prefix = context.flags.path_prefix if context.flags else None self._assets_zip_provider = context.assets_zip_provider self._data_provider = context.data_provider + self._include_debug_info = bool(include_debug_info) def is_active(self): return True @@ -165,13 +172,7 @@ def _serve_index(self, index_asset_bytes, request): @wrappers.Request.application def _serve_environment(self, request): - """Serve a JSON object containing some base properties used by the - frontend. - - * data_location is either a path to a directory or an address to a - database (depending on which mode TensorBoard is running in). - * window_title is the title of the TensorBoard web page. - """ + """Serve a JSON object describing the TensorBoard parameters.""" ctx = plugin_util.context(request.environ) experiment = plugin_util.experiment_id(request.environ) data_location = self._data_provider.data_location( @@ -193,12 +194,38 @@ def _serve_environment(self, request): "creation_time": experiment_metadata.creation_time, } ) + if self._include_debug_info: + environment["debug"] = { + "version": version.VERSION, + "data_provider": str(self._data_provider), + "flags": self._render_flags(), + } return http_util.Respond( request, environment, "application/json", ) + def _render_flags(self): + """Return a JSON-and-human-friendly version of `self._flags`. + + Like `json.loads(json.dumps(self._flags, default=str))` but + without the wasteful serialization overhead. + """ + if self._flags is None: + return None + + def go(x): + if isinstance(x, (type(None), str, int, float)): + return x + if isinstance(x, (list, tuple)): + return [go(v) for v in x] + if isinstance(x, dict): + return {str(k): go(v) for (k, v) in x.items()} + return str(x) + + return go(vars(self._flags)) + @wrappers.Request.application def _serve_logdir(self, request): """Respond with a JSON object containing this TensorBoard's logdir.""" @@ -270,6 +297,9 @@ def _serve_experiment_runs(self, request): class CorePluginLoader(base_plugin.TBLoader): """CorePlugin factory.""" + def __init__(self, include_debug_info=None): + self._include_debug_info = include_debug_info + def define_flags(self, parser): """Adds standard TensorBoard CLI flags to parser.""" parser.add_argument( @@ -640,7 +670,7 @@ def fix_flags(self, flags): def load(self, context): """Creates CorePlugin instance.""" - return CorePlugin(context) + return CorePlugin(context, include_debug_info=self._include_debug_info) def _gzip(bytestring): diff --git a/tensorboard/plugins/core/core_plugin_test.py b/tensorboard/plugins/core/core_plugin_test.py index 269356fb3e..d662c35164 100644 --- a/tensorboard/plugins/core/core_plugin_test.py +++ b/tensorboard/plugins/core/core_plugin_test.py @@ -190,6 +190,27 @@ def testEnvironmentForLogdir(self): parsed_object = self._get_json(self.server, "/data/environment") self.assertEqual(parsed_object["data_location"], self.get_temp_dir()) + def testEnvironmentDebugOffByDefault(self): + parsed_object = self._get_json(self.server, "/data/environment") + self.assertNotIn("debug", parsed_object) + + def testEnvironmentDebugOnExplicitly(self): + multiplexer = event_multiplexer.EventMultiplexer() + logdir = self.get_temp_dir() + provider = data_provider.MultiplexerDataProvider(multiplexer, logdir) + context = base_plugin.TBContext( + assets_zip_provider=get_test_assets_zip_provider(), + logdir=logdir, + data_provider=provider, + window_title="title foo", + ) + plugin = core_plugin.CorePlugin(context, include_debug_info=True) + app = application.TensorBoardWSGI([plugin]) + server = werkzeug_test.Client(app, wrappers.BaseResponse) + + parsed_object = self._get_json(server, "/data/environment") + self.assertIn("debug", parsed_object) + def testLogdir(self): """Test the format of the data/logdir endpoint.""" parsed_object = self._get_json(self.server, "/data/logdir") From 185591ec9267188016df07b7502c46adaded3231 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Mon, 8 Mar 2021 21:51:12 -0800 Subject: [PATCH 2/3] [environment-debug-info: fix buildifier] wchargin-branch: environment-debug-info wchargin-source: e55a96e2e1623792787aebc9603c54da67ad6e85 --- tensorboard/plugins/core/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tensorboard/plugins/core/BUILD b/tensorboard/plugins/core/BUILD index 9ed42af7c9..7ba15fef7d 100644 --- a/tensorboard/plugins/core/BUILD +++ b/tensorboard/plugins/core/BUILD @@ -11,11 +11,11 @@ py_library( srcs_version = "PY3", deps = [ "//tensorboard:plugin_util", + "//tensorboard:version", "//tensorboard/backend:http_util", "//tensorboard/plugins:base_plugin", "//tensorboard/util:grpc_util", "//tensorboard/util:tb_logging", - "//tensorboard:version", "@org_pocoo_werkzeug", ], ) From da59f822ff5c9d9022dbf1d8760d24bf397df860 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Thu, 11 Mar 2021 11:59:33 -0800 Subject: [PATCH 3/3] [environment-debug-info: update patch] wchargin-branch: environment-debug-info wchargin-source: 35e9a6c306c132075e402bf6d74878237b65c806 --- tensorboard/http_api.md | 10 ++++++++++ tensorboard/plugins/core/core_plugin.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tensorboard/http_api.md b/tensorboard/http_api.md index cbc90b8516..2d29e50648 100644 --- a/tensorboard/http_api.md +++ b/tensorboard/http_api.md @@ -161,12 +161,22 @@ Example response: Returns environment in which the TensorBoard app is running. +The `version` is the Pip version string of the TensorBoard server, like +`"2.4.0a0"`. + +The `window_title` is the value of the `--window_title` flag. + The `data_location` is a user-readable string describing the source from which TensorBoard is reading data, such as a directory on disk. +The response may also include a `debug` field, with information that may be +useful for humans inspecting the system. The contents and structure of `debug` +are subject to change. + Example response: { + "version": "2.4.0", "window_title": "Custom Name", "data_location": "/Users/tbuser/tensorboard_data/" } diff --git a/tensorboard/plugins/core/core_plugin.py b/tensorboard/plugins/core/core_plugin.py index 9d7a9e7f60..f9770ce9bd 100644 --- a/tensorboard/plugins/core/core_plugin.py +++ b/tensorboard/plugins/core/core_plugin.py @@ -183,6 +183,7 @@ def _serve_environment(self, request): ) environment = { + "version": version.VERSION, "data_location": data_location, "window_title": self._window_title, } @@ -196,7 +197,6 @@ def _serve_environment(self, request): ) if self._include_debug_info: environment["debug"] = { - "version": version.VERSION, "data_provider": str(self._data_provider), "flags": self._render_flags(), }