Update released TensorBoard.
Update bower dependencies.
Also force urls to lowercase.
Change: 119584968
diff --git a/WORKSPACE b/WORKSPACE
index d3e01b7..4ec77c7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -46,7 +46,7 @@
 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(
   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(
   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(
   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(
   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 @@
   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(
   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(
   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(
   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 @@
   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(
   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(
   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(
   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 60d3b2f..b6a7d89 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 39b50cc..503fd25 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 15ede2b..a9cc4e4 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 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 @@
                     _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 @@
                     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 @@
                             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 @@
             }
             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 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 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 @@
             };
         }
         ;
+        /**
+         * 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 @@
   </style>
   <template>
     <template is="dom-if" if="[[imageUrl]]">
-      <img src="[[imageUrl]]">
+      <img src="[[imageUrl]]" on-error="retry">
     </template>
   </template>
   <script>
@@ -2569,7 +2686,11 @@
       },
       ready: function() {
         this.reload();
-      }
+      },
+      retry: function() {
+        this.imageUrl = ""; // force reload
+        this.reload();
+      },
     });
   </script>
 </dom-module>
@@ -2747,11 +2868,6 @@
       notify: true,
     },
     datasets: Array,
-    hasStats: {
-      type: Boolean,
-      readOnly: true, // This property produces data.
-      notify: true
-    },
     selectedDataset: Number,
     selectedFile: {
       type: Object,
@@ -2767,21 +2883,41 @@
       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 @@
       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 @@
         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 @@
     .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 @@
     });
   },
   _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 @@
          * 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 @@
                 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 @@
                 // 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 @@
                     // 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 @@
                 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 @@
             }
             // 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 @@
             /**
              * 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 @@
                     });
                 });
             }
-            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);
-                    });
-                });
-            }
-            parser.readJson = readJson;
-            /**
-             * Reads the graph and stats file (if available), parses them and returns a
-             * promise of the result.
-             */
-            function readAndParseData(dataset, pbTxtFile, tracker) {
-                var graphPbTxt;
-                var statsJson;
-                return tf.runTask("Reading graph.pbtxt", 20, function () {
-                    return pbTxtFile ?
-                        Promise.resolve(pbTxtFile) :
-                        readPbTxt(dataset.path).then(function (text) { return new Blob([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) {
-                    graphPbTxt = blob;
-                    return tf.runTask("Reading stats.pbtxt", 20, function () {
-                        return (dataset != null && dataset.statsPath != null) ?
-                            readJson(dataset.statsPath) : null;
+                    return tf.runTask("Parsing metadata.pbtxt", 60, function () {
+                        return blob != null ? parseStatsPbTxt(blob) : null;
                     }, tracker);
-                })
-                    .then(function (json) {
-                    statsJson = json;
-                    return tf.runTask("Parsing graph.pbtxt", 60, function () {
-                        return parsePbtxtFile(graphPbTxt);
-                    }, tracker);
-                })
-                    .then(function (nodes) {
-                    return {
-                        nodes: nodes,
-                        statsJson: statsJson
-                    };
                 });
             }
-            parser.readAndParseData = readAndParseData;
+            parser.fetchAndParseMetadata = fetchAndParseMetadata;
+            /**
+             * Fetches the graph file, parses it and returns a promise of the result.
+             */
+            function fetchAndParseGraphData(path, pbTxtFile, tracker) {
+                return tf.runTask("Reading graph pbtxt", 40, function () {
+                    return pbTxtFile ?
+                        Promise.resolve(pbTxtFile) :
+                        fetchPbTxt(path).then(function (text) { return new Blob([text]); });
+                }, tracker)
+                    .then(function (blob) {
+                    return tf.runTask("Parsing graph.pbtxt", 60, function () {
+                        return parseGraphPbTxt(blob);
+                    }, tracker);
+                });
+            }
+            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 @@
             }
             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 @@
                     };
                 }
                 /**
-                 * 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 @@
                     // 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 @@
                             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 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 @@
             }
             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 @@
                         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 @@
                         }
                         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 @@
                 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 @@
                     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 @@
                 /** 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 @@
                     // 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 @@
     * 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 @@
 <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 @@
       observer: '_graphChanged'
     },
     basicGraph: Object,
+    stats: {
+      type: Object,
+      observer: '_statsChanged'
+    },
     hierarchyParams: Object,
     progress: {
       type: Object,
@@ -9835,6 +10001,14 @@
   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 @@
 </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 @@
     // 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 @@
   }
 });
 </script>
+
 <dom-module id="tf-graph-controls" assetpath="../tf-graph/">
 <template>
 <style>
@@ -11055,6 +11228,7 @@
 }
 
 .allcontrols {
+  width: 188px;
   padding: 30px;
 }
 
@@ -11065,6 +11239,7 @@
 }
 
 paper-radio-button {
+  display: block;
   padding: 5px;
 }
 svg.icon {
@@ -11128,7 +11303,7 @@
 }
 
 .color-text {
-  padding: 0 0 0 55px;
+  padding: 0 0 0 49px;
 }
 
 .button-text {
@@ -11161,6 +11336,15 @@
   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 @@
     </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 @@
     </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 @@
   </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 @@
   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 @@
       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 @@
       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 @@
 <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 @@
 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 @@
             <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 @@
         -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 @@
         --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>