Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter scalar values based on optional param experiment #1350

Merged
merged 5 commits into from Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -47,8 +47,9 @@ export type TagCategory = Category<{tag: string, runs: string[]}>;
export type RunTagCategory = Category<{tag: string, run: string}>;

export type Series = {
experiment: string,
experiment: tf_backend.Experiment,
run: string,
tag: string,
};

/**
Expand All @@ -57,7 +58,7 @@ export type Series = {
*/
export type SeriesCategory = Category<{
tag: string,
series: Array<Series>,
series: Series[],
}>;

export type RawCategory = Category<string>; // Intermediate structure.
Expand Down Expand Up @@ -146,10 +147,10 @@ export function categorizeTags(
export function categorizeSelection(
selection: tf_data_selector.Selection[], pluginName: string):
SeriesCategory[] {
const tagToSeries = new Map<string, Array<Series>>();
const tagToSeries = new Map<string, Series[]>();
// `tagToSearchSeries` contains subset of `tagToSeries`. tagRegex in each
// selection can omit series from a tag category.
const tagToSearchSeries = new Map<string, Array<Series>>();
const tagToSearchSeries = new Map<string, Series[]>();
const searchCategories = [];

selection.forEach(({experiment, runs, tagRegex}) => {
Expand All @@ -161,7 +162,7 @@ export function categorizeSelection(
tags.forEach(tag => {
const series = tagToSeries.get(tag) || [];
series.push(...tagToSelectedRuns.get(tag)
.map(run => ({experiment: experiment.name, run})));
.map(run => ({experiment, run, tag})));
tagToSeries.set(tag, series);
});

Expand All @@ -171,7 +172,7 @@ export function categorizeSelection(
searchCategory.items.forEach(tag => {
const series = tagToSearchSeries.get(tag) || [];
series.push(...tagToSelectedRuns.get(tag)
.map(run => ({experiment: experiment.name, run})));
.map(run => ({experiment, run, tag})));
tagToSearchSeries.set(tag, series);
});
});
Expand Down
Expand Up @@ -202,17 +202,14 @@ describe('categorizationUtils', () => {
const tag1 = {
id: 1, pluginName: 'scalar',
name: 'tag1', displayName: 'tag1',

};
const tag2_1 = {
id: 2, pluginName: 'scalar',
name: 'tag2/subtag1', displayName: 'tag2/subtag1',

};
const tag2_2 = {
id: 3, pluginName: 'scalar',
name: 'tag2/subtag2', displayName: 'tag2/subtag2',

};
const tag3 = {
id: 4, pluginName: 'scalar',
Expand All @@ -227,17 +224,17 @@ describe('categorizationUtils', () => {
this.run2 = {id: 2, name: 'run2', startTime: 5, tags: [tag2_1, tag2_2]};
this.run3 = {id: 3, name: 'run3', startTime: 0, tags: [tag2_1, tag3]};

this.experiment1 = {
this.selection1 = {
experiment: {id: 1, name: 'exp1', startTime: 0},
runs: [this.run1, this.run2],
tagRegex: '',
};
this.experiment2 = {
this.selection2 = {
experiment: {id: 2, name: 'exp2', startTime: 0},
runs: [this.run2, this.run3],
tagRegex: '(subtag1|tag3)',
};
this.experiment3 = {
this.selection3 = {
experiment: {id: 3, name: 'exp3', startTime: 0},
runs: [this.run1, this.run2, this.run3],
tagRegex: 'junk',
Expand All @@ -246,7 +243,7 @@ describe('categorizationUtils', () => {

it('merges the results of the query and the prefix groups', function() {
const result = categorizeSelection(
[this.experiment1], 'scalar');
[this.selection1], 'scalar');

expect(result).to.have.lengthOf(3);
expect(result[0]).to.have.property('metadata')
Expand All @@ -261,7 +258,7 @@ describe('categorizationUtils', () => {
describe('search group', () => {
it('filters groups by tag with a tagRegex', function() {
const [searchResult] = categorizeSelection(
[this.experiment2], 'scalar');
[this.selection2], 'scalar');

// should match 'tag2/subtag1' and 'tag3'.
expect(searchResult).to.have.property('items')
Expand All @@ -280,8 +277,9 @@ describe('categorizationUtils', () => {
});

it('combines selection without tagRegex with one', function() {
const [searchResult] = categorizeSelection(
[this.experiment1, this.experiment2], 'scalar');
const sel1 = this.selection1;
const sel2 = this.selection2;
const [searchResult] = categorizeSelection([sel1, sel2], 'scalar');

// should match 'tag1', 'tag2/subtag1', 'tag2/subtag2', and 'tag3'.
expect(searchResult).to.have.property('items')
Expand All @@ -294,15 +292,15 @@ describe('categorizationUtils', () => {
expect(searchResult.items[1]).to.have.property('series')
.that.has.lengthOf(3)
.and.that.deep.equal([
{experiment: 'exp1', run: 'run2'},
{experiment: 'exp2', run: 'run2'},
{experiment: 'exp2', run: 'run3'},
{experiment: sel1.experiment, run: 'run2', tag: 'tag2/subtag1'},
{experiment: sel2.experiment, run: 'run2', tag: 'tag2/subtag1'},
{experiment: sel2.experiment, run: 'run3', tag: 'tag2/subtag1'},
]);
});

it('sorts the tag by name', function() {
const [searchResult] = categorizeSelection(
[this.experiment2, this.experiment1], 'scalar');
[this.selection2, this.selection1], 'scalar');

// should match 'tag1', 'tag2/subtag1', 'tag2/subtag2', and 'tag3'.
expect(searchResult).to.have.property('items')
Expand All @@ -315,17 +313,16 @@ describe('categorizationUtils', () => {

it('returns name `multi` when there are multiple selections', function() {
const [searchResult2] = categorizeSelection(
[this.experiment2], 'scalar');
[this.selection2], 'scalar');
expect(searchResult2).to.have.property('name', '(subtag1|tag3)');

const [searchResult1] = categorizeSelection(
[this.experiment1, this.experiment2], 'scalar');
[this.selection1, this.selection2], 'scalar');
expect(searchResult1).to.have.property('name', 'multi');
});

it('returns an empty array when tagRegex does not match any', function() {
const result = categorizeSelection(
[this.experiment3], 'custom_scalar');
const result = categorizeSelection([this.selection3], 'custom_scalar');

expect(result).to.have.lengthOf(2);
expect(result[0]).to.have.property('items')
Expand Down Expand Up @@ -354,8 +351,7 @@ describe('categorizationUtils', () => {

describe('prefix group', () => {
it('creates a group when a tag misses separator', function() {
const result = categorizeSelection(
[this.experiment1], 'scalar');
const result = categorizeSelection([this.selection1], 'scalar');

expect(result[1]).to.have.property('items')
.that.has.lengthOf(1);
Expand All @@ -367,8 +363,7 @@ describe('categorizationUtils', () => {
});

it('creates a grouping when tag has a separator', function() {
const result = categorizeSelection(
[this.experiment1], 'scalar');
const result = categorizeSelection([this.selection1], 'scalar');

expect(result[2]).to.have.property('items')
.that.has.lengthOf(2);
Expand All @@ -381,29 +376,30 @@ describe('categorizationUtils', () => {
});

it('creates a group with items with experiment and run', function() {
const result = categorizeSelection(
[this.experiment1], 'scalar');
const sel = this.selection1;
const result = categorizeSelection([sel], 'scalar');

expect(result[1].items[0]).to.have.property('series')
.that.has.lengthOf(1)
.and.that.deep.equal([{experiment: 'exp1', run: 'run1'}]);
.and.that.deep.equal([
{experiment: sel.experiment, run: 'run1', tag: 'tag1'},
]);
});

it('creates distinct subitems when tags exactly match', function() {
const result = categorizeSelection(
[this.experiment2], 'scalar');

const sel = this.selection2;
const result = categorizeSelection([sel], 'scalar');
expect(result[1].items[0]).to.have.property('series')
.that.has.lengthOf(2)
.and.that.deep.equal([
{experiment: 'exp2', run: 'run2'},
{experiment: 'exp2', run: 'run3'},
{experiment: sel.experiment, run: 'run2', tag: 'tag2/subtag1'},
{experiment: sel.experiment, run: 'run3', tag: 'tag2/subtag1'},
]);
});

it('filters out tags of a different pluguin', function() {
const result = categorizeSelection(
[this.experiment3], 'custom_scalar');
it('filters out tags of a different plugin', function() {
const sel = this.selection3;
const result = categorizeSelection([sel], 'custom_scalar');

expect(result).to.have.lengthOf(2);
expect(result[1]).to.have.property('name', 'tag4');
Expand All @@ -412,7 +408,7 @@ describe('categorizationUtils', () => {
expect(result[1].items[0]).to.have.property('series')
.that.has.lengthOf(1)
.and.that.deep.equal([
{experiment: 'exp3', run: 'run1'},
{experiment: sel.experiment, run: 'run1', tag: 'tag4'},
]);
});
});
Expand Down
6 changes: 5 additions & 1 deletion tensorboard/plugins/core/core_plugin.py
Expand Up @@ -174,6 +174,10 @@ def _serve_experiments(self, request):
started time (aka first event time) with empty times sorted last, and then
ties are broken by sorting on the experiment name.
"""
results = self.list_experiments_impl()
return http_util.Respond(request, results, 'application/json')

def list_experiments_impl(self):
results = []
if self._db_connection_provider:
db = self._db_connection_provider()
Expand All @@ -193,7 +197,7 @@ def _serve_experiments(self, request):
"startTime": row[2],
} for row in cursor]

return http_util.Respond(request, results, 'application/json')
return results

@wrappers.Request.application
def _serve_experiment_runs(self, request):
Expand Down
10 changes: 6 additions & 4 deletions tensorboard/plugins/custom_scalar/custom_scalars_plugin.py
Expand Up @@ -142,7 +142,7 @@ def download_data_impl(self, run, tag, response_format):
'The scalars plugin is oddly not registered.'))

body, mime_type = scalars_plugin_instance.scalars_impl(
tag, run, response_format)
tag, run, None, response_format)
return body, mime_type

@wrappers.Request.application
Expand Down Expand Up @@ -226,9 +226,11 @@ def scalars_impl(self, run, tag_regex_string):
'The scalars plugin is oddly not registered.'))

form = scalars_plugin.OutputFormat.JSON
payload = {tag: scalars_plugin_instance.scalars_impl(tag, run, form)[0]
for tag in tag_to_data.keys()
if regex.match(tag)}
payload = {
tag: scalars_plugin_instance.scalars_impl(tag, run, None, form)[0]
for tag in tag_to_data.keys()
if regex.match(tag)
}

return {
_REGEX_VALID_PROPERTY: True,
Expand Down
10 changes: 6 additions & 4 deletions tensorboard/plugins/scalar/scalars_plugin.py
Expand Up @@ -123,7 +123,7 @@ def index_impl(self):

return result

def scalars_impl(self, tag, run, output_format):
def scalars_impl(self, tag, run, experiment, output_format):
"""Result of the form `(body, mime_type)`."""
if self._db_connection_provider:
db = self._db_connection_provider()
Expand All @@ -141,13 +141,14 @@ def scalars_impl(self, tag, run, output_format):
JOIN Runs
ON Tags.run_id = Runs.run_id
WHERE
Runs.run_name = ?
Runs.experiment_id IS ?
AND Runs.run_name = ?
AND Tags.tag_name = ?
AND Tags.plugin_name = ?
AND Tensors.shape = ''
AND Tensors.step > -1
ORDER BY Tensors.step
''', (run, tag, metadata.PLUGIN_NAME))
''', (experiment, run, tag, metadata.PLUGIN_NAME))
values = [(wall_time, step, self._get_value(data, dtype_enum))
for (step, wall_time, data, dtype_enum) in cursor]
else:
Expand Down Expand Up @@ -191,6 +192,7 @@ def scalars_route(self, request):
# TODO: return HTTP status code for malformed requests
tag = request.args.get('tag')
run = request.args.get('run')
experiment = request.args.get('experiment')
output_format = request.args.get('format')
(body, mime_type) = self.scalars_impl(tag, run, output_format)
(body, mime_type) = self.scalars_impl(tag, run, experiment, output_format)
return http_util.Respond(request, body, mime_type)