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

core: stabilize and use plugin_metadata #2304

Merged
merged 1 commit into from
Jun 5, 2019
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions tensorboard/backend/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from __future__ import print_function

import atexit
import collections
import json
import os
import re
Expand Down Expand Up @@ -274,7 +275,7 @@ def _serve_plugins_listing(self, request):
Returns:
A werkzeug.Response object.
"""
response = {}
response = collections.OrderedDict()
for plugin in self._plugins:
start = time.time()
is_active = plugin.is_active()
Expand Down Expand Up @@ -310,9 +311,8 @@ def _serve_plugins_listing(self, request):
]),
}
else:
# As a compatibility measure (for plugins that we don't control,
# and for incremental migration of core plugins), we'll pull it
# from the frontend registry for now.
# As a compatibility measure (for plugins that we don't
# control), we'll pull it from the frontend registry for now.
loading_mechanism = {
'type': 'NONE',
}
Expand Down
6 changes: 5 additions & 1 deletion tensorboard/backend/json_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from __future__ import division
from __future__ import print_function

import collections
import math

from tensorboard.compat import tf
Expand Down Expand Up @@ -69,6 +70,9 @@ def Cleanse(obj, encoding='utf-8'):
elif isinstance(obj, set):
return [Cleanse(i, encoding) for i in sorted(obj)]
elif isinstance(obj, dict):
return {Cleanse(k, encoding): Cleanse(v, encoding) for k, v in obj.items()}
return collections.OrderedDict(
(Cleanse(k, encoding), Cleanse(v, encoding))
for k, v in obj.items()
)
else:
return obj
14 changes: 13 additions & 1 deletion tensorboard/backend/json_util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
from __future__ import division
from __future__ import print_function

import collections
import string

from tensorboard import test as tb_test
from tensorboard.backend import json_util

_INFINITY = float('inf')


class FloatWrapperTest(tb_test.TestCase):
class CleanseTest(tb_test.TestCase):

def _assertWrapsAs(self, to_wrap, expected):
"""Asserts that |to_wrap| becomes |expected| when wrapped."""
Expand All @@ -50,6 +53,15 @@ def testWrapsInListsAndTuples(self):
def testWrapsRecursively(self):
self._assertWrapsAs({'x': [_INFINITY]}, {'x': ['Infinity']})

def testOrderedDict_preservesOrder(self):
# dict iteration order is not specified prior to Python 3.7, and is
# observably different from insertion order in CPython 2.7.
od = collections.OrderedDict()
for c in string.ascii_lowercase:
od[c] = c
self.assertEqual(len(od), 26, od)
self.assertEqual(list(od), list(json_util.Cleanse(od)))

def testTuple_turnsIntoList(self):
self.assertEqual(json_util.Cleanse(('a', 'b')), ['a', 'b'])

Expand Down
8 changes: 2 additions & 6 deletions tensorboard/components/tf_tensorboard/default-plugins.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@
<!--
Load dashboards into UI for all first-party TensorBoard plugins.

Each dashboard calls the registerDashboard() function to tell
<tf-tensorboard> that it exists, so it can be loaded dynamically via
document.createElement().

Ordering matters. The order in which these lines appear determines the
ordering of tabs in TensorBoard's GUI.
Each dashboard registers itself onto the global CustomElementRegistry,
and is later instantiated dynamically with `document.createElement`.
-->
<link rel="import" href="../tf-scalar-dashboard/tf-scalar-dashboard.html">
<link rel="import" href="../tf-custom-scalar-dashboard/tf-custom-scalar-dashboard.html">
Expand Down
7 changes: 6 additions & 1 deletion tensorboard/components/tf_tensorboard/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ export type DashboardRegistry = {[key: string]: Dashboard};
export let dashboardRegistry : DashboardRegistry = {};

/**
* Registers Dashboard for plugin into TensorBoard frontend.
* For legacy plugins, registers Dashboard for plugin into TensorBoard frontend.
*
* New plugins should implement the `frontend_metadata` method on the
* corresponding Python plugin to provide this information instead.
*
* For legacy plugins:
*
* This function should be called after the Polymer custom element is defined.
* It's what allows the tf-tensorboard component to dynamically load it as a
Expand Down
47 changes: 24 additions & 23 deletions tensorboard/components/tf_tensorboard/tf-tensorboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,6 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
</template>
<script src="autoReloadBehavior.js"></script>
<script>
if (_.isEmpty(tf_tensorboard.dashboardRegistry)) {
throw new Error('HTML import plugin dashboards *before* tf-tensorboard');
}

/**
* @typedef {{
* plugin: string,
Expand Down Expand Up @@ -555,7 +551,7 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
_showNoSuchDashboardMessage: {
type: Boolean,
computed:
'_computeShowNoSuchDashboardMessage(_dashboardData, _selectedDashboard)',
'_computeShowNoSuchDashboardMessage(_activeDashboardsLoaded, _dashboardRegistry, _selectedDashboard)',
},

/**
Expand All @@ -571,13 +567,12 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
_dashboardToMaybeRemove: String,

/*
* Will be set to `true` once our DOM is ready: in particular,
* once each dashboard has a `<div>` into which we can render its
* Polymer component root.
* Once the dashboard container for dashboard `d` is stamped,
* key `d` of this object will be set to `true`.
*/
_dashboardContainersStamped: {
type: Boolean,
value: false,
type: Object,
value: () => ({}),
},
_isReloadDisabled: {
type: Boolean,
Expand Down Expand Up @@ -764,7 +759,7 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
activeDashboards,
selectedDashboard,
) {
if (!containersStamped || !activeDashboards || !selectedDashboard) {
if (!activeDashboards || !selectedDashboard || !containersStamped[selectedDashboard]) {
return;
}
const previous = this._dashboardToMaybeRemove;
Expand Down Expand Up @@ -824,20 +819,13 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
* `null` is returned.
*/
_selectedDashboardComponent() {
if (!this._dashboardContainersStamped) {
throw new Error(
'There is no "selected dashboard" before containers are stamped.');
}
const selectedDashboard = this._selectedDashboard;
var dashboard = this.$$(
`.dashboard-container[data-dashboard=${selectedDashboard}] #dashboard`);
return dashboard;
},

_updateDataSelection(dashboardRegistry) {
// The dashboard is not ready yet until it stamped the containers.
if (!this._dashboardContainersStamped) return;

this.cancelDebouncer('updateDataSelection');

const component = this._selectedDashboardComponent();
Expand Down Expand Up @@ -873,7 +861,11 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
const dashboardsTemplate = this.$$('#dashboards-template');
const onDomChange = () => {
// This will trigger an observer that kicks off everything.
this._dashboardContainersStamped = true;
const dashboardContainersStamped = {};
for (const container of this.querySelectorAll('.dashboard-container')) {
dashboardContainersStamped[container.dataset.dashboard] = true;
}
this._dashboardContainersStamped = dashboardContainersStamped;
};
dashboardsTemplate.addEventListener(
'dom-change', onDomChange, /*useCapture=*/false);
Expand Down Expand Up @@ -972,7 +964,17 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
};
}
}
return registry;

// Reorder to list all values from the `/data/plugins_listing`
// response first and in their listed order.
const orderedRegistry = {};
for (const plugin of Object.keys(pluginsListing)) {
if (registry[plugin]) {
orderedRegistry[plugin] = registry[plugin];
}
}
Object.assign(orderedRegistry, registry);
return orderedRegistry;
},

_computeDashboardData(dashboardRegistry) {
Expand Down Expand Up @@ -1016,9 +1018,8 @@ <h3>There’s no dashboard by the name of “<tt>[[_selectedDashboard]]</tt>.”
&& activeDashboards.length === 0
&& selectedDashboard == null);
},
_computeShowNoSuchDashboardMessage(dashboards, selectedDashboard) {
return !!selectedDashboard &&
!dashboards.some(d => d.plugin === selectedDashboard);
_computeShowNoSuchDashboardMessage(loaded, registry, selectedDashboard) {
return loaded && !!selectedDashboard && registry[selectedDashboard] == null;
},

_updateRouter(router) {
Expand Down
10 changes: 6 additions & 4 deletions tensorboard/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,24 @@

logger = logging.getLogger(__name__)

# Ordering matters. The order in which these lines appear determines the
# ordering of tabs in TensorBoard's GUI.
_PLUGINS = [
core_plugin.CorePluginLoader(),
beholder_plugin_loader.BeholderPluginLoader(),
scalars_plugin.ScalarsPlugin,
custom_scalars_plugin.CustomScalarsPlugin,
images_plugin.ImagesPlugin,
audio_plugin.AudioPlugin,
debugger_plugin_loader.DebuggerPluginLoader(),
graphs_plugin.GraphsPlugin,
distributions_plugin.DistributionsPlugin,
histograms_plugin.HistogramsPlugin,
pr_curves_plugin.PrCurvesPlugin,
projector_plugin.ProjectorPlugin,
text_plugin.TextPlugin,
interactive_inference_plugin_loader.InteractiveInferencePluginLoader(),
pr_curves_plugin.PrCurvesPlugin,
profile_plugin_loader.ProfilePluginLoader(),
debugger_plugin_loader.DebuggerPluginLoader(),
beholder_plugin_loader.BeholderPluginLoader(),
interactive_inference_plugin_loader.InteractiveInferencePluginLoader(),
hparams_plugin_loader.HParamsPluginLoader(),
mesh_plugin.MeshPlugin,
]
Expand Down
5 changes: 5 additions & 0 deletions tensorboard/plugins/audio/audio_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def is_active(self):
return False
return bool(self._multiplexer.PluginRunToTagToContent(metadata.PLUGIN_NAME))

def frontend_metadata(self):
return super(AudioPlugin, self).frontend_metadata()._replace(
element_name='tf-audio-dashboard',
)

def _index_impl(self):
"""Return information about the tags in each run.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,5 @@ <h3>No audio data was found.</h3>
},
});

tf_tensorboard.registerDashboard({
plugin: 'audio',
elementName: 'tf-audio-dashboard',
});

</script>
</dom-module>
6 changes: 6 additions & 0 deletions tensorboard/plugins/beholder/beholder_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ def is_active(self):
return tf.io.gfile.exists(summary_filename) and\
tf.io.gfile.exists(info_filename)

def frontend_metadata(self):
return super(BeholderPlugin, self).frontend_metadata()._replace(
element_name='tf-beholder-dashboard',
remove_dom=True,
)

def is_config_writable(self):
try:
if not tf.io.gfile.exists(self.PLUGIN_LOGDIR):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,6 @@ <h3>No Beholder data was found.</h3>
},
});

tf_tensorboard.registerDashboard({
plugin: PLUGIN_NAME,
elementName: 'tf-beholder-dashboard',
shouldRemoveDom: true,
});

})();
</script>
</dom-module>
6 changes: 6 additions & 0 deletions tensorboard/plugins/custom_scalar/custom_scalars_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ def is_active(self):
# This plugin is active if any run has a layout.
return bool(self._multiplexer.PluginRunToTagToContent(metadata.PLUGIN_NAME))

def frontend_metadata(self):
return super(CustomScalarsPlugin, self).frontend_metadata()._replace(
element_name='tf-custom-scalar-dashboard',
tab_name='Custom Scalars',
)

@wrappers.Request.application
def download_data_route(self, request):
run = request.args.get('run')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,5 @@ <h3>The custom scalars dashboard is inactive.</h3>
}
},
});

tf_tensorboard.registerDashboard({
plugin: 'custom_scalars',
elementName: 'tf-custom-scalar-dashboard',
tabName: 'Custom Scalars',
});
</script>
</dom-module>
5 changes: 5 additions & 0 deletions tensorboard/plugins/debugger/debugger_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ def is_active(self):
self._event_multiplexer.PluginRunToTagToContent(
constants.DEBUGGER_PLUGIN_NAME))

def frontend_metadata(self):
return super(DebuggerPlugin, self).frontend_metadata()._replace(
element_name='tf-debugger-dashboard',
)

@wrappers.Request.application
def _serve_health_pills_handler(self, request):
"""A (wrapped) werkzeug handler for serving health pills.
Expand Down
5 changes: 5 additions & 0 deletions tensorboard/plugins/debugger/interactive_debugger_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ def is_active(self):
"""
return self._grpc_port is not None

def frontend_metadata(self):
return super(InteractiveDebuggerPlugin, self).frontend_metadata()._replace(
element_name='tf-debugger-dashboard',
)

@wrappers.Request.application
def _serve_ack(self, request):
# Send client acknowledgement. `True` is just used as a dummy value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1426,10 +1426,5 @@
{defaultValue: _DEFAULT_TOP_RIGHT_QUADRANT_HEIGHT}),
});

tf_tensorboard.registerDashboard({
plugin: 'debugger',
elementName: 'tf-debugger-dashboard',
});

</script>
</dom-module>
5 changes: 5 additions & 0 deletions tensorboard/plugins/distribution/distributions_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def is_active(self):
"""
return self._histograms_plugin.is_active()

def frontend_metadata(self):
return super(DistributionsPlugin, self).frontend_metadata()._replace(
element_name='tf-distribution-dashboard',
)

def distributions_impl(self, tag, run):
"""Result of the form `(body, mime_type)`, or `ValueError`."""
(histograms, mime_type) = self._histograms_plugin.histograms_impl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,5 @@ <h3>No distribution data was found.</h3>
},
});

tf_tensorboard.registerDashboard({
plugin: 'distributions',
elementName: 'tf-distribution-dashboard',
});

</script>
</dom-module>
7 changes: 7 additions & 0 deletions tensorboard/plugins/graph/graphs_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ def is_active(self):
"""The graphs plugin is active iff any run has a graph."""
return bool(self._multiplexer and self.info_impl())

def frontend_metadata(self):
return super(GraphsPlugin, self).frontend_metadata()._replace(
element_name='tf-graph-dashboard',
# TODO(@chihuahua): Reconcile this setting with Health Pills.
disable_reload=True,
)

def info_impl(self):
"""Returns a dict of all runs and tags and their data availabilities."""
result = {}
Expand Down
Loading