Skip to content

Commit bb71c7c

Browse files
committed
uploader: add metadata to experiment listing RPC (#2906)
Summary: This extends the `StreamExperiments` RPC such that the client can specify a set of additional metadata fields that the server should include, like “creation time” or “number of scalar points”. The format is both forward- and backward-compatible. Servers are expected to send responses with both `experiment_ids` and `experiments` until we drop support for clients that do not support `experiments`, at which point they need only send `experiments`. Test Plan: Unit test added to simulate the future behavior of servers. wchargin-branch: streamexperiments-metadata
1 parent fd46c42 commit bb71c7c

File tree

3 files changed

+105
-11
lines changed

3 files changed

+105
-11
lines changed

tensorboard/uploader/exporter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,11 @@ def list_experiments(api_client, read_time=None):
177177
stream = api_client.StreamExperiments(
178178
request, metadata=grpc_util.version_metadata())
179179
for response in stream:
180-
for experiment_id in response.experiment_ids:
181-
yield experiment_id
180+
if not response.experiments:
181+
for experiment_id in response.experiment_ids:
182+
yield experiment_id
183+
for experiment in response.experiments:
184+
yield experiment.experiment_id
182185

183186

184187
class OutputDirectoryExistsError(ValueError):

tensorboard/uploader/exporter_test.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def test_propagates_mkdir_errors(self):
317317

318318
class ListExperimentsTest(tb_test.TestCase):
319319

320-
def test(self):
320+
def test_experiment_ids_only(self):
321321
mock_api_client = _create_mock_api_client()
322322

323323
def stream_experiments(request, **kwargs):
@@ -332,6 +332,36 @@ def stream_experiments(request, **kwargs):
332332
mock_api_client.StreamExperiments.assert_not_called()
333333
self.assertEqual(list(gen), ["123", "456", "789"])
334334

335+
def test_mixed_experiments_and_ids(self):
336+
mock_api_client = _create_mock_api_client()
337+
338+
def stream_experiments(request, **kwargs):
339+
del request # unused
340+
341+
# Should include `experiment_ids` when no `experiments` given.
342+
response = export_service_pb2.StreamExperimentsResponse()
343+
response.experiment_ids.append("123")
344+
response.experiment_ids.append("456")
345+
yield response
346+
347+
# Should ignore `experiment_ids` in the presence of `experiments`.
348+
response = export_service_pb2.StreamExperimentsResponse()
349+
response.experiment_ids.append("999") # will be omitted
350+
response.experiments.add(experiment_id="789")
351+
response.experiments.add(experiment_id="012")
352+
yield response
353+
354+
# Should include `experiments` even when no `experiment_ids` are given.
355+
response = export_service_pb2.StreamExperimentsResponse()
356+
response.experiments.add(experiment_id="345")
357+
response.experiments.add(experiment_id="678")
358+
yield response
359+
360+
mock_api_client.StreamExperiments = mock.Mock(wraps=stream_experiments)
361+
gen = exporter_lib.list_experiments(mock_api_client)
362+
mock_api_client.StreamExperiments.assert_not_called()
363+
self.assertEqual(list(gen), ["123", "456", "789", "012", "345", "678"])
364+
335365

336366
class MkdirPTest(tb_test.TestCase):
337367

tensorboard/uploader/proto/export_service.proto

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,80 @@ message StreamExperimentsRequest {
2727
string user_id = 2;
2828
// Limits the number of experiment IDs returned. This is useful to check if
2929
// user might have any data by setting limit=1. Also useful to preview the
30-
// list of experiments.
30+
// list of experiments. TODO(@karthikv2k): Support pagination.
3131
int64 limit = 3;
32-
// TODO(@karthikv2k): Support pagination.
32+
// Field mask for what experiment data to return via the `experiments` field
33+
// on the response. If not specified, this should be interpreted the same as
34+
// an empty message: i.e., only the experiment ID should be returned.
35+
ExperimentMask experiments_mask = 4;
3336
}
3437

35-
// Streams experiment IDs returned from TensorBoard.dev.
38+
// Streams experiment metadata (ID, creation time, etc.) from TensorBoard.dev.
3639
message StreamExperimentsResponse {
37-
// List of experiment IDs for the experiments owned by the user. The entire
38-
// list of experiments owned by the user is streamed in batches and each batch
39-
// contains a list of experiment IDs. A consumer of this stream needs to
40-
// concatenate all these lists to get the full response. The order of
41-
// experiment IDs in the stream is not defined.
40+
// Deprecated in favor of `experiments`. If a response has `experiments` set,
41+
// clients should ignore `experiment_ids` entirely. Otherwise, clients should
42+
// treat `experiment_ids` as a list of `experiments` for which only the
43+
// `experiment_id` field is set, with the understanding that the other fields
44+
// were not populated regardless of the requested field mask.
45+
//
46+
// For example, the following responses should be treated the same:
47+
//
48+
// # Response 1
49+
// experiment_ids: "123"
50+
// experiment_ids: "456"
51+
//
52+
// # Response 2
53+
// experiments { experiment_id: "123" }
54+
// experiments { experiment_id: "456" }
55+
//
56+
// # Response 3
57+
// experiment_ids: "789"
58+
// experiments { experiment_id: "123" }
59+
// experiments { experiment_id: "456" }
60+
//
61+
// See documentation on `experiments` for batching semantics.
4262
repeated string experiment_ids = 1;
63+
// List of experiments owned by the user. The entire list of experiments
64+
// owned by the user is streamed in batches and each batch contains a list of
65+
// experiments. A consumer of this stream needs to concatenate all these
66+
// lists to get the full response. The order of experiments in the stream is
67+
// not defined. Every response will contain at least one experiment.
68+
//
69+
// These messages may be partially populated, in accordance with the field
70+
// mask given in the request.
71+
repeated Experiment experiments = 2;
72+
}
73+
74+
// Metadata about an experiment.
75+
message Experiment {
76+
// Permanent ID of this experiment; e.g.: "AdYd1TgeTlaLWXx6I8JUbA".
77+
string experiment_id = 1;
78+
// The time that the experiment was created.
79+
google.protobuf.Timestamp create_time = 2;
80+
// The time that the experiment was last modified: i.e., the most recent time
81+
// that scalars were added to the experiment.
82+
google.protobuf.Timestamp update_time = 3;
83+
// The number of scalars in this experiment, across all time series.
84+
int64 num_scalars = 4;
85+
// The number of distinct run names in this experiment.
86+
int64 num_runs = 5;
87+
// The number of distinct tag names in this experiment. A tag name that
88+
// appears in multiple runs will be counted only once.
89+
int64 num_tags = 6;
90+
}
91+
92+
// Field mask for `Experiment`. The `experiment_id` field is always implicitly
93+
// considered to be requested. Other fields of `Experiment` will be populated
94+
// if their corresponding bits in the `ExperimentMask` are set. The server may
95+
// choose to populate fields that are not explicitly requested.
96+
message ExperimentMask {
97+
reserved 1;
98+
reserved "experiment_id";
99+
bool create_time = 2;
100+
bool update_time = 3;
101+
bool num_scalars = 4;
102+
bool num_runs = 5;
103+
bool num_tags = 6;
43104
}
44105

45106
// Request to stream scalars from all the runs and tags in an experiment.

0 commit comments

Comments
 (0)