diff --git a/tensorboard/backend/application.py b/tensorboard/backend/application.py index f2f7c0e232..726922f2fd 100644 --- a/tensorboard/backend/application.py +++ b/tensorboard/backend/application.py @@ -138,7 +138,9 @@ def standard_tensorboard_wsgi(flags, plugin_loaders, assets_zip_provider): max_reload_threads=flags.max_reload_threads, event_file_active_filter=_get_event_file_active_filter(flags)) if flags.generic_data != 'false': - data_provider = event_data_provider.MultiplexerDataProvider(multiplexer) + data_provider = event_data_provider.MultiplexerDataProvider( + multiplexer, flags.logdir + ) if reload_interval >= 0: # We either reload the multiplexer once when TensorBoard starts up, or we diff --git a/tensorboard/backend/event_processing/data_provider.py b/tensorboard/backend/event_processing/data_provider.py index ef60232058..0b354aa8ec 100644 --- a/tensorboard/backend/event_processing/data_provider.py +++ b/tensorboard/backend/event_processing/data_provider.py @@ -29,14 +29,17 @@ class MultiplexerDataProvider(provider.DataProvider): - def __init__(self, multiplexer): + def __init__(self, multiplexer, logdir): """Trivial initializer. Args: multiplexer: A `plugin_event_multiplexer.EventMultiplexer` (note: not a boring old `event_multiplexer.EventMultiplexer`). + logdir: The log directory from which data is being read. Only used + cosmetically. Should be a `str`. """ self._multiplexer = multiplexer + self._logdir = logdir def _test_run_tag(self, run_tag_filter, run, tag): runs = run_tag_filter.runs @@ -53,6 +56,10 @@ def _get_first_event_timestamp(self, run_name): except ValueError as e: return None + def data_location(self, experiment_id): + del experiment_id # ignored + return str(self._logdir) + def list_runs(self, experiment_id): del experiment_id # ignored for now return [ diff --git a/tensorboard/backend/event_processing/data_provider_test.py b/tensorboard/backend/event_processing/data_provider_test.py index 6c8b6c6bf3..ca0914a386 100644 --- a/tensorboard/backend/event_processing/data_provider_test.py +++ b/tensorboard/backend/event_processing/data_provider_test.py @@ -75,7 +75,13 @@ def create_multiplexer(self): return multiplexer def create_provider(self): - return data_provider.MultiplexerDataProvider(self.create_multiplexer()) + multiplexer = self.create_multiplexer() + return data_provider.MultiplexerDataProvider(multiplexer, self.logdir) + + def test_data_location(self): + provider = self.create_provider() + result = provider.data_location(experiment_id="unused") + self.assertEqual(result, self.logdir) def test_list_runs(self): # We can't control the timestamps of events written to disk (without @@ -102,7 +108,7 @@ def FirstEventTimestamp(multiplexer, run): return result multiplexer = FakeMultiplexer() - provider = data_provider.MultiplexerDataProvider(multiplexer) + provider = data_provider.MultiplexerDataProvider(multiplexer, "fake_logdir") result = provider.list_runs(experiment_id="unused") self.assertItemsEqual(result, [ base_provider.Run(run_id=run, run_name=run, start_time=start_time) @@ -164,7 +170,7 @@ def test_list_scalars_filters(self): def test_read_scalars(self): multiplexer = self.create_multiplexer() - provider = data_provider.MultiplexerDataProvider(multiplexer) + provider = data_provider.MultiplexerDataProvider(multiplexer, self.logdir) run_tag_filter = base_provider.RunTagFilter( runs=["waves", "polynomials", "unicorns"], diff --git a/tensorboard/components/tf_backend/environmentStore.ts b/tensorboard/components/tf_backend/environmentStore.ts index 961a5b9ff6..419027a96a 100644 --- a/tensorboard/components/tf_backend/environmentStore.ts +++ b/tensorboard/components/tf_backend/environmentStore.ts @@ -22,7 +22,9 @@ namespace tf_backend { private environment: Environment; load() { - const url = tf_backend.getRouter().environment(); + const url = tf_backend + .getRouter() + .environment(tf_backend.getExperimentId()); return this.requestManager.request(url).then((result) => { const environment = { dataLocation: result.data_location, diff --git a/tensorboard/components/tf_backend/router.ts b/tensorboard/components/tf_backend/router.ts index 77bd0ff9bb..d6cb443f4b 100644 --- a/tensorboard/components/tf_backend/router.ts +++ b/tensorboard/components/tf_backend/router.ts @@ -14,7 +14,7 @@ limitations under the License. ==============================================================================*/ namespace tf_backend { export interface Router { - environment: () => string; + environment: (experiment?: string) => string; experiments: () => string; pluginRoute: ( pluginName: string, @@ -39,7 +39,11 @@ namespace tf_backend { dataDir = dataDir.slice(0, dataDir.length - 1); } return { - environment: () => createDataPath(dataDir, '/environment'), + environment: (experiment?: string) => { + const searchParams = new URLSearchParams(); + searchParams.set('experiment', experiment || ''); + return createDataPath(dataDir, '/environment', searchParams); + }, experiments: () => createDataPath(dataDir, '/experiments'), pluginRoute: ( pluginName: string, diff --git a/tensorboard/components/tf_backend/test/backendTests.ts b/tensorboard/components/tf_backend/test/backendTests.ts index 2e1f2fc272..9be6ae0ba2 100644 --- a/tensorboard/components/tf_backend/test/backendTests.ts +++ b/tensorboard/components/tf_backend/test/backendTests.ts @@ -97,7 +97,7 @@ namespace tf_backend { }); it('returns correct value for #environment', () => { - assert.equal(router.environment(), 'data/environment'); + assert.equal(router.environment(), 'data/environment?experiment='); }); it('returns correct value for #experiments', () => { diff --git a/tensorboard/data/provider.py b/tensorboard/data/provider.py index f56c45374c..29bc1efa07 100644 --- a/tensorboard/data/provider.py +++ b/tensorboard/data/provider.py @@ -33,6 +33,22 @@ class DataProvider(object): downsampling strategies or domain restriction by step or wall time. """ + def data_location(self, experiment_id): + """Render a human-readable description of the data source. + + For instance, this might return a path to a directory on disk. + + The default implementation always returns the empty string. + + Args: + experiment_id: ID of enclosing experiment. + + Returns: + A string, which may be empty. + """ + return "" + + @abc.abstractmethod def list_runs(self, experiment_id): """List all runs within an experiment. diff --git a/tensorboard/plugins/core/core_plugin.py b/tensorboard/plugins/core/core_plugin.py index a46f658943..dfc06506bc 100644 --- a/tensorboard/plugins/core/core_plugin.py +++ b/tensorboard/plugins/core/core_plugin.py @@ -120,10 +120,15 @@ def _serve_environment(self, request): database (depending on which mode TensorBoard is running in). * window_title is the title of the TensorBoard web page. """ + if self._data_provider: + experiment = request.args.get('experiment', '') + data_location = self._data_provider.data_location(experiment) + else: + data_location = self._logdir or self._db_uri return http_util.Respond( request, { - 'data_location': self._logdir or self._db_uri, + 'data_location': data_location, 'window_title': self._window_title, }, 'application/json') diff --git a/tensorboard/plugins/scalar/scalars_plugin_test.py b/tensorboard/plugins/scalar/scalars_plugin_test.py index 0f5c8801ec..0a5351caeb 100644 --- a/tensorboard/plugins/scalar/scalars_plugin_test.py +++ b/tensorboard/plugins/scalar/scalars_plugin_test.py @@ -124,7 +124,7 @@ def wrapper(self, *args, **kwargs): fn(self, scalars_plugin.ScalarsPlugin(ctx), *args, **kwargs) with self.subTest('generic data provider'): flags = argparse.Namespace(generic_data='true') - provider = data_provider.MultiplexerDataProvider(multiplexer) + provider = data_provider.MultiplexerDataProvider(multiplexer, logdir) ctx = base_plugin.TBContext( flags=flags, logdir=logdir,