aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--tensorflow/tensorboard/components/tf_backend/backend.ts8
-rw-r--r--tensorflow/tensorboard/components/tf_graph_board/tf-graph-board.html22
-rw-r--r--tensorflow/tensorboard/components/tf_graph_dashboard/tf-graph-dashboard.html78
-rw-r--r--tensorflow/tensorboard/components/tf_graph_info/tf-graph-info.html138
4 files changed, 224 insertions, 22 deletions
diff --git a/tensorflow/tensorboard/components/tf_backend/backend.ts b/tensorflow/tensorboard/components/tf_backend/backend.ts
index b4be89163e..b87ced2ec2 100644
--- a/tensorflow/tensorboard/components/tf_backend/backend.ts
+++ b/tensorflow/tensorboard/components/tf_backend/backend.ts
@@ -200,8 +200,14 @@ module TF.Backend {
/**
* Returns a promise for requesting the health pills for a list of nodes.
*/
- public healthPills(nodeNames: string[]): Promise<HealthPillsResponse> {
+ public healthPills(nodeNames: string[], step?: number):
+ Promise<HealthPillsResponse> {
let postData = {'node_names': JSON.stringify(nodeNames)};
+ if (step !== undefined) {
+ // The user requested health pills for a specific step. This request
+ // might be slow since the backend reads events sequentially from disk.
+ postData['step'] = step;
+ }
return this.requestManager.request(this.router.healthPills(), postData);
}
diff --git a/tensorflow/tensorboard/components/tf_graph_board/tf-graph-board.html b/tensorflow/tensorboard/components/tf_graph_board/tf-graph-board.html
index c2ad0d09f7..5909172fbe 100644
--- a/tensorflow/tensorboard/components/tf_graph_board/tf-graph-board.html
+++ b/tensorflow/tensorboard/components/tf_graph_board/tf-graph-board.html
@@ -157,7 +157,11 @@ paper-progress {
highlighted-node="{{_highlightedNode}}"
color-by="[[colorBy]]"
color-by-params="[[colorByParams]]"
+ debugger-data-enabled="[[debuggerDataEnabled]]"
+ are-health-pills-loading="[[areHealthPillsLoading]]"
node-names-to-health-pills="[[nodeNamesToHealthPills]]"
+ all-steps-mode-enabled="{{allStepsModeEnabled}}"
+ specific-health-pill-step="{{specificHealthPillStep}}"
health-pill-step-index="{{healthPillStepIndex}}"
></tf-graph-info>
</div>
@@ -190,8 +194,26 @@ Polymer({
type: Object,
notify: true
},
+ // Whether debugger data is enabled for this instance of Tensorboard.
+ debuggerDataEnabled: Boolean,
+ // Whether health pills are currently being loaded.
+ areHealthPillsLoading: Boolean,
// A mapping between node name to the tf.graph.scene.HealthPill to render.
nodeNamesToHealthPills: Object,
+ // Whether the user can request health pills for individual steps from the server. This can be
+ // slow compared the default of showing sampled health pills.
+ allStepsModeEnabled: {
+ type: Boolean,
+ notify: true,
+ value: false,
+ },
+ // Relevant if allStepsModeEnabled. The specific step for which to fetch health pills from the
+ // server for.
+ specificHealthPillStep: {
+ type: Number,
+ notify: true,
+ value: 0,
+ },
// The step of health pills to show throughout the graph.
healthPillStepIndex: Number,
// Private API: Data routing between child components.
diff --git a/tensorflow/tensorboard/components/tf_graph_dashboard/tf-graph-dashboard.html b/tensorflow/tensorboard/components/tf_graph_dashboard/tf-graph-dashboard.html
index b0e296742a..d62e4ccedc 100644
--- a/tensorflow/tensorboard/components/tf_graph_dashboard/tf-graph-dashboard.html
+++ b/tensorflow/tensorboard/components/tf_graph_dashboard/tf-graph-dashboard.html
@@ -75,7 +75,11 @@ out-hierarchy-params="{{_hierarchyParams}}"
graph="[[_graph]]"
hierarchy-params="[[_hierarchyParams]]"
progress="[[_progress]]"
+ debugger-data-enabled="[[debuggerDataEnabled]]"
+ are-health-pills-loading="[[_areHealthPillsLoading]]"
node-names-to-health-pills="[[_nodeNamesToHealthPills]]"
+ all-steps-mode-enabled="{{allStepsModeEnabled}}"
+ specific-health-pill-step="{{specificHealthPillStep}}"
health-pill-step-index="[[_healthPillStepIndex]]"
render-hierarchy="{{_renderHierarchy}}"
stats="[[_stats]]"
@@ -111,18 +115,38 @@ Polymer({
_renderHierarchy: {type: Object, observer: '_renderHierarchyChanged'},
backend: {type: Object, observer: '_backendChanged'},
debuggerDataEnabled: Boolean,
+ allStepsModeEnabled: Boolean,
+ specificHealthPillStep: {type: Number, value: 0},
healthPillsToggledOn: {type: Boolean, value: true, observer: '_healthPillsToggledOnChanged'},
+ // Whether health pills are currently being loaded, in which case we may want to say show a
+ // spinner.
+ _areHealthPillsLoading: Boolean,
// Maps the names of nodes to an array of health pills (HealthPillDatums).
_nodeNamesToHealthPills: {
type: Object,
value: {},
},
_healthPillStepIndex: Number,
- runs: Array
+ // A strictly increasing ID. Each request for health pills has a unique ID. This helps us
+ // identify stale requests.
+ _healthPillRequestId: {type: Number, value: 1},
+ // The setTimeout ID for the pending request for health pills at a specific step.
+ _healthPillStepRequestTimerId: Number,
+ // The request for health pills at a specific step (as opposed to all sampled health pills) may
+ // involve slow disk reads. Hence, we throttle to 1 of those requests every this many ms.
+ _healthPillStepRequestTimerDelay: {
+ type: Number,
+ value: 500,
+ readOnly: true,
+ },
+ runs: Array,
},
listeners: {
'node-toggle-expand': '_handleNodeToggleExpand',
},
+ observers: [
+ '_maybeFetchHealthPillsAtSpecificStep(allStepsModeEnabled, specificHealthPillStep)',
+ ],
reload: function() {
if (!this.debuggerDataEnabled ||
!this.healthPillsToggledOn ||
@@ -161,12 +185,51 @@ Polymer({
}.bind(this));
},
_requestHealthPills: function() {
- this.backend.healthPills(this._renderHierarchy.getNamesOfRenderedOps()).then(function(result) {
+ this.set('_areHealthPillsLoading', true);
+ const requestId = ++this._healthPillRequestId;
+
+ if (this._healthPillStepRequestTimerId !== null) {
+ // A request for health pills is already scheduled to be initiated. Clear it, and schedule a
+ // new request.
+ window.clearTimeout(this._healthPillStepRequestTimerId);
+ this._healthPillStepRequestTimerId = null;
+ }
+
+ if (this.allStepsModeEnabled) {
+ // This path may be slow. Schedule network requests to start some time later. If another
+ // request is scheduled in the mean time, drop this current request.
+ this._healthPillStepRequestTimerId = setTimeout(function() {
+ this._healthPillStepRequestTimerId = null;
+ this._initiateNetworkRequestForHealthPills(requestId);
+ }.bind(this), this._healthPillStepRequestTimerDelay);
+ } else {
+ // The user is fetching sampled steps. This path is fast, so no need to throttle. Directly
+ // fetch the health pills across the network.
+ this._initiateNetworkRequestForHealthPills(requestId);
+ }
+ },
+ // Initiates the network request for health pills. Do not directly call this method - network
+ // requests may be throttled. Instead, call _requestHealthPills, which uses this method.
+ _initiateNetworkRequestForHealthPills: function(requestId) {
+ if (this._healthPillRequestId !== requestId) {
+ // This possibly scheduled request was outdated before it was even sent across the network. Do
+ // not bother initiating it.
+ return;
+ }
+
+ const specificStep = this.allStepsModeEnabled ? this.specificHealthPillStep : undefined;
+ this.backend.healthPills(this._renderHierarchy.getNamesOfRenderedOps(), specificStep).then(
+ function(result) {
if (!this.healthPillsToggledOn) {
// The user has opted to hide health pills via the toggle button.
return;
}
+ if (requestId !== this._healthPillRequestId) {
+ // This response is no longer relevant.
+ return;
+ }
+
// Set the index for which step to show for the health pills. By default, show the last step.
// A precondition we assume (that Tensorboard's reservoir sampling guarantees) is that all
// node names should be mapped to the same number of steps.
@@ -176,6 +239,8 @@ Polymer({
}
this.set('_nodeNamesToHealthPills', result);
+ this.set('_areHealthPillsLoading', false);
+ this.set('_healthPillStepRequestTimerId', null);
}.bind(this));
},
_datasetsEmpty: function(datasets) {
@@ -199,6 +264,15 @@ Polymer({
this.set('_nodeNamesToHealthPills', {});
}
},
+ // Fetch health pills for a specific step if applicable.
+ _maybeFetchHealthPillsAtSpecificStep: function(allStepsModeEnabled, specificHealthPillStep) {
+ if (!this._renderHierarchy) {
+ // The graph is not ready yet.
+ return;
+ }
+
+ this._requestHealthPills();
+ },
});
})();
</script>
diff --git a/tensorflow/tensorboard/components/tf_graph_info/tf-graph-info.html b/tensorflow/tensorboard/components/tf_graph_info/tf-graph-info.html
index 1fb3c5a8de..45347fb1de 100644
--- a/tensorflow/tensorboard/components/tf_graph_info/tf-graph-info.html
+++ b/tensorflow/tensorboard/components/tf_graph_info/tf-graph-info.html
@@ -17,6 +17,7 @@ limitations under the License.
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../paper-slider/paper-slider.html">
+<link rel="import" href="../paper-spinner/paper-spinner-lite.html">
<link rel="import" href="../tf-graph-common/tf-graph-common.html">
<link rel="import" href="tf-node-info.html">
@@ -78,6 +79,17 @@ h2 {
* Apparently, the paper-slider lacks a mixin for those padding values. */
width: calc(100% + 31px);
}
+
+#health-pills-loading-spinner {
+ width: 20px;
+ height: 20px;
+ vertical-align: top;
+}
+
+#health-pill-step-number-input {
+ text-align: center;
+ vertical-align: top;
+}
</style>
<template is="dom-if" if="{{selectedNode}}">
<paper-material elevation="1" class="card">
@@ -91,19 +103,49 @@ h2 {
</tf-node-info>
</paper-material>
</template>
-<template is="dom-if" if="[[_healthPillsAvailable(nodeNamesToHealthPills)]]">
+<template is="dom-if" if="[[_healthPillsAvailable(debuggerDataEnabled, nodeNamesToHealthPills)]]">
<paper-material elevation="1" class="card health-pill-legend">
- <template is="dom-if" if="[[_maxStepIndex]]">
- <h2>
- Step of Health Pills: [[_currentStepDisplayValue]]
- </h2>
+ <div class="title">
+ Enable all (not just sampled) steps. Requires slow disk read.
+ </div>
+ <paper-toggle-button id="enableAllStepsModeToggle" checked="{{allStepsModeEnabled}}">
+ </paper-toggle-button>
+ <h2>
+ Step of Health Pills:
+ <template is="dom-if" if="[[allStepsModeEnabled]]">
+ <input type="number"
+ id="health-pill-step-number-input"
+ min="0"
+ max="[[_biggestStepEverSeen]]"
+ value="{{specificHealthPillStep::input}}">
+ </template>
+ <template is="dom-if" if="[[!allStepsModeEnabled]]">
+ [[_currentStepDisplayValue]]
+ </template>
+
+ <paper-spinner-lite active
+ hidden$=[[!areHealthPillsLoading]]
+ id="health-pills-loading-spinner"></paper-spinner-lite>
+ </h2>
+ <template is="dom-if" if="[[allStepsModeEnabled]]">
<paper-slider
id="health-pill-step-slider"
- immediate-value="{{healthPillStepIndex}}"
- max="[[_maxStepIndex]]"
+ immediate-value="{{specificHealthPillStep}}"
+ max="[[_biggestStepEverSeen]]"
snaps
step="1"
- value="{{healthPillStepIndex}}"></paper-slider>
+ value="{{specificHealthPillStep}}"></paper-slider>
+ </template>
+ <template is="dom-if" if="[[!allStepsModeEnabled]]">
+ <template is="dom-if" if="[[_maxStepIndex]]">
+ <paper-slider
+ id="health-pill-step-slider"
+ immediate-value="{{healthPillStepIndex}}"
+ max="[[_maxStepIndex]]"
+ snaps
+ step="1"
+ value="{{healthPillStepIndex}}"></paper-slider>
+ </template>
</template>
<h2>
Health Pill
@@ -141,6 +183,13 @@ h2 {
type: Number,
notify: true,
},
+ // Only relevant if we are in all steps mode, in which case the user may want to view health
+ // pills for a specific step.
+ specificHealthPillStep: {
+ type: Number,
+ value: 0,
+ notify: true,
+ },
colorBy: String,
// Two-ways
selectedNode: {
@@ -156,6 +205,11 @@ h2 {
type: Number,
notify: true
},
+ // Whether debugger data is enabled for this instance of Tensorboard.
+ debuggerDataEnabled: Boolean,
+ // Whether health pills are currently being loaded, in which case we show a spinner (and the
+ // current health pills shown might be out of date).
+ areHealthPillsLoading: Boolean,
healthPillEntries: {
type: Array,
value: tf.graph.scene.healthPillEntries,
@@ -163,7 +217,19 @@ h2 {
},
healthPillValuesForSelectedNode: {
type: Array,
- computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode)',
+ computed: '_computeHealthPillForNode(nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading)',
+ },
+ // When all-steps mode is enabled, the user can request health pills for any step. In this
+ // mode, Tensorboard makes a request every time the user drags the slider to a different step.
+ allStepsModeEnabled: {
+ type: Boolean,
+ notify: true,
+ },
+ // The biggest step value ever seen. Used to determine what steps of health pills to let the
+ // user fetch in all steps mode.
+ _biggestStepEverSeen: {
+ type: Number,
+ computed: '_computeBiggestStepEverSeen(nodeNamesToHealthPills)',
},
_maxStepIndex: {
type: Number,
@@ -171,7 +237,7 @@ h2 {
},
_currentStepDisplayValue: {
type: String,
- computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex)',
+ computed: '_computeCurrentStepDisplayValue(nodeNamesToHealthPills, healthPillStepIndex, allStepsModeEnabled, specificHealthPillStep, areHealthPillsLoading)',
},
},
listeners: {
@@ -188,12 +254,11 @@ h2 {
_nodeListItemMouseout: function() {
this.highlightedNode = null;
},
- _healthPillsAvailable: function(nodeNamesToHealthPills) {
- let count = 0;
- for (let nodeName in nodeNamesToHealthPills) {
- return true;
- }
- return false;
+ _healthPillsAvailable: function(debuggerDataEnabled, nodeNamesToHealthPills) {
+ // So long as there is a mapping (even if empty) from node name to health pills, show the
+ // legend and slider. We do that because, even if no health pills exist at the current step,
+ // the user may desire to change steps, and the slider must show for the user to do that.
+ return debuggerDataEnabled && nodeNamesToHealthPills;
},
_computeTensorCountString: function(healthPillValuesForSelectedNode, valueIndex) {
if (!healthPillValuesForSelectedNode) {
@@ -204,7 +269,12 @@ h2 {
return healthPillValuesForSelectedNode[valueIndex].toFixed(0);
},
_computeHealthPillForNode: function(
- nodeNamesToHealthPills, healthPillStepIndex, selectedNode) {
+ nodeNamesToHealthPills, healthPillStepIndex, selectedNode, allStepsModeEnabled, areHealthPillsLoading) {
+ if (areHealthPillsLoading) {
+ // Health pills are loading. Do not render data that is out of date.
+ return null;
+ }
+
if (!selectedNode) {
// No node is selected.
return null;
@@ -216,7 +286,9 @@ h2 {
return null;
}
- const healthPill = healthPills[healthPillStepIndex];
+ // If all steps mode is enabled, we use the first health pill in the list because the JSON
+ // response from the server is a mapping between node name and a list of 1 health pill.
+ const healthPill = healthPills[allStepsModeEnabled ? 0 : healthPillStepIndex];
if (!healthPill) {
// This node lacks a health pill at the current step.
return null;
@@ -225,16 +297,44 @@ h2 {
// The health pill count values start at 2. Each health pill contains 6 values.
return healthPill.value.slice(2, 8);
},
- _computeCurrentStepDisplayValue: function(nodeNamesToHealthPills, healthPillStepIndex) {
+ _computeCurrentStepDisplayValue: function(
+ nodeNamesToHealthPills,
+ healthPillStepIndex,
+ allStepsModeEnabled,
+ specificHealthPillStep,
+ areHealthPillsLoading) {
+ if (allStepsModeEnabled) {
+ // The user seeks health pills for specific step from the server.
+ return specificHealthPillStep.toFixed(0);
+ }
+
+ if (areHealthPillsLoading) {
+ // The current step is undefined.
+ return 0;
+ }
+
for (let nodeName in nodeNamesToHealthPills) {
// All nodes have the same number of steps stored, so only examine 1 node. We cannot
// directly index into the nodeNamesToHealthPills object because we do not have a key.
+ // If all steps mode is enabled, we only have 1 step to show.
return nodeNamesToHealthPills[nodeName][healthPillStepIndex].step.toFixed(0);
}
// The current step could not be computed.
return 0;
},
+ _computeBiggestStepEverSeen: function(nodeNamesToHealthPills) {
+ for (let nodeName in nodeNamesToHealthPills) {
+ // All nodes have the same number of steps stored, so only examine 1 node.
+ // The index is 1 less than the count. Tensorboard backend logic guarantees that the length
+ // of the array will be greater than 1.
+ var healthPills = nodeNamesToHealthPills[nodeName];
+ return Math.max(this._biggestStepEverSeen, healthPills[healthPills.length - 1].step);
+ }
+
+ // No steps seen so far. Default to 0.
+ return this._biggestStepEverSeen || 0;
+ },
_computeMaxStepIndex: function(nodeNamesToHealthPills) {
for (let nodeName in nodeNamesToHealthPills) {
// All nodes have the same number of steps stored, so only examine 1 node.