summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorviona@google.com <viona@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-09 01:10:57 +0000
committerviona@google.com <viona@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-09 01:10:57 +0000
commit13b456a31ff39f27b1fae6b803203ce8facc9810 (patch)
treedcb3087c7e3695d0ad949ba2b0998e1c05fc2996
parent1611aa5c6ce06e4cfb0b738c5e6e8a036936e1b4 (diff)
downloadchromium_src-13b456a31ff39f27b1fae6b803203ce8facc9810.zip
chromium_src-13b456a31ff39f27b1fae6b803203ce8facc9810.tar.gz
chromium_src-13b456a31ff39f27b1fae6b803203ce8facc9810.tar.bz2
Implementation for Events Waterfall View
BUG=234355 Review URL: https://chromiumcodereview.appspot.com/17285007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@210473 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/resources/net_internals/index.html2
-rw-r--r--chrome/browser/resources/net_internals/index.js2
-rw-r--r--chrome/browser/resources/net_internals/main.js1
-rw-r--r--chrome/browser/resources/net_internals/source_entry.js10
-rw-r--r--chrome/browser/resources/net_internals/waterfall_row.js163
-rw-r--r--chrome/browser/resources/net_internals/waterfall_view.css60
-rw-r--r--chrome/browser/resources/net_internals/waterfall_view.html20
-rw-r--r--chrome/browser/resources/net_internals/waterfall_view.js143
-rw-r--r--chrome/test/data/webui/net_internals/log_util.js2
-rw-r--r--chrome/test/data/webui/net_internals/main.js1
-rw-r--r--chrome/test/data/webui/net_internals/net_internals_test.js1
11 files changed, 402 insertions, 3 deletions
diff --git a/chrome/browser/resources/net_internals/index.html b/chrome/browser/resources/net_internals/index.html
index 6771cb4..a62ff75 100644
--- a/chrome/browser/resources/net_internals/index.html
+++ b/chrome/browser/resources/net_internals/index.html
@@ -12,6 +12,7 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="status_view.css">
<link rel="stylesheet" href="events_view.css">
+ <link rel="stylesheet" href="waterfall_view.css">
<link rel="stylesheet" href="timeline_view.css">
<link rel="stylesheet" href="logs_view.css">
<link rel="stylesheet" href="chromeos_view.css">
@@ -41,6 +42,7 @@ found in the LICENSE file.
<include src="test_view.html"/>
<include src="hsts_view.html"/>
<include src="events_view.html"/>
+ <include src="waterfall_view.html"/>
<include src="timeline_view.html"/>
<include src="logs_view.html"/>
<include src="chromeos_view.html"/>
diff --git a/chrome/browser/resources/net_internals/index.js b/chrome/browser/resources/net_internals/index.js
index 44465fd..49b1c8a 100644
--- a/chrome/browser/resources/net_internals/index.js
+++ b/chrome/browser/resources/net_internals/index.js
@@ -28,6 +28,8 @@
<include src="source_filter_parser.js"/>
<include src="source_row.js"/>
<include src="events_view.js"/>
+<include src="waterfall_view.js"/>
+<include src="waterfall_row.js"/>
<include src="details_view.js"/>
<include src="source_entry.js"/>
<include src="horizontal_scrollbar_view.js"/>
diff --git a/chrome/browser/resources/net_internals/main.js b/chrome/browser/resources/net_internals/main.js
index 8cbd37c..50927d1 100644
--- a/chrome/browser/resources/net_internals/main.js
+++ b/chrome/browser/resources/net_internals/main.js
@@ -185,6 +185,7 @@ var MainView = (function() {
addTab(ImportView);
addTab(ProxyView);
addTab(EventsView);
+ addTab(WaterfallView);
addTab(TimelineView);
addTab(DnsView);
addTab(SocketsView);
diff --git a/chrome/browser/resources/net_internals/source_entry.js b/chrome/browser/resources/net_internals/source_entry.js
index 99338ba..38c5f89 100644
--- a/chrome/browser/resources/net_internals/source_entry.js
+++ b/chrome/browser/resources/net_internals/source_entry.js
@@ -37,7 +37,7 @@ var SourceEntry = (function() {
this.isInactive_ = true;
}
- // If we have a net error code, update |this.isError_| if apporpriate.
+ // If we have a net error code, update |this.isError_| if appropriate.
if (logEntry.params) {
var netErrorCode = logEntry.params.net_error;
// Skip both cases where netErrorCode is undefined, and cases where it
@@ -291,6 +291,11 @@ var SourceEntry = (function() {
return this.isError_;
},
+ getStartTime: function() {
+ var startTicks = this.entries_[0].time;
+ return timeutil.convertTimeTicksToTime(startTicks);
+ },
+
/**
* Returns time of last event if inactive. Returns current time otherwise.
*/
@@ -309,8 +314,7 @@ var SourceEntry = (function() {
* last event.
*/
getDuration: function() {
- var startTicks = this.entries_[0].time;
- var startTime = timeutil.convertTimeTicksToTime(startTicks);
+ var startTime = this.getStartTime();
var endTime = this.getEndTime();
return endTime - startTime;
},
diff --git a/chrome/browser/resources/net_internals/waterfall_row.js b/chrome/browser/resources/net_internals/waterfall_row.js
new file mode 100644
index 0000000..d24595a
--- /dev/null
+++ b/chrome/browser/resources/net_internals/waterfall_row.js
@@ -0,0 +1,163 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var WaterfallRow = (function() {
+ 'use strict';
+
+ /**
+ * A WaterfallRow represents the row corresponding to a single SourceEntry
+ * displayed by the EventsWaterfallView.
+ *
+ * @constructor
+ */
+
+ // TODO(viona):
+ // -Support nested events.
+ // -Handle updating length when an event is stalled.
+ function WaterfallRow(parentView, sourceEntry, eventList) {
+ this.parentView_ = parentView;
+ this.sourceEntry_ = sourceEntry;
+
+ this.eventTypes_ = eventList;
+ this.description_ = sourceEntry.getDescription();
+
+ this.createRow_();
+ }
+
+ WaterfallRow.prototype = {
+ onSourceUpdated: function() {
+ this.updateRow();
+ },
+
+ updateRow: function() {
+ var scale = this.parentView_.getScaleFactor();
+ // In some cases, the REQUEST_ALIVE event has been received, while the
+ // URL Request to start the job has not been received. In that case, the
+ // description obtained is incorrect. The following fixes that.
+ if (this.description_ == '') {
+ this.urlCell_.innerHTML = '';
+ this.description_ = this.sourceEntry_.getDescription();
+ addTextNode(this.urlCell_, this.description_);
+ }
+
+ this.barCell_.innerHTML = '';
+ var matchingEventPairs = this.findLogEntryPairs_(this.eventTypes_);
+
+ // Creates the spacing in the beginning to show start time.
+ var startTime = this.parentView_.getStartTime();
+ var sourceEntryStartTime = this.sourceEntry_.getStartTime();
+ var delay = sourceEntryStartTime - startTime;
+ var scaledMinTime = delay * scale;
+ this.createNode_(this.barCell_, scaledMinTime, 'padding');
+
+ var currentEnd = sourceEntryStartTime;
+ for (var i = 0; i < matchingEventPairs.length; ++i) {
+ var event = matchingEventPairs[i];
+ var startTicks = event.startEntry.time;
+ var endTicks = event.endEntry.time;
+ event.eventType = event.startEntry.type;
+ event.startTime = timeutil.convertTimeTicksToTime(startTicks);
+ event.endTime = timeutil.convertTimeTicksToTime(endTicks);
+ event.eventDuration = event.endTime - event.startTime;
+
+ // Handles the spaces between events.
+ if (currentEnd < event.startTime) {
+ var eventDuration = event.startTime - currentEnd;
+ var eventWidth = eventDuration * scale;
+ this.createNode_(this.barCell_, eventWidth, this.type_);
+ }
+
+ // Creates event bars.
+ var eventType = eventTypeToCssClass_(EventTypeNames[event.eventType]);
+ var eventWidth = event.eventDuration * scale;
+ this.createNode_(this.barCell_, eventWidth, eventType);
+ currentEnd = event.startTime + event.eventDuration;
+ }
+ // Creates a bar for the part after the last event.
+ if (this.getEndTime() > currentEnd) {
+ var endWidth = (this.getEndTime() - currentEnd) * scale;
+ this.createNode_(this.barCell_, endWidth, this.type_);
+ }
+ },
+
+ getStartTime: function() {
+ return this.sourceEntry_.getStartTime();
+ },
+
+ getEndTime: function() {
+ return this.sourceEntry_.getEndTime();
+ },
+
+ createRow_: function() {
+ // Create a row.
+ var tr = addNode($(WaterfallView.TBODY_ID), 'tr');
+ tr._id = this.sourceEntry_.getSourceId();
+ this.row_ = tr;
+
+ var idCell = addNode(tr, 'td');
+ addTextNode(idCell, this.sourceEntry_.getSourceId());
+
+ var urlCell = addNode(tr, 'td');
+ urlCell.classList.add('waterfall-view-url-cell');
+ addTextNode(urlCell, this.description_);
+ this.urlCell_ = urlCell;
+
+ // Creates the offset for where the color bar appears.
+ var barCell = addNode(tr, 'td');
+ barCell.classList.add('waterfall-view-row');
+ this.barCell_ = barCell;
+
+ this.updateRow();
+
+ var sourceTypeString = this.sourceEntry_.getSourceTypeString();
+ this.type_ = eventTypeToCssClass_(sourceTypeString);
+ },
+
+ // Generates nodes.
+ createNode_: function(parentNode, durationScaled, eventType) {
+ var newNode = addNode(parentNode, 'div');
+ setNodeWidth(newNode, durationScaled);
+ newNode.classList.add('waterfall-view-row-' + eventType);
+ return newNode;
+ },
+
+ /**
+ * Finds pairs of starting and ending events of all types that are in
+ * typeList. Currently does not handle nested events. Can consider adding
+ * starting events to a stack and popping off as their close events are
+ * found. Returns an array of objects containing start/end entry pairs,
+ * in the format {startEntry, endEntry}.
+ */
+ findLogEntryPairs_: function() {
+ var typeList = this.eventTypes_;
+ var matchingEventPairs = [];
+ var startEntries = {};
+ var entries = this.sourceEntry_.getLogEntries();
+ for (var i = 0; i < entries.length; ++i) {
+ var type = entries[i].type;
+ if (typeList.indexOf(type) < 0) {
+ continue;
+ }
+ if (entries[i].phase == EventPhase.PHASE_BEGIN) {
+ startEntries[type] = entries[i];
+ }
+ if (startEntries[type] && entries[i].phase == EventPhase.PHASE_END) {
+ var event = {
+ startEntry: startEntries[type],
+ endEntry: entries[i],
+ };
+ matchingEventPairs.push(event);
+ }
+ }
+ return matchingEventPairs;
+ },
+
+ };
+
+ function eventTypeToCssClass_(rawEventType) {
+ return rawEventType.toLowerCase().replace(/_/g, '-');
+ }
+
+ return WaterfallRow;
+})();
diff --git a/chrome/browser/resources/net_internals/waterfall_view.css b/chrome/browser/resources/net_internals/waterfall_view.css
new file mode 100644
index 0000000..2d1b636
--- /dev/null
+++ b/chrome/browser/resources/net_internals/waterfall_view.css
@@ -0,0 +1,60 @@
+/* Copyright 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.waterfall-view-url-cell {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 250px;
+}
+
+.waterfall-view-row {
+ white-space: nowrap;
+}
+
+.waterfall-view-row * {
+ display: inline-block;
+ height: 15px;
+ opacity: 0.5;
+ padding-top: 0;
+}
+
+.waterfall-view-row [class*='http-stream-request'] {
+ background-color: #000;
+}
+
+.waterfall-view-row [class*='http-transaction-read-headers'] {
+ background-color: rgb(255, 0, 0);
+}
+
+.waterfall-view-row [class*='url-request'] {
+ background-color: rgb(0, 0, 255);
+}
+
+.waterfall-view-row [class*='http-stream-job'] {
+ background-color: rgb(0, 255, 0);
+}
+
+.waterfall-view-row [class*='proxy-service'] {
+ background-color: rgb(122, 122, 0);
+}
+
+.waterfall-view-row [class*='socket-pool-connect-job'] {
+ background-color: rgb(0, 122, 122);
+}
+
+.waterfall-view-row [class*='host-resolver-impl'] {
+ background-color: rgb(122, 0, 122);
+}
+
+.waterfall-view-row [class*='socket'] {
+ background-color: rgb(122, 178, 0);
+}
+
+.waterfall-popup {
+ display: block;
+ font-size: 15px;
+}
diff --git a/chrome/browser/resources/net_internals/waterfall_view.html b/chrome/browser/resources/net_internals/waterfall_view.html
new file mode 100644
index 0000000..d605122
--- /dev/null
+++ b/chrome/browser/resources/net_internals/waterfall_view.html
@@ -0,0 +1,20 @@
+<!-- =============== Events Waterfall View ================= -->
+<div id=waterfall-view-tab-content class=content-box>
+ <div>
+ Information:
+ <p>Currently only captures URL Request events. Blue represents URL
+ Requests. Red represents Header Reads. Grey represents Stream Requests.
+ <input id=waterfall-view-time-scale type="button"
+ value="Scale To Fit Window">
+ <table id=waterfall-view-source-list-table>
+ <thead>
+ <tr>
+ <td id=waterfall-view-source-id>ID</td>
+ <td id=waterfall-view-source-header>Source</td>
+ </tr>
+ </thead>
+ <!-- Events Waterfall table body: This is where request rows go into -->
+ <tbody id=waterfall-view-source-list-tbody></tbody>
+ </table>
+ </div>
+</div>
diff --git a/chrome/browser/resources/net_internals/waterfall_view.js b/chrome/browser/resources/net_internals/waterfall_view.js
new file mode 100644
index 0000000..2f1b0ed
--- /dev/null
+++ b/chrome/browser/resources/net_internals/waterfall_view.js
@@ -0,0 +1,143 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** This view displays the event waterfall. */
+var WaterfallView = (function() {
+ 'use strict';
+
+ // We inherit from DivView.
+ var superClass = DivView;
+
+ /**
+ * @constructor
+ */
+ function WaterfallView() {
+ assertFirstConstructorCall(WaterfallView);
+
+ // Call superclass's constructor.
+ superClass.call(this, WaterfallView.MAIN_BOX_ID);
+
+ SourceTracker.getInstance().addSourceEntryObserver(this);
+
+ // For adjusting the range of view.
+ $(WaterfallView.SCALE_ID).addEventListener(
+ 'click', this.adjustToWindow_.bind(this), true);
+
+ this.initializeSourceList_();
+ }
+
+ WaterfallView.TAB_ID = 'tab-handle-waterfall';
+ WaterfallView.TAB_NAME = 'Waterfall';
+ WaterfallView.TAB_HASH = '#waterfall';
+
+ // IDs for special HTML elements in events_waterfall_view.html.
+ WaterfallView.MAIN_BOX_ID = 'waterfall-view-tab-content';
+ WaterfallView.TBODY_ID = 'waterfall-view-source-list-tbody';
+ WaterfallView.SCALE_ID = 'waterfall-view-time-scale';
+ WaterfallView.ID_HEADER_ID = 'waterfall-view-source-id';
+ WaterfallView.SOURCE_HEADER_ID = 'waterfall-view-source-header';
+
+ cr.addSingletonGetter(WaterfallView);
+
+ WaterfallView.prototype = {
+ // Inherit the superclass's methods.
+ __proto__: superClass.prototype,
+
+ /**
+ * Creates new WaterfallRows for URL Requests when the sourceEntries are
+ * updated if they do not already exist.
+ * Updates pre-existing WaterfallRows that correspond to updated sources.
+ */
+ onSourceEntriesUpdated: function(sourceEntries) {
+ if (this.startTime_ == null && sourceEntries.length > 0) {
+ var logEntries = sourceEntries[0].getLogEntries();
+ this.startTime_ = timeutil.convertTimeTicksToTime(logEntries[0].time);
+ // Initial scale factor.
+ this.scaleFactor_ = 0.1;
+ }
+ for (var i = 0; i < sourceEntries.length; ++i) {
+ var sourceEntry = sourceEntries[i];
+ var id = sourceEntry.getSourceId();
+ if (sourceEntry.getSourceType() == EventSourceType.URL_REQUEST) {
+ var row = this.sourceIdToRowMap_[id];
+ if (!row) {
+ var importantEventTypes = [
+ EventType.HTTP_STREAM_REQUEST,
+ EventType.HTTP_TRANSACTION_READ_HEADERS
+ ];
+ this.sourceIdToRowMap_[id] =
+ new WaterfallRow(this, sourceEntry, importantEventTypes);
+ } else {
+ row.onSourceUpdated();
+ }
+ }
+ }
+ },
+
+ onAllSourceEntriesDeleted: function() {
+ this.initializeSourceList_();
+ },
+
+ onLoadLogFinish: function(data) {
+ return true;
+ },
+
+ getScaleFactor: function() {
+ return this.scaleFactor_;
+ },
+
+ getStartTime: function() {
+ return this.startTime_;
+ },
+
+ /**
+ * Initializes the list of source entries. If source entries are already
+ * being displayed, removes them all in the process.
+ */
+ initializeSourceList_: function() {
+ this.sourceIdToRowMap_ = {};
+ $(WaterfallView.TBODY_ID).innerHTML = '';
+ this.startTime_ = null;
+ this.scaleFactor_ = null;
+ },
+
+ /**
+ * Changes width of the bars such that horizontally, everything fits into
+ * the user's current window size.
+ * TODO(viona): Deal with the magic number.
+ */
+ adjustToWindow_: function() {
+ var usedWidth = $(WaterfallView.SOURCE_HEADER_ID).offsetWidth +
+ $(WaterfallView.ID_HEADER_ID).offsetWidth;
+ var availableWidth = ($(WaterfallView.MAIN_BOX_ID).offsetWidth -
+ usedWidth - 50);
+ if (availableWidth <= 0) {
+ availableWidth = 1;
+ }
+ var totalDuration = 0;
+ for (var id in this.sourceIdToRowMap_) {
+ var row = this.sourceIdToRowMap_[id];
+ var rowDuration = row.getEndTime() - this.startTime_;
+ if (totalDuration < rowDuration) {
+ totalDuration = rowDuration;
+ }
+ }
+ var scaleFactor = availableWidth / totalDuration;
+ this.scaleAll_(scaleFactor);
+ },
+
+ /**
+ * Scales all existing rows by scaleFactor.
+ */
+ scaleAll_: function(scaleFactor) {
+ this.scaleFactor_ = scaleFactor;
+ for (var id in this.sourceIdToRowMap_) {
+ var row = this.sourceIdToRowMap_[id];
+ row.updateRow();
+ }
+ },
+ };
+
+ return WaterfallView;
+})();
diff --git a/chrome/test/data/webui/net_internals/log_util.js b/chrome/test/data/webui/net_internals/log_util.js
index 2b30ce0..b499b77 100644
--- a/chrome/test/data/webui/net_internals/log_util.js
+++ b/chrome/test/data/webui/net_internals/log_util.js
@@ -161,6 +161,7 @@ function checkViewsAfterLogLoaded() {
import: true,
proxy: true,
events: true,
+ waterfall: true,
timeline: true,
dns: true,
sockets: true,
@@ -192,6 +193,7 @@ function checkViewsAfterNetLogLoggerLogLoaded() {
import: true,
proxy: false,
events: true,
+ waterfall: true,
timeline: true,
dns: false,
sockets: false,
diff --git a/chrome/test/data/webui/net_internals/main.js b/chrome/test/data/webui/net_internals/main.js
index 92a2323a..c45340f 100644
--- a/chrome/test/data/webui/net_internals/main.js
+++ b/chrome/test/data/webui/net_internals/main.js
@@ -26,6 +26,7 @@ TEST_F('NetInternalsTest', 'netInternalsTourTabs', function() {
import: true,
proxy: true,
events: true,
+ waterfall: true,
timeline: true,
dns: true,
sockets: true,
diff --git a/chrome/test/data/webui/net_internals/net_internals_test.js b/chrome/test/data/webui/net_internals/net_internals_test.js
index 739a581..ff5421b 100644
--- a/chrome/test/data/webui/net_internals/net_internals_test.js
+++ b/chrome/test/data/webui/net_internals/net_internals_test.js
@@ -265,6 +265,7 @@ var NetInternalsTest = (function() {
import: ImportView.TAB_ID,
proxy: ProxyView.TAB_ID,
events: EventsView.TAB_ID,
+ waterfall: WaterfallView.TAB_ID,
timeline: TimelineView.TAB_ID,
dns: DnsView.TAB_ID,
sockets: SocketsView.TAB_ID,