Skip to content

Commit acd46f5

Browse files
authored
data: plumb experiment ID through runs and scalars (#2580)
Summary: This implements a rudimentary experiment selection mechanism for the runs selector and scalars dashboard only, for the purposes of testing data provider implementations. The experiment ID is stored as a query parameter, which isn’t perfect because it precludes caching the big TensorBoard HTML blob across experiments. In the long term, we can consider refactoring to serve the big HTML blob from a static path that’s fetched by the page. The TensorBoard codebase still has some fragments of a previous experimental data selector, which was partially removed in #2290, and so parts of this diff look like changes but are functionally additions. Test Plan: Instrument the multiplexer data provider to log experiment IDs: ```diff diff --git a/tensorboard/backend/event_processing/data_provider.py b/tensorboard/backend/event_processing/data_provider.py index ef60232..4c72d096 100644 --- a/tensorboard/backend/event_processing/data_provider.py +++ b/tensorboard/backend/event_processing/data_provider.py @@ -54,7 +54,7 @@ class MultiplexerDataProvider(provider.DataProvider): return None def list_runs(self, experiment_id): - del experiment_id # ignored for now + logger.warn("Listing runs for experiment %r", experiment_id) return [ provider.Run( run_id=run, # use names as IDs @@ -65,7 +65,7 @@ class MultiplexerDataProvider(provider.DataProvider): ] def list_scalars(self, experiment_id, plugin_name, run_tag_filter=None): - del experiment_id # ignored for now + logger.warn("Listing scalars for experiment %r", experiment_id) run_tag_content = self._multiplexer.PluginRunToTagToContent(plugin_name) result = {} if run_tag_filter is None: @@ -96,6 +96,7 @@ class MultiplexerDataProvider(provider.DataProvider): def read_scalars( self, experiment_id, plugin_name, downsample=None, run_tag_filter=None ): + logger.warn("Reading scalars for experiment %r", experiment_id) # TODO(@wchargin): Downsampling not implemented, as the multiplexer # is already downsampled. We could downsample on top of the existing # sampling, which would be nice for testing. ``` Then launch TensorBoard with `--generic_data=true` and navigate to <http://localhost:6006/?experiment=foo>; verify that all three varieties of server logs are exclusively for the correct experiment ID. Then, remove the query parameter, and ensure that TensorBoard still works normally (with experiment ID the empty string). wchargin-branch: data-experiment
1 parent 56a9fd0 commit acd46f5

File tree

7 files changed

+37
-18
lines changed

7 files changed

+37
-18
lines changed

tensorboard/components/tf_backend/router.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace tf_backend {
2222
params?: URLSearchParams
2323
) => string;
2424
pluginsListing: () => string;
25-
runs: () => string;
25+
runs: (experiment?: string) => string;
2626
runsForExperiment: (id: tf_backend.ExperimentId) => string;
2727
}
2828

@@ -53,7 +53,11 @@ namespace tf_backend {
5353
);
5454
},
5555
pluginsListing: () => createDataPath(dataDir, '/plugins_listing'),
56-
runs: () => createDataPath(dataDir, '/runs'),
56+
runs: (experiment?: string) => {
57+
const searchParams = new URLSearchParams();
58+
searchParams.set('experiment', experiment || '');
59+
return createDataPath(dataDir, '/runs', searchParams);
60+
},
5761
runsForExperiment: (id) => {
5862
return createDataPath(
5963
dataDir,
@@ -71,6 +75,13 @@ namespace tf_backend {
7175
return _router;
7276
}
7377

78+
/**
79+
* @return {string} the experiment ID for the currently loaded page
80+
*/
81+
export function getExperimentId() {
82+
return new URLSearchParams(window.location.search).get('experiment') || '';
83+
}
84+
7485
/**
7586
* Set the global router, to be returned by future calls to `getRouter`.
7687
* You may wish to invoke this if you are running a demo server with a

tensorboard/components/tf_backend/runsStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace tf_backend {
1717
private _runs: string[] = [];
1818

1919
load() {
20-
const url = getRouter().runs();
20+
const url = tf_backend.getRouter().runs(tf_backend.getExperimentId());
2121
return this.requestManager.request(url).then((newRuns) => {
2222
if (!_.isEqual(this._runs, newRuns)) {
2323
this._runs = newRuns;

tensorboard/components/tf_backend/test/backendTests.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,18 @@ namespace tf_backend {
8282

8383
it('leading slash in pathPrefix is an absolute path', () => {
8484
const router = createRouter('/data/');
85-
assert.equal(router.runs(), '/data/runs');
85+
assert.equal(router.runs(), '/data/runs?experiment=');
8686
});
8787

8888
it('returns complete pathname when pathPrefix omits slash', () => {
8989
const router = createRouter('data/');
90-
assert.equal(router.runs(), 'data/runs');
90+
assert.equal(router.runs(), 'data/runs?experiment=');
9191
});
9292

9393
it('does not prune many leading slashes that forms full url', () => {
9494
const router = createRouter('///data/hello');
95-
// This becomes 'http://data/hello/runs'
96-
assert.equal(router.runs(), '///data/hello/runs');
95+
// This becomes 'http://data/hello/runs?experiment='
96+
assert.equal(router.runs(), '///data/hello/runs?experiment=');
9797
});
9898

9999
it('returns correct value for #environment', () => {
@@ -177,7 +177,7 @@ namespace tf_backend {
177177
});
178178

179179
it('returns correct value for #runs', () => {
180-
assert.equal(router.runs(), 'data/runs');
180+
assert.equal(router.runs(), 'data/runs?experiment=');
181181
});
182182

183183
it('returns correct value for #runsForExperiment', () => {

tensorboard/plugins/core/core_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ def _serve_runs(self, request):
154154
last, and then ties are broken by sorting on the run name.
155155
"""
156156
if self._data_provider:
157+
experiment = request.args.get('experiment', '')
157158
runs = sorted(
158-
# (`experiment_id=None` as experiment support is not yet implemented)
159-
self._data_provider.list_runs(experiment_id=None),
159+
self._data_provider.list_runs(experiment_id=experiment),
160160
key=lambda run: (
161161
run.start_time if run.start_time is not None else float('inf'),
162162
run.run_name,

tensorboard/plugins/scalar/scalars_plugin.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,11 @@ def frontend_metadata(self):
9393
element_name='tf-scalar-dashboard',
9494
)
9595

96-
def index_impl(self):
96+
def index_impl(self, experiment=None):
9797
"""Return {runName: {tagName: {displayName: ..., description: ...}}}."""
9898
if self._data_provider:
9999
mapping = self._data_provider.list_scalars(
100-
experiment_id=None, # experiment support not yet implemented
100+
experiment_id=experiment,
101101
plugin_name=metadata.PLUGIN_NAME,
102102
)
103103
result = {run: {} for run in mapping}
@@ -155,7 +155,7 @@ def scalars_impl(self, tag, run, experiment, output_format):
155155
# logic.
156156
SAMPLE_COUNT = 1000
157157
all_scalars = self._data_provider.read_scalars(
158-
experiment_id=None, # experiment support not yet implemented
158+
experiment_id=experiment,
159159
plugin_name=metadata.PLUGIN_NAME,
160160
downsample=SAMPLE_COUNT,
161161
run_tag_filter=provider.RunTagFilter(runs=[run], tags=[tag]),
@@ -224,7 +224,8 @@ def _get_value(self, scalar_data_blob, dtype_enum):
224224

225225
@wrappers.Request.application
226226
def tags_route(self, request):
227-
index = self.index_impl()
227+
experiment = request.args.get('experiment', '')
228+
index = self.index_impl(experiment=experiment)
228229
return http_util.Respond(request, index, 'application/json')
229230

230231
@wrappers.Request.application
@@ -233,7 +234,7 @@ def scalars_route(self, request):
233234
# TODO: return HTTP status code for malformed requests
234235
tag = request.args.get('tag')
235236
run = request.args.get('run')
236-
experiment = request.args.get('experiment')
237+
experiment = request.args.get('experiment', '')
237238
output_format = request.args.get('format')
238239
(body, mime_type) = self.scalars_impl(tag, run, experiment, output_format)
239240
return http_util.Respond(request, body, mime_type)

tensorboard/plugins/scalar/tf_scalar_dashboard/tf-scalar-card.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@
241241
new URLSearchParams({
242242
tag,
243243
run,
244-
experiment: experiment ? experiment.id : '',
244+
experiment: experiment || '',
245245
})
246246
);
247247
};

tensorboard/plugins/scalar/tf_scalar_dashboard/tf-scalar-dashboard.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,13 @@ <h3>No scalar data was found.</h3>
309309
});
310310
},
311311
_fetchTags() {
312-
const url = tf_backend.getRouter().pluginRoute('scalars', '/tags');
312+
const url = tf_backend
313+
.getRouter()
314+
.pluginRoute(
315+
'scalars',
316+
'/tags',
317+
new URLSearchParams({experiment: tf_backend.getExperimentId()})
318+
);
313319
return this._requestManager.request(url).then((runToTagInfo) => {
314320
if (_.isEqual(runToTagInfo, this._runToTagInfo)) {
315321
// No need to update anything if there are no changes.
@@ -345,10 +351,11 @@ <h3>No scalar data was found.</h3>
345351
selectedRuns,
346352
query
347353
);
354+
const experiment = tf_backend.getExperimentId();
348355
categories.forEach((category) => {
349356
category.items = category.items.map((item) => ({
350357
tag: item.tag,
351-
series: item.runs.map((run) => ({run, tag: item.tag})),
358+
series: item.runs.map((run) => ({run, tag: item.tag, experiment})),
352359
}));
353360
});
354361
this.updateArrayProp('_categories', categories, this._getCategoryKey);

0 commit comments

Comments
 (0)