diff options
author | viona@google.com <viona@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-09 01:10:57 +0000 |
---|---|---|
committer | viona@google.com <viona@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-09 01:10:57 +0000 |
commit | 13b456a31ff39f27b1fae6b803203ce8facc9810 (patch) | |
tree | dcb3087c7e3695d0ad949ba2b0998e1c05fc2996 | |
parent | 1611aa5c6ce06e4cfb0b738c5e6e8a036936e1b4 (diff) | |
download | chromium_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
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, |