diff options
-rw-r--r-- | WORKSPACE | 78 | ||||
-rw-r--r-- | tensorflow/tensorboard/TAG | 2 | ||||
-rw-r--r-- | tensorflow/tensorboard/bower.json | 8 | ||||
-rw-r--r-- | tensorflow/tensorboard/dist/tf-tensorboard.html | 693 |
4 files changed, 502 insertions, 279 deletions
@@ -46,7 +46,7 @@ new_git_repository( new_git_repository( name = "font_roboto", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/font-roboto.git", + remote = "https://github.com/polymerelements/font-roboto.git", tag = "v1.0.1", ) @@ -60,49 +60,49 @@ new_git_repository( new_git_repository( name = "iron_a11y_announcer", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-a11y-announcer.git", + remote = "https://github.com/polymerelements/iron-a11y-announcer.git", tag = "v1.0.4", ) new_git_repository( name = "iron_a11y_keys_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-a11y-keys-behavior.git", + remote = "https://github.com/polymerelements/iron-a11y-keys-behavior.git", tag = "v1.1.2", ) new_git_repository( name = "iron_ajax", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-ajax.git", + remote = "https://github.com/polymerelements/iron-ajax.git", tag = "v1.1.1", ) new_git_repository( name = "iron_autogrow_textarea", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-autogrow-textarea.git", + remote = "https://github.com/polymerelements/iron-autogrow-textarea.git", tag = "v1.0.12", ) new_git_repository( name = "iron_behaviors", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-behaviors.git", + remote = "https://github.com/polymerelements/iron-behaviors.git", tag = "v1.0.13", ) new_git_repository( name = "iron_checked_element_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-checked-element-behavior.git", + remote = "https://github.com/polymerelements/iron-checked-element-behavior.git", tag = "v1.0.4", ) new_git_repository( name = "iron_collapse", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-collapse.git", + remote = "https://github.com/polymerelements/iron-collapse.git", tag = "v1.0.6", ) @@ -116,7 +116,7 @@ new_git_repository( new_git_repository( name = "iron_fit_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-fit-behavior.git", + remote = "https://github.com/polymerelements/iron-fit-behavior.git", tag = "v1.0.6", ) @@ -130,7 +130,7 @@ new_git_repository( new_git_repository( name = "iron_form_element_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-form-element-behavior.git", + remote = "https://github.com/polymerelements/iron-form-element-behavior.git", tag = "v1.0.6", ) @@ -151,28 +151,28 @@ new_git_repository( new_git_repository( name = "iron_iconset_svg", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-iconset-svg.git", + remote = "https://github.com/polymerelements/iron-iconset-svg.git", tag = "v1.0.9", ) new_git_repository( name = "iron_input", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-input.git", + remote = "https://github.com/polymerelements/iron-input.git", tag = "v1.0.9", ) new_git_repository( name = "iron_list", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-list.git", + remote = "https://github.com/polymerelements/iron-list.git", tag = "v1.1.7", ) new_git_repository( name = "iron_menu_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-menu-behavior.git", + remote = "https://github.com/polymerelements/iron-menu-behavior.git", tag = "v1.1.5", ) @@ -187,13 +187,13 @@ new_git_repository( name = "iron_overlay_behavior", build_file = "bower.BUILD", remote = "https://github.com/polymerelements/iron-overlay-behavior.git", - tag = "v1.6.1", + tag = "v1.6.2", ) new_git_repository( name = "iron_range_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-range-behavior.git", + remote = "https://github.com/polymerelements/iron-range-behavior.git", tag = "v1.0.4", ) @@ -207,14 +207,14 @@ new_git_repository( new_git_repository( name = "iron_selector", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-selector.git", + remote = "https://github.com/polymerelements/iron-selector.git", tag = "v1.2.4", ) new_git_repository( name = "iron_validatable_behavior", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/iron-validatable-behavior.git", + remote = "https://github.com/polymerelements/iron-validatable-behavior.git", tag = "v1.0.5", ) @@ -235,56 +235,56 @@ new_git_repository( new_git_repository( name = "paper_behaviors", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-behaviors.git", + remote = "https://github.com/polymerelements/paper-behaviors.git", tag = "v1.0.11", ) new_git_repository( name = "paper_button", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-button.git", + remote = "https://github.com/polymerelements/paper-button.git", tag = "v1.0.11", ) new_git_repository( name = "paper_checkbox", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-checkbox.git", + remote = "https://github.com/polymerelements/paper-checkbox.git", tag = "v1.1.3", ) new_git_repository( name = "paper_dropdown_menu", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-dropdown-menu.git", + remote = "https://github.com/polymerelements/paper-dropdown-menu.git", tag = "v1.1.3", ) new_git_repository( name = "paper_header_panel", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-header-panel.git", + remote = "https://github.com/polymerelements/paper-header-panel.git", tag = "v1.1.4", ) new_git_repository( name = "paper_icon_button", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-icon-button.git", + remote = "https://github.com/polymerelements/paper-icon-button.git", tag = "v1.0.6", ) new_git_repository( name = "paper_input", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-input.git", + remote = "https://github.com/polymerelements/paper-input.git", tag = "v1.1.5", ) new_git_repository( name = "paper_item", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-item.git", + remote = "https://github.com/polymerelements/paper-item.git", tag = "v1.1.4", ) @@ -298,7 +298,7 @@ new_git_repository( new_git_repository( name = "paper_menu", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-menu.git", + remote = "https://github.com/polymerelements/paper-menu.git", tag = "v1.2.2", ) @@ -306,27 +306,27 @@ new_git_repository( name = "paper_menu_button", build_file = "bower.BUILD", remote = "https://github.com/polymerelements/paper-menu-button.git", - tag = "v1.0.4", + tag = "v1.1.0", ) new_git_repository( name = "paper_progress", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-progress.git", - tag = "v1.0.8", + remote = "https://github.com/polymerelements/paper-progress.git", + tag = "v1.0.9", ) new_git_repository( name = "paper_radio_button", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-radio-button.git", + remote = "https://github.com/polymerelements/paper-radio-button.git", tag = "v1.1.1", ) new_git_repository( name = "paper_radio_group", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-radio-group.git", + remote = "https://github.com/polymerelements/paper-radio-group.git", tag = "v1.0.9", ) @@ -340,35 +340,35 @@ new_git_repository( new_git_repository( name = "paper_slider", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-slider.git", + remote = "https://github.com/polymerelements/paper-slider.git", tag = "v1.0.8", ) new_git_repository( name = "paper_styles", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-styles.git", + remote = "https://github.com/polymerelements/paper-styles.git", tag = "v1.1.1", ) new_git_repository( name = "paper_tabs", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-tabs.git", + remote = "https://github.com/polymerelements/paper-tabs.git", tag = "v1.2.4", ) new_git_repository( name = "paper_toggle_button", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-toggle-button.git", + remote = "https://github.com/polymerelements/paper-toggle-button.git", tag = "v1.0.12", ) new_git_repository( name = "paper_toolbar", build_file = "bower.BUILD", - remote = "https://github.com/PolymerElements/paper-toolbar.git", + remote = "https://github.com/polymerelements/paper-toolbar.git", tag = "v1.1.2", ) @@ -382,7 +382,7 @@ new_git_repository( new_git_repository( name = "polymer", build_file = "bower.BUILD", - remote = "https://github.com/Polymer/polymer.git", + remote = "https://github.com/polymer/polymer.git", tag = "v1.4.0", ) @@ -403,6 +403,6 @@ new_git_repository( new_git_repository( name = "webcomponentsjs", build_file = "bower.BUILD", - remote = "https://github.com/Polymer/webcomponentsjs.git", + remote = "https://github.com/polymer/webcomponentsjs.git", tag = "v0.7.21", ) diff --git a/tensorflow/tensorboard/TAG b/tensorflow/tensorboard/TAG index 60d3b2f4a4..b6a7d89c68 100644 --- a/tensorflow/tensorboard/TAG +++ b/tensorflow/tensorboard/TAG @@ -1 +1 @@ -15 +16 diff --git a/tensorflow/tensorboard/bower.json b/tensorflow/tensorboard/bower.json index 39b50cc2aa..503fd25e4f 100644 --- a/tensorflow/tensorboard/bower.json +++ b/tensorflow/tensorboard/bower.json @@ -46,7 +46,7 @@ "iron-collapse": "PolymerElements/iron-collapse#1.0.6", "iron-dropdown": "PolymerElements/iron-dropdown#1.3.0", "iron-fit-behavior": "PolymerElements/iron-fit-behavior#1.0.6", - "iron-flex-layout": "PolymerElements/iron-flex-layout#1.2.3", + "iron-flex-layout": "PolymerElements/iron-flex-layout#1.3.0", "iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#1.0.6", "iron-icon": "PolymerElements/iron-icon#1.0.8", "iron-icons": "PolymerElements/iron-icons#1.1.3", @@ -73,7 +73,7 @@ "paper-material": "PolymerElements/paper-material#1.0.6", "paper-menu": "PolymerElements/paper-menu#1.2.2", "paper-menu-button": "PolymerElements/paper-menu-button#1.0.4", - "paper-progress": "PolymerElements/paper-progress#1.0.8", + "paper-progress": "PolymerElements/paper-progress#1.0.9", "paper-radio-button": "PolymerElements/paper-radio-button#1.1.1", "paper-radio-group": "PolymerElements/paper-radio-group#1.0.9", "paper-ripple": "PolymerElements/paper-ripple#1.0.5", @@ -117,7 +117,7 @@ "iron-collapse": "1.0.6", "iron-dropdown": "1.3.0", "iron-fit-behavior": "1.0.6", - "iron-flex-layout": "1.2.3", + "iron-flex-layout": "1.3.0", "iron-form-element-behavior": "1.0.6", "iron-icon": "1.0.8", "iron-icons": "1.1.3", @@ -144,7 +144,7 @@ "paper-material": "1.0.6", "paper-menu": "1.2.2", "paper-menu-button": "1.0.4", - "paper-progress": "1.0.8", + "paper-progress": "1.0.9", "paper-radio-button": "1.1.1", "paper-radio-group": "1.0.9", "paper-ripple": "1.0.5", diff --git a/tensorflow/tensorboard/dist/tf-tensorboard.html b/tensorflow/tensorboard/dist/tf-tensorboard.html index 15ede2b755..a9cc4e419e 100644 --- a/tensorflow/tensorboard/dist/tf-tensorboard.html +++ b/tensorflow/tensorboard/dist/tf-tensorboard.html @@ -1,4 +1,22 @@ -// AUTOGENERATED FILE - DO NOT MODIFY +<!-- Copyright 2015 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +============================================================================ + +This file is generated by `gulp` & `vulcanize`. Do not directly change it. +Instead, use `gulp regenerate` to create a new version with your changes. +--> + <html><head><meta charset="UTF-8"> </head><body><div hidden="" by-vulcanize=""> <dom-module id="tf-tooltip-coordinator" assetpath="../tf-event-dashboard/"> @@ -405,8 +423,8 @@ <style is="custom-style"> :root { - --tb-orange-weak: #fcb938; - --tb-orange-strong: #f3913e; + --tb-orange-weak: #ffa726; + --tb-orange-strong: #f57c00; --tb-grey-darker: #e2e2e2; --tb-grey-lighter: #f3f3f3; --tb-ui-dark-accent: #757575; @@ -1797,19 +1815,33 @@ var TF; */ var RequestCancellationError = (function (_super) { __extends(RequestCancellationError, _super); - function RequestCancellationError(message) { - _super.call(this, message); + function RequestCancellationError() { + _super.apply(this, arguments); this.name = "RequestCancellationError"; } return RequestCancellationError; }(Error)); Backend.RequestCancellationError = RequestCancellationError; + var RequestNetworkError = (function (_super) { + __extends(RequestNetworkError, _super); + function RequestNetworkError(req, url) { + _super.call(this); + this.message = "RequestNetworkError: " + req.status + " at " + url; + this.name = "RequestNetworkError"; + this.req = req; + this.url = url; + } + return RequestNetworkError; + }(Error)); + Backend.RequestNetworkError = RequestNetworkError; var RequestManager = (function () { - function RequestManager(nSimultaneousRequests) { + function RequestManager(nSimultaneousRequests, maxRetries) { if (nSimultaneousRequests === void 0) { nSimultaneousRequests = 10; } + if (maxRetries === void 0) { maxRetries = 3; } this._queue = []; this._nActiveRequests = 0; this._nSimultaneousRequests = nSimultaneousRequests; + this._maxRetries = maxRetries; } /* Gives a promise that loads assets from given url (respects queuing) */ RequestManager.prototype.request = function (url) { @@ -1819,11 +1851,20 @@ var TF; _this._queue.push(resolver); _this.launchRequests(); }).then(function () { - return _this._promiseFromUrl(url); + return _this.promiseWithRetries(url, _this._maxRetries); }).then(function (response) { + // Success - Let's free space for another active reqest, and launch it _this._nActiveRequests--; - _this.launchRequests(); // since we may have queued responses to launch + _this.launchRequests(); return response; + }, function (rejection) { + if (rejection.name === "RequestNetworkError") { + // If we failed due to network error, we should decrement + // _nActiveRequests because this request was active + _this._nActiveRequests--; + _this.launchRequests(); + } + return Promise.reject(rejection); }); return promise; }; @@ -1846,6 +1887,29 @@ var TF; this._queue.pop().resolve(); } }; + /** + * Try to request a given URL using overwritable _promiseFromUrl method. + * If the request fails for any reason, we will retry up to maxRetries + * times. In practice, this will help us paper over transient network issues + * like "502 Bad Gateway". + * By default, Chrome displays network errors in console, so + * the user will be able to tell when the requests are failing. I think this + * is a feature, if the request failures and retries are causing any + * pain to users, they can see it and file issues. + */ + RequestManager.prototype.promiseWithRetries = function (url, maxRetries) { + var _this = this; + var success = function (x) { return x; }; + var failure = function (x) { + if (maxRetries > 0) { + return _this.promiseWithRetries(url, maxRetries - 1); + } + else { + return Promise.reject(x); + } + }; + return this._promiseFromUrl(url).then(success, failure); + }; /* Actually get promise from url using XMLHttpRequest */ RequestManager.prototype._promiseFromUrl = function (url) { return new Promise(function (resolve, reject) { @@ -1856,11 +1920,11 @@ var TF; resolve(JSON.parse(req.responseText)); } else { - reject(Error("Status: " + req.status + ":" + req.statusText + " at url: " + url)); + reject(new RequestNetworkError(req, url)); } }; req.onerror = function () { - reject(Error("Network error")); + reject(new RequestNetworkError(req, url)); }; req.send(); }); @@ -1952,11 +2016,19 @@ var TF; } function standardRoute(route) { return function (tag, run) { - return dataDir + "/" + route + clean(Backend.queryEncoder({ tag: tag, run: run })); + var url = dataDir + "/" + route + clean(Backend.queryEncoder({ tag: tag, run: run })); + if (demoMode) { + url += ".json"; + } + return url; }; } function individualImageUrl(query) { - return dataDir + "/" + clean("individualImage?" + query); + var url = dataDir + "/" + clean("individualImage?" + query); + if (demoMode) { + url += ".png"; + } + return url; } function graphUrl(run, limit_attr_size, large_attrs_key) { var query_params = [["run", clean(run)]]; @@ -1969,10 +2041,14 @@ var TF; var query = query_params.map(function (param) { return param[0] + "=" + encodeURIComponent(param[1]); }).join("&"); - return dataDir + "/graph" + clean("?" + query); + var url = dataDir + "/graph" + clean("?" + query); + if (demoMode) { + url += ".pbtxt"; + } + return url; } return { - runs: function () { return dataDir + "/runs"; }, + runs: function () { return dataDir + "/runs" + (demoMode ? ".json" : ""); }, individualImage: individualImageUrl, graph: graphUrl, scalars: standardRoute("scalars"), @@ -2108,7 +2184,16 @@ var TF; var p; var url = this.router.histograms(tag, run); p = this.requestManager.request(url); - return p.then(map(detupler(createHistogram))); + return p.then(map(detupler(createHistogram))) + .then(function (histos) { + return histos.map(function (histo, i) { + return { + wall_time: histo.wall_time, + step: histo.step, + bins: convertBins(histo) + }; + }); + }); }; /** * Return a promise containing ImageDatums for given run and tag. @@ -2209,6 +2294,38 @@ var TF; }; } ; + /** + * Takes histogram data as stored by tensorboard backend and converts it to + * the standard d3 histogram data format to make it more compatible and easier to + * visualize. When visualizing histograms, having the left edge and width makes + * things quite a bit easier. + * + * @param {histogram} Histogram - A histogram from tensorboard backend. + * @return {HistogramBin[]} - Each bin has an x (left edge), a dx (width), and a y (count). + * + * If given rightedges are inclusive, then these left edges (x) are exclusive. + */ + function convertBins(histogram) { + if (histogram.bucketRightEdges.length !== histogram.bucketCounts.length) { + throw (new Error("Edges and counts are of different lengths.")); + } + var previousRightEdge = histogram.min; + return histogram.bucketRightEdges.map(function (rightEdge, i) { + // Use the previous bin's rightEdge as the new leftEdge + var left = previousRightEdge; + // We need to clip the rightEdge because right-most edge can be + // infinite-sized + var right = Math.min(histogram.max, rightEdge); + // Store rightEdgeValue for next iteration + previousRightEdge = rightEdge; + return { + x: left, + dx: right - left, + y: histogram.bucketCounts[i] + }; + }); + } + Backend_1.convertBins = convertBins; })(Backend = TF.Backend || (TF.Backend = {})); })(TF || (TF = {})); </script> @@ -2548,7 +2665,7 @@ var TF; </style> <template> <template is="dom-if" if="[[imageUrl]]"> - <img src="[[imageUrl]]"> + <img src="[[imageUrl]]" on-error="retry"> </template> </template> <script> @@ -2569,7 +2686,11 @@ var TF; }, ready: function() { this.reload(); - } + }, + retry: function() { + this.imageUrl = ""; // force reload + this.reload(); + }, }); </script> </dom-module> @@ -2747,11 +2868,6 @@ Polymer({ notify: true, }, datasets: Array, - hasStats: { - type: Boolean, - readOnly: true, // This property produces data. - notify: true - }, selectedDataset: Number, selectedFile: { type: Object, @@ -2767,21 +2883,41 @@ Polymer({ readOnly: true, //readonly so outsider can't change this via binding notify: true }, - outGraphName: { - type: String, - readOnly: true, - notify: true - }, outHierarchyParams: { type: Object, readOnly: true, notify: true }, + outStats: { + type: Object, + readOnly: true, // This property produces data. + notify: true + } }, observers: [ - '_selectedDatasetChanged(selectedDataset, datasets)' + '_selectedDatasetChanged(selectedDataset, datasets)', + '_readAndParseMetadata(selectedDataset, selectedMetadataTag, datasets)' ], - _parseAndConstructHierarchicalGraph: function(dataset, pbTxtFile) { + _readAndParseMetadata: function(datasetIndex, metadataIndex, datasets) { + if (metadataIndex == -1 || datasets[datasetIndex] == null || + datasets[datasetIndex].runMetadata == null || + datasets[datasetIndex].runMetadata[metadataIndex] == null) { + this._setOutStats(null); + return; + } + var path = datasets[datasetIndex].runMetadata[metadataIndex].path; + // Reset the progress bar to 0. + this.set('progress', { + value: 0, + msg: '' + }); + var tracker = tf.getTracker(this); + tf.graph.parser.fetchAndParseMetadata(path, tracker) + .then(function(stats) { + this._setOutStats(stats); + }.bind(this)); + }, + _parseAndConstructHierarchicalGraph: function(path, pbTxtFile) { // Reset the progress bar to 0. this.set('progress', { value: 0, @@ -2800,13 +2936,10 @@ Polymer({ seriesMap: {}, }; this._setOutHierarchyParams(hierarchyParams); - var statsJson; var dataTracker = tf.getSubtaskTracker(tracker, 30, 'Data'); - tf.graph.parser.readAndParseData(dataset, pbTxtFile, dataTracker) - .then(function(result) { + tf.graph.parser.fetchAndParseGraphData(path, pbTxtFile, dataTracker) + .then(function(graph) { // Build the flat graph (consists only of Op nodes). - var nodes = result.nodes; - statsJson = result.statsJson; // This is the whitelist of inputs on op types that are considered // reference edges. "Assign 0" indicates that the first input to @@ -2831,18 +2964,11 @@ Polymer({ outEmbeddingTypes: ['^[a-zA-Z]+Summary$'], refEdges: refEdges }; - var graphTracker = tf.getSubtaskTracker(tracker, 20, - 'Graph'); - return tf.graph.build(nodes, buildParams, graphTracker); + var graphTracker = tf.getSubtaskTracker(tracker, 20, 'Graph'); + return tf.graph.build(graph, buildParams, graphTracker); }) .then(function(graph) { this._setOutGraph(graph); - if (statsJson) { - // If there are associated stats, join them with the graph. - tf.time('Joining stats info with graph...', function() { - tf.graph.joinStatsInfoWithGraph(graph, statsJson); - }); - } var hierarchyTracker = tf.getSubtaskTracker(tracker, 50, 'Namespace hierarchy'); return tf.graph.hierarchy.build(graph, hierarchyParams, hierarchyTracker); @@ -2850,7 +2976,6 @@ Polymer({ .then(function(graphHierarchy) { // Update the properties which notify the parent with the // graph hierarchy and whether the data has live stats or not. - this._setHasStats(statsJson != null); this._setOutGraphHierarchy(graphHierarchy); }.bind(this)) .catch(function(e) { @@ -2860,9 +2985,7 @@ Polymer({ }); }, _selectedDatasetChanged: function(datasetIndex, datasets) { - var dataset = datasets[datasetIndex]; - this._parseAndConstructHierarchicalGraph(dataset); - this._setOutGraphName(dataset.name); + this._parseAndConstructHierarchicalGraph(datasets[datasetIndex].path); }, _selectedFileChanged: function(e) { if (!e) { @@ -3177,33 +3300,36 @@ var tf; * Joins the information from the stats file (memory, compute time) with the * graph information. */ - function joinStatsInfoWithGraph(graph, statsJson) { - _.each(statsJson.devStats, function (stats) { - _.each(stats.nodeStats, function (nodeStats) { + function joinStatsInfoWithGraph(graph, stats) { + _.each(stats.dev_stats, function (devStats) { + _.each(devStats.node_stats, function (nodeStats) { // Lookup the node in the graph by its original name, e.g. A. If not // found, lookup by the rewritten name A/(A) in case the name is both // a namespace and a node name. - var nodeName = nodeStats.nodeName in graph.nodes ? - nodeStats.nodeName : - nodeStats.nodeName + graph_1.NAMESPACE_DELIM + "(" + nodeStats.nodeName + ")"; - if (nodeName in graph.nodes) { - // Compute the total bytes used. - var totalBytes_1 = 0; - if (nodeStats.memory) { - _.each(nodeStats.memory, function (alloc) { - if (alloc.totalBytes) { - totalBytes_1 += Number(alloc.totalBytes); - } - }); - } - var outputSize = null; - if (nodeStats.output) { - outputSize = _.map(nodeStats.output, function (output) { - return _.map(output.tensorDescription.shape.dim, function (dim) { return Number(dim.size); }); - }); - } - graph.nodes[nodeName].stats = new NodeStats(totalBytes_1, Number(nodeStats.allEndRelMicros), outputSize); + var nodeName = nodeStats.node_name in graph.nodes ? + nodeStats.node_name : + nodeStats.node_name + graph_1.NAMESPACE_DELIM + "(" + nodeStats.node_name + ")"; + // Couldn't find a matching node. + if (!(nodeName in graph.nodes)) { + return; + } + // Compute the total bytes used. + var totalBytes = 0; + if (nodeStats.memory) { + _.each(nodeStats.memory, function (alloc) { + if (alloc.total_bytes) { + totalBytes += Number(alloc.total_bytes); + } + }); + } + var outputSize = null; + if (nodeStats.output) { + outputSize = _.map(nodeStats.output, function (output) { + return _.map(output.tensor_description.shape.dim, function (dim) { return Number(dim.size); }); + }); } + graph.nodes[nodeName].device = devStats.device; + graph.nodes[nodeName].stats = new NodeStats(totalBytes, Number(nodeStats.all_end_rel_micros), outputSize); }); }); } @@ -3261,7 +3387,6 @@ var tf; this.templateId = null; /** Metanode which contains this node, if any */ this.parentNode = null; - this.stats = new NodeStats(0, 0, null); this.hasNonControlEdges = false; this.include = InclusionType.UNSPECIFIED; } @@ -3335,6 +3460,7 @@ var tf; // Compute the size of the tensor flowing through this // base edge. this.totalSize += MetaedgeImpl.computeSizeOfEdge(edge, h); + h.maxMetaEdgeSize = Math.max(h.maxMetaEdgeSize, this.totalSize); }; MetaedgeImpl.computeSizeOfEdge = function (edge, h) { var opNode = h.node(edge.v); @@ -3343,6 +3469,7 @@ var tf; // a lower bound for the total size. return 1; } + h.hasShapeInfo = true; // Sum the sizes of all output tensors. return _(opNode.outputShapes).map(function (shape) { // If the shape is unknown, treat it as 1 when computing @@ -3394,7 +3521,6 @@ var tf; this.parentNode = null; this.deviceHistogram = {}; this.hasNonControlEdges = false; - this.stats = new NodeStats(0, 0, null); this.include = InclusionType.UNSPECIFIED; } return SeriesNodeImpl; @@ -3439,7 +3565,7 @@ var tf; } // We didn't find OUTPUT_SHAPES_KEY in attributes, so we don't know anything // about the output tensors. - return result; + return null; } /** * Normalizes the inputs and extracts associated metadata: @@ -3828,7 +3954,7 @@ var tf; /** * Fetches a text file and returns a promise of the result. */ - function readPbTxt(filepath) { + function fetchPbTxt(filepath) { return new Promise(function (resolve, reject) { d3.text(filepath, function (error, text) { if (error) { @@ -3839,55 +3965,40 @@ var tf; }); }); } - parser.readPbTxt = readPbTxt; + parser.fetchPbTxt = fetchPbTxt; /** - * Fetches and parses a json file and returns a promise of the result. + * Fetches the metadata file, parses it and returns a promise of the result. */ - function readJson(filepath) { - return new Promise(function (resolve, reject) { - d3.json(filepath, function (error, text) { - if (error) { - reject(error); - return; - } - resolve(text); - }); + function fetchAndParseMetadata(path, tracker) { + return tf.runTask("Reading metadata pbtxt", 40, function () { + if (path == null) { + return Promise.resolve(null); + } + return fetchPbTxt(path).then(function (text) { return new Blob([text]); }); + }, tracker) + .then(function (blob) { + return tf.runTask("Parsing metadata.pbtxt", 60, function () { + return blob != null ? parseStatsPbTxt(blob) : null; + }, tracker); }); } - parser.readJson = readJson; + parser.fetchAndParseMetadata = fetchAndParseMetadata; /** - * Reads the graph and stats file (if available), parses them and returns a - * promise of the result. + * Fetches the graph file, parses it and returns a promise of the result. */ - function readAndParseData(dataset, pbTxtFile, tracker) { - var graphPbTxt; - var statsJson; - return tf.runTask("Reading graph.pbtxt", 20, function () { + function fetchAndParseGraphData(path, pbTxtFile, tracker) { + return tf.runTask("Reading graph pbtxt", 40, function () { return pbTxtFile ? Promise.resolve(pbTxtFile) : - readPbTxt(dataset.path).then(function (text) { return new Blob([text]); }); + fetchPbTxt(path).then(function (text) { return new Blob([text]); }); }, tracker) .then(function (blob) { - graphPbTxt = blob; - return tf.runTask("Reading stats.pbtxt", 20, function () { - return (dataset != null && dataset.statsPath != null) ? - readJson(dataset.statsPath) : null; - }, tracker); - }) - .then(function (json) { - statsJson = json; return tf.runTask("Parsing graph.pbtxt", 60, function () { - return parsePbtxtFile(graphPbTxt); + return parseGraphPbTxt(blob); }, tracker); - }) - .then(function (nodes) { - return { - nodes: nodes, - statsJson: statsJson - }; }); } - parser.readAndParseData = readAndParseData; + parser.fetchAndParseGraphData = fetchAndParseGraphData; /** * Parse a file object in a streaming fashion line by line (or custom delim). * Can handle very large files. @@ -3946,13 +4057,55 @@ var tf; } parser.streamParse = streamParse; /** - * Parses a proto txt file or blob into javascript object. + * Since proto-txt doesn't explicitly say whether an attribute is repeated + * (an array) or not, we keep a hard-coded list of attributes that are known + * to be repeated. This list is used in parsing time to convert repeated + * attributes into arrays even when the attribute only shows up once in the + * object. + */ + var GRAPH_REPEATED_FIELDS = { + "node": true, + "node.input": true, + "node.attr": true, + "node.attr.value.list.type": true, + "node.attr.value.shape.dim": true, + "node.attr.value.tensor.string_val": true, + "node.attr.value.tensor.tensor_shape.dim": true, + "node.attr.value.list.shape": true, + "node.attr.value.list.shape.dim": true, + "node.attr.value.list.s": true + }; + var METADATA_REPEATED_FIELDS = { + "step_stats.dev_stats": true, + "step_stats.dev_stats.node_stats": true, + "step_stats.dev_stats.node_stats.output": true, + "step_stats.dev_stats.node_stats.memory": true, + "step_stats.dev_stats.node_stats.output.tensor_description.shape.dim": true + }; + /** + * Parses a blob of proto txt file into a raw Graph object. + */ + function parseGraphPbTxt(input) { + return parsePbtxtFile(input, GRAPH_REPEATED_FIELDS).then(function (obj) { return obj["node"]; }); + } + parser.parseGraphPbTxt = parseGraphPbTxt; + /** + * Parses a blob of proto txt file into a StepStats object. + */ + function parseStatsPbTxt(input) { + return parsePbtxtFile(input, METADATA_REPEATED_FIELDS) + .then(function (obj) { return obj["step_stats"]; }); + } + /** + * Parses a blob of proto txt file into javascript object. * * @param input The Blob or file object implementing slice. + * @param repeatedFields Map (Set) of all the repeated fields, since you can't + * tell directly from the pbtxt if a field is repeated or not. * @returns The parsed object. */ - function parsePbtxtFile(input) { - var output = { node: [] }; + function parsePbtxtFile(input, repeatedFields) { + var output = {}; var stack = []; var path = []; var current = output; @@ -3966,25 +4119,6 @@ var tf; }; } /** - * Since proto-txt doesn't explicitly say whether an attribute is repeated - * (an array) or not, we keep a hard-coded list of attributes that are known - * to be repeated. This list is used in parsing time to convert repeated - * attributes into arrays even when the attribute only shows up once in the - * object. - */ - var ARRAY_ATTRIBUTES = { - "node": true, - "node.input": true, - "node.attr": true, - "node.attr.value.list.type": true, - "node.attr.value.shape.dim": true, - "node.attr.value.tensor.string_val": true, - "node.attr.value.tensor.tensor_shape.dim": true, - "node.attr.value.list.shape": true, - "node.attr.value.list.shape.dim": true, - "node.attr.value.list.s": true - }; - /** * Adds a value, given the attribute name and the host object. If the * attribute already exists, but is not an array, it will convert it to an * array of values. @@ -3999,7 +4133,7 @@ var tf; // We treat "node" specially since it is done so often. var existingValue = obj[name]; if (existingValue == null) { - obj[name] = path.join(".") in ARRAY_ATTRIBUTES ? [value] : value; + obj[name] = path.join(".") in repeatedFields ? [value] : value; } else if (Array.isArray(existingValue)) { existingValue.push(value); @@ -4032,21 +4166,9 @@ var tf; break; } }).then(function () { - return output["node"]; + return output; }); } - parser.parsePbtxtFile = parsePbtxtFile; - /** - * Parses a proto txt file into a javascript object. - * - * @param input The string contents of the proto txt file. - * @return The parsed object. - */ - function parsePbtxt(input) { - var blob = new Blob([input]); - return parsePbtxtFile(blob); - } - parser.parsePbtxt = parsePbtxt; })(parser = graph.parser || (graph.parser = {})); })(graph = tf.graph || (tf.graph = {})); })(tf || (tf = {})); // Close module tf.graph.parser. @@ -4079,6 +4201,8 @@ var tf; */ var HierarchyImpl = (function () { function HierarchyImpl() { + this.hasShapeInfo = false; + this.maxMetaEdgeSize = 1; this.root = graph_1.createMetanode(graph_1.ROOT_NAME, { compound: true }); this.templates = null; this.devices = null; @@ -4415,6 +4539,40 @@ var tf; } hierarchy_1.build = build; ; + function joinAndAggregateStats(h, stats) { + // Get all the possible device names. + var deviceNames = {}; + _.each(h.root.leaves(), function (nodeName) { + var leaf = h.node(nodeName); + if (leaf.device != null) { + deviceNames[leaf.device] = true; + } + }); + h.devices = _.keys(deviceNames); + // Reset stats for each group node. + _.each(h.getNodeMap(), function (node, nodeName) { + if (node.isGroupNode) { + node.stats = new graph_1.NodeStats(0, 0, null); + node.deviceHistogram = {}; + } + }); + // Bubble-up the stats and device distribution from leaves to parents. + _.each(h.root.leaves(), function (nodeName) { + var leaf = h.node(nodeName); + var node = leaf; + while (node.parentNode != null) { + if (leaf.device != null) { + var deviceHistogram = node.parentNode.deviceHistogram; + deviceHistogram[leaf.device] = (deviceHistogram[leaf.device] || 0) + 1; + } + if (leaf.stats != null) { + node.parentNode.stats.combine(leaf.stats); + } + node = node.parentNode; + } + }); + } + hierarchy_1.joinAndAggregateStats = joinAndAggregateStats; /** * Creates the metanodes in the hierarchical graph and assigns parent-child * relationship between them. @@ -4431,9 +4589,6 @@ var tf; parent.depth = Math.max(parent.depth, path.length - i); parent.cardinality += node.cardinality; parent.opHistogram[node.op] = (parent.opHistogram[node.op] || 0) + 1; - if (node.stats) { - parent.stats.combine(node.stats); - } if (node.device != null) { parent.deviceHistogram[node.device] = (parent.deviceHistogram[node.device] || 0) + 1; @@ -4593,9 +4748,6 @@ var tf; } child.parentNode = seriesNode; seriesNames[n] = seriesName; - if (child.stats) { - seriesNode.stats.combine(child.stats); - } // Remove now-grouped node from its original parent's metagraph. metagraph.removeNode(n); }); @@ -4870,10 +5022,20 @@ var tf; function RenderGraphInfo(hierarchy) { this.hierarchy = hierarchy; this.index = {}; + this.computeScales(); + // Maps node name to whether the rendering hierarchy was already + // constructed. + this.hasSubhierarchy = {}; + this.root = new RenderGroupNodeInfo(hierarchy.root); + this.index[hierarchy.root.name] = this.root; + this.buildSubhierarchy(hierarchy.root.name); + this.root.expanded = true; + } + RenderGraphInfo.prototype.computeScales = function () { this.deviceColorMap = d3.scale.ordinal() - .domain(hierarchy.devices) - .range(_.map(d3.range(hierarchy.devices.length), render.MetanodeColors.DEVICE_PALETTE)); - var topLevelGraph = hierarchy.root.metagraph; + .domain(this.hierarchy.devices) + .range(_.map(d3.range(this.hierarchy.devices.length), render.MetanodeColors.DEVICE_PALETTE)); + var topLevelGraph = this.hierarchy.root.metagraph; // Find the maximum and minimum memory usage. var memoryExtent = d3.extent(topLevelGraph.nodes(), function (nodeName, index) { var node = topLevelGraph.node(nodeName); @@ -4896,14 +5058,12 @@ var tf; this.computeTimeScale = d3.scale.linear() .domain(computeTimeExtent) .range(PARAMS.minMaxColors); - // Maps node name to whether the rendering hierarchy was already - // constructed. - this.hasSubhierarchy = {}; - this.root = new RenderGroupNodeInfo(hierarchy.root); - this.index[hierarchy.root.name] = this.root; - this.buildSubhierarchy(hierarchy.root.name); - this.root.expanded = true; - } + this.edgeWidthScale = this.hierarchy.hasShapeInfo ? + graph_1.scene.edge.EDGE_WIDTH_SCALE : + d3.scale.linear() + .domain([1, this.hierarchy.maxMetaEdgeSize]) + .range([graph_1.scene.edge.MIN_EDGE_WIDTH, graph_1.scene.edge.MAX_EDGE_WIDTH]); + }; /** * Get a previously created RenderNodeInfo by its node name. */ @@ -6818,20 +6978,20 @@ var tf; /** Delimiter between dimensions when showing sizes of tensors. */ var TENSOR_SHAPE_DELIM = "×"; /** The minimum stroke width of an edge. */ - var MIN_EDGE_WIDTH = 0.75; + edge.MIN_EDGE_WIDTH = 0.75; /** The maximum stroke width of an edge. */ - var MAX_EDGE_WIDTH = 12; + edge.MAX_EDGE_WIDTH = 12; /** The exponent used in the power scale for edge thickness. */ var EDGE_WIDTH_SCALE_EXPONENT = 0.3; /** The domain (min and max value) for the edge width. */ var DOMAIN_EDGE_WIDTH_SCALE = [1, 5E6]; - var edgeWidthScale = d3.scale.pow() + edge.EDGE_WIDTH_SCALE = d3.scale.pow() .exponent(EDGE_WIDTH_SCALE_EXPONENT) .domain(DOMAIN_EDGE_WIDTH_SCALE) - .range([MIN_EDGE_WIDTH, MAX_EDGE_WIDTH]) + .range([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH]) .clamp(true); var arrowheadMap = d3.scale.quantize() - .domain([MIN_EDGE_WIDTH, MAX_EDGE_WIDTH]) + .domain([edge.MIN_EDGE_WIDTH, edge.MAX_EDGE_WIDTH]) .range(["small", "medium", "large", "xlarge"]); /** Minimum stroke width to put edge labels in the middle of edges */ var CENTER_EDGE_LABEL_MIN_STROKE_WIDTH = 2.5; @@ -7005,7 +7165,7 @@ var tf; // Give the path a unique id, which will be used to link // the textPath (edge label) to this path. var pathId = "path_" + getEdgeKey(d); - var strokeWidth = edgeWidthScale(size); + var strokeWidth = sceneElement.renderHierarchy.edgeWidthScale(size); var path = edgeGroup.append("path") .attr({ "id": pathId, @@ -9614,12 +9774,14 @@ Polymer({ * UI controls. */ _colorByChanged: function() { - // We iterate through each svg node and update its state. - _.each(this._nodeGroupIndex, function(nodeGroup, nodeName) { - this._updateNodeState(nodeName); - }, this); - // Notify also the minimap. - this.minimap.update(); + if (this.renderHierarchy != null) { + // We iterate through each svg node and update its state. + _.each(this._nodeGroupIndex, function(nodeGroup, nodeName) { + this._updateNodeState(nodeName); + }, this); + // Notify also the minimap. + this.minimap.update(); + } }, fit: function() { tf.graph.scene.fit(this.$.svg, this.$.root, this._zoom, function() { @@ -9779,7 +9941,7 @@ paper-button { <div class="container"> <div class="vertical"> <h2>[[title]]</h2> - <tf-graph-scene id="scene" class="auto" render-hierarchy="[[renderHierarchy]]" highlighted-node="[[_getVisible(highlightedNode)]]" selected-node="[[selectedNode]]" color-by="[[colorBy]]" name="[[graphName]]" progress="[[progress]]"></tf-graph-scene> + <tf-graph-scene id="scene" class="auto" render-hierarchy="[[renderHierarchy]]" highlighted-node="[[_getVisible(highlightedNode)]]" selected-node="[[selectedNode]]" color-by="[[colorBy]]" progress="[[progress]]"></tf-graph-scene> </div> </div> </template> @@ -9797,6 +9959,10 @@ Polymer({ observer: '_graphChanged' }, basicGraph: Object, + stats: { + type: Object, + observer: '_statsChanged' + }, hierarchyParams: Object, progress: { type: Object, @@ -9835,6 +10001,14 @@ Polymer({ observers: [ '_buildRenderHierarchy(graphHierarchy)' ], + _statsChanged: function(stats) { + if (stats != null) { + tf.graph.joinStatsInfoWithGraph(this.basicGraph, stats); + tf.graph.hierarchy.joinAndAggregateStats(this.graphHierarchy, stats); + // Recompute the rendering information. + this._buildRenderHierarchy(this.graphHierarchy); + } + }, _buildRenderHierarchy: function(graphHierarchy) { tf.time('new tf.graph.render.Hierarchy', function() { if (graphHierarchy.root.type !== tf.graph.NodeType.META) { @@ -10936,7 +11110,7 @@ paper-progress { </template> <div class$="[[_getContainerClass(progress)]]"> <div id="main"> - <tf-graph id="graph" graph-hierarchy="{{graphHierarchy}}" basic-graph="[[graph]]" hierarchy-params="[[hierarchyParams]]" render-hierarchy="{{_renderHierarchy}}" selected-node="{{_selectedNode}}" highlighted-node="{{_highlightedNode}}" color-by="[[colorBy]]" color-by-params="{{colorByParams}}" graph-name="[[graphName]]" progress="{{progress}}"></tf-graph> + <tf-graph id="graph" graph-hierarchy="{{graphHierarchy}}" basic-graph="[[graph]]" hierarchy-params="[[hierarchyParams]]" render-hierarchy="{{_renderHierarchy}}" stats="[[stats]]" selected-node="{{_selectedNode}}" highlighted-node="{{_highlightedNode}}" color-by="[[colorBy]]" color-by-params="{{colorByParams}}" progress="{{progress}}"></tf-graph> </div> <div id="info"> <tf-graph-info id="graph-info" title="selected" graph-hierarchy="[[graphHierarchy]]" render-hierarchy="[[_renderHierarchy]]" graph="[[graph]]" selected-node="{{_selectedNode}}" selected-node-include="{{_selectedNodeInclude}}" highlighted-node="{{_highlightedNode}}" color-by="[[colorBy]]" color-by-params="[[colorByParams]]"></tf-graph-info> @@ -10953,9 +11127,7 @@ Polymer({ // Public API. graphHierarchy: Object, graph: Object, - graphName: String, - // True if the graph data has also run-time stats. - hasStats: Boolean, + stats: Object, /** * @type {value: number, msg: string} * @@ -11005,6 +11177,7 @@ Polymer({ } }); </script> + <dom-module id="tf-graph-controls" assetpath="../tf-graph/"> <template> <style> @@ -11055,6 +11228,7 @@ table td { } .allcontrols { + width: 188px; padding: 30px; } @@ -11065,6 +11239,7 @@ table td { } paper-radio-button { + display: block; padding: 5px; } svg.icon { @@ -11128,7 +11303,7 @@ svg.icon { } .color-text { - padding: 0 0 0 55px; + padding: 0 0 0 49px; } .button-text { @@ -11161,6 +11336,15 @@ svg.icon { display: flex; clear: both; } + +.allcontrols .control-holder paper-radio-group { + margin-top: 5px; +} + +span.counter { + font-size: 13px; + color: gray; +} </style> <div class="allcontrols"> <div class="control-holder"> @@ -11178,7 +11362,7 @@ svg.icon { </a> </div> <div class="control-holder"> - <div class="title">Run</div> + <div class="title">Run <span class="counter">([[datasets.length]])</span></div> <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown"> <paper-menu id="select" class="dropdown-content" selected="{{selectedDataset}}"> <template is="dom-repeat" items="[[datasets]]"> @@ -11188,6 +11372,17 @@ svg.icon { </paper-dropdown-menu> </div> <div class="control-holder"> + <div class="title">Session runs <span class="counter">([[_numSessionRuns(metadataTags)]])</span></div> + <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="run-dropdown"> + <paper-menu id="select" class="dropdown-content" selected="{{selectedMetadataTag}}"> + <template is="dom-repeat" items="[[metadataTags]]"> + <paper-item>[[item.tag]]</paper-item> + </template> + <paper-item>None</paper-item> + </paper-menu> + </paper-dropdown-menu> + </div> + <div class="control-holder"> <div class="title">Upload</div> <paper-button raised="" class="text-button upload-button" on-click="_getFile">Choose File</paper-button> <div class="hidden-input"> @@ -11196,27 +11391,25 @@ svg.icon { </div> <div class="control-holder"> <div class="title">Color</div> - <paper-dropdown-menu no-label-float="" no-animations="" noink="" class="color-dropdown"> - <paper-menu class="dropdown-content" selected="{{_colorByIndex}}"> - <paper-item>Structure</paper-item> - <paper-item>Device</paper-item> - <template is="dom-if" if="[[hasStats]]"> - <paper-item>Compute time</paper-item> - <paper-item>Memory</paper-item> - </template> - </paper-menu> - </paper-dropdown-menu> + <paper-radio-group selected="{{colorBy}}"> + <paper-radio-button name="structure">Structure</paper-radio-button> + <paper-radio-button name="device">Device</paper-radio-button> + <template is="dom-if" if="[[_statsNotNull(stats)]]"> + <paper-radio-button name="compute_time">Compute time</paper-radio-button> + <paper-radio-button name="memory">Memory</paper-radio-button> + </template> + </paper-radio-group> </div> <div> <template is="dom-if" if="[[_isGradientColoring(colorBy)]]"> - <svg width="160" height="20" style="margin: 0 5px" class="color-text"> + <svg width="140" height="20" style="margin: 0 5px" class="color-text"> <defs> <linearGradient id="linearGradient" x1="0%" y1="0%" x2="100%" y2="0%"> <stop class="start" offset="0%" stop-color$="[[_currentGradientParams.startColor]]"></stop> <stop class="end" offset="100%" stop-color$="[[_currentGradientParams.endColor]]"></stop> </linearGradient> </defs> - <rect x="0" y="0" width="160" height="20" fill="url(#linearGradient)" stroke="black"></rect> + <rect x="0" y="0" width="135" height="20" fill="url(#linearGradient)" stroke="black"></rect> </svg> <div class="domainValues color-text"> <div class="domainStart">[[_currentGradientParams.minValue]]</div> @@ -11343,19 +11536,22 @@ Polymer({ is: 'tf-graph-controls', properties: { // Public API. - hasStats: { - type: Boolean - }, + stats: Object, colorBy: { type: String, + value: 'structure', notify: true, - computed: '_getColorBy(_colorByIndex)' + readonly: true }, colorByParams: Object, datasets: { type: Array, observer: '_datasetsChanged' }, + metadataTags: { + type: Array, + computed: '_getMetadataTags(selectedDataset, datasets)' + }, selectedDataset: { type: Number, notify: true, @@ -11366,18 +11562,21 @@ Polymer({ type: Object, notify: true }, - // Private API. - _colorByIndex: { + selectedMetadataTag: { type: Number, - value: 0 // Defaults to 'structure'. + notify: true, + value: -1 }, _currentGradientParams: { type: Object, computed: '_getCurrentGradientParams(colorByParams, colorBy)' } }, - _getColorBy: function(colorByIndex) { - return ["structure", "device", "compute_time", "memory"][colorByIndex]; + _statsNotNull: function(stats) { + return stats != null; + }, + _numSessionRuns: function(metadataTags) { + return metadataTags != null ? metadataTags.length : 0; }, _getBackgroundColor: function(color) { return 'background-color:' + color; @@ -11430,8 +11629,13 @@ Polymer({ this._setDownloadFilename(this.datasets[this.selectedDataset].path); } }, + _getMetadataTags: function(selectedDataset, datasets) { + return this.datasets[selectedDataset].runMetadata; + }, _selectedDatasetChanged: function(newDataset, oldDataset) { if (this.datasets) { + this.set('selectedMetadataTag', -1); + this.set('colorBy', 'structure'); this._setDownloadFilename(this.datasets[newDataset].path); } }, @@ -11500,11 +11704,11 @@ function convertToHumanReadable(value, units, unitIndex) { <template is="dom-if" if="[[!_datasetsEmpty(_datasets)]]"> <tf-dashboard-layout> <div class="sidebar"> - <tf-graph-controls id="controls" color-by-params="[[_colorByParams]]" has-stats="[[_hasStats]]" color-by="{{_colorBy}}" ,="" datasets="[[_datasets]]" selected-dataset="{{_selectedDataset}}" selected-file="{{_selectedFile}}"></tf-graph-controls> - <tf-graph-loader id="loader" datasets="[[_datasets]]" ,="" selected-dataset="[[_selectedDataset]]" selected-file="[[_selectedFile]]" out-graph-hierarchy="{{_graphHierarchy}}" out-graph="{{_graph}}" out-graph-name="{{_graphName}}" has-stats="{{_hasStats}}" progress="{{_progress}}" out-hierarchy-params="{{_hierarchyParams}}"></tf-graph-loader> + <tf-graph-controls id="controls" color-by-params="[[_colorByParams]]" stats="[[_stats]]" color-by="{{_colorBy}}" ,="" datasets="[[_datasets]]" selected-dataset="{{_selectedDataset}}" selected-file="{{_selectedFile}}" selected-metadata-tag="{{_selectedMetadataTag}}"></tf-graph-controls> + <tf-graph-loader id="loader" datasets="[[_datasets]]" ,="" selected-dataset="[[_selectedDataset]]" selected-metadata-tag="[[_selectedMetadataTag]]" selected-file="[[_selectedFile]]" out-graph-hierarchy="{{_graphHierarchy}}" out-graph="{{_graph}}" out-stats="{{_stats}}" progress="{{_progress}}" out-hierarchy-params="{{_hierarchyParams}}"></tf-graph-loader> </div> <div class="center"> - <tf-graph-board id="graphboard" graph-hierarchy="[[_graphHierarchy]]" graph="[[_graph]]" has-stats="[[_hasStats]]" graph-name="[[_graphName]]" progress="[[_progress]]" color-by="[[_colorBy]]" color-by-params="{{_colorByParams}}" hierarchy-params="[[_hierarchyParams]]"> + <tf-graph-board id="graphboard" graph-hierarchy="[[_graphHierarchy]]" graph="[[_graph]]" stats="[[_stats]]" progress="[[_progress]]" color-by="[[_colorBy]]" color-by-params="{{_colorByParams}}" hierarchy-params="[[_hierarchyParams]]"> </tf-graph-board> </div> </tf-dashboard-layout> @@ -11529,28 +11733,31 @@ function convertToHumanReadable(value, units, unitIndex) { Polymer({ is: 'tf-graph-dashboard', properties: { - _datasets: { - type: Object, - computed: '_getDatasets(runs.*, router)' - }, + _datasets: Object, backend: {type: Object, observer: 'reload'}, router: {type: Object}, runs: Array, }, reload: function() { - var _this = this; - this.backend.graphRuns().then(function(x) { - _this.runs = x; - }); - }, - _getDatasets: function(runs, router) { - return _.map(this.runs, function(runName) { - return { - name: runName, - path: router.graph(runName, tf.graph.LIMIT_ATTR_SIZE, - tf.graph.LARGE_ATTRS_KEY) - }; - }); + Promise.all([this.backend.graphRuns(), this.backend.runMetadataRuns()]) + .then(function(result) { + var runsWithGraph = result[0]; + var runToMetadata = result[1]; + var datasets = _.map(runsWithGraph, function(runName) { + return { + name: runName, + path: this.router.graph(runName, tf.graph.LIMIT_ATTR_SIZE, + tf.graph.LARGE_ATTRS_KEY), + runMetadata: _.map(runToMetadata[runName], function(tag) { + return { + tag: tag, + path: this.router.runMetadata(tag, runName) + }; + }, this) + }; + }, this); + this.set('_datasets', datasets); + }.bind(this)); }, _datasetsEmpty: function(datasets) { return !datasets || !datasets.length; @@ -11570,6 +11777,11 @@ Polymer({ <paper-tab data-mode="graphs">Graph</paper-tab> <paper-tab data-mode="histograms">Histograms</paper-tab> </paper-tabs> + <div class="global-actions"> + <a href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/tensorboard/README.md" tabindex="-1"> + <paper-icon-button icon="help-outline"></paper-icon-button> + </a> + </div> </div> </paper-toolbar> <div id="content" class="fit"> @@ -11602,25 +11814,13 @@ Polymer({ -webkit-font-smoothing: antialiased; } - #toolbar-content { - width: 100%; - height: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - } - .toolbar-title { font-size: 20px; margin-left: 10px; text-rendering: optimizeLegibility; letter-spacing: -0.025em; font-weight: 500; - } - - #content { - height: 100%; + width: 340px; } .tabs { @@ -11633,6 +11833,29 @@ Polymer({ --paper-tabs-selection-bar-color: white; } + .global-actions { + flex-grow: 2; + text-align: right; + color: white; + } + + .global-actions a { + color: white; + } + + #toolbar-content { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + #content { + height: 100%; + } + </style> </template> <script> |