1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 var WaterfallRow
= (function() {
9 * A WaterfallRow represents the row corresponding to a single SourceEntry
10 * displayed by the EventsWaterfallView. All times are relative to the
11 * "base time" (Time of first event from any source).
17 // -Support nested events.
18 // -Handle updating length when an event is stalled.
19 function WaterfallRow(parentView
, sourceEntry
) {
20 this.parentView_
= parentView
;
21 this.sourceEntry_
= sourceEntry
;
23 this.description_
= sourceEntry
.getDescription();
28 // Offset of popup from mouse location.
29 var POPUP_OFFSET_FROM_CURSOR
= 25;
31 WaterfallRow
.prototype = {
32 onSourceUpdated: function() {
36 updateRow: function() {
37 var scale
= this.parentView_
.getScaleFactor();
38 // In some cases, the REQUEST_ALIVE event has been received, while the
39 // URL Request to start the job has not been received. In that case, the
40 // description obtained is incorrect. The following fixes that.
41 if (this.description_
== '') {
42 this.sourceCell_
.innerHTML
= '';
43 this.description_
= this.sourceEntry_
.getDescription();
44 addTextNode(this.sourceCell_
, this.description_
);
47 this.rowCell_
.innerHTML
= '';
49 var matchingEventPairs
=
50 WaterfallRow
.findUrlRequestEvents(this.sourceEntry_
);
52 // Creates the spacing in the beginning to show start time.
53 var sourceStartTicks
= this.getStartTicks();
54 var frontNode
= addNode(this.rowCell_
, 'div');
55 frontNode
.classList
.add('waterfall-view-padding');
56 setNodeWidth(frontNode
, sourceStartTicks
* scale
);
58 var barCell
= addNode(this.rowCell_
, 'div');
59 barCell
.classList
.add('waterfall-view-bar');
61 if (this.sourceEntry_
.isError()) {
62 barCell
.classList
.add('error');
65 var currentEnd
= sourceStartTicks
;
67 for (var i
= 0; i
< matchingEventPairs
.length
; ++i
) {
68 var event
= matchingEventPairs
[i
];
70 timeutil
.convertTimeTicksToRelativeTime(event
.startEntry
.time
);
72 timeutil
.convertTimeTicksToRelativeTime(event
.endEntry
.time
);
73 event
.eventType
= event
.startEntry
.type
;
74 event
.startTime
= startTicks
;
75 event
.endTime
= endTicks
;
76 event
.eventDuration
= event
.endTime
- event
.startTime
;
78 // Handles the spaces between events.
79 if (currentEnd
< event
.startTime
) {
80 var eventDuration
= event
.startTime
- currentEnd
;
81 var padNode
= this.createNode_(
82 barCell
, eventDuration
, this.sourceTypeString_
, 'source');
85 // Creates event bars.
86 var eventNode
= this.createNode_(
87 barCell
, event
.eventDuration
, EventTypeNames
[event
.eventType
],
89 currentEnd
= event
.startTime
+ event
.eventDuration
;
92 // Creates a bar for the part after the last event.
93 var endTime
= this.getEndTicks();
94 if (endTime
> currentEnd
) {
95 var endDuration
= endTime
- currentEnd
;
96 var endNode
= this.createNode_(
97 barCell
, endDuration
, this.sourceTypeString_
, 'source');
101 getStartTicks: function() {
102 return timeutil
.convertTimeTicksToRelativeTime(
103 this.sourceEntry_
.getStartTicks());
106 getEndTicks: function() {
107 return timeutil
.convertTimeTicksToRelativeTime(
108 this.sourceEntry_
.getEndTicks());
111 clearPopup_: function(parentNode
) {
112 parentNode
.innerHTML
= '';
115 createPopup_: function(parentNode
, event
, eventType
, duration
, mouse
) {
116 var newPopup
= addNode(parentNode
, 'div');
117 newPopup
.classList
.add('waterfall-view-popup');
119 var popupList
= addNode(newPopup
, 'ul');
120 popupList
.classList
.add('waterfall-view-popup-list');
122 popupList
.style
.maxWidth
=
123 $(WaterfallView
.MAIN_BOX_ID
).offsetWidth
* 0.5 + 'px';
125 this.createPopupItem_(
126 popupList
, 'Has Error', this.sourceEntry_
.isError());
128 this.createPopupItem_(popupList
, 'Event Type', eventType
);
130 if (event
!= 'source') {
131 this.createPopupItem_(
132 popupList
, 'Event Duration', duration
.toFixed(0) + 'ms');
133 this.createPopupItem_(
134 popupList
, 'Event Start Time', event
.startTime
+ 'ms');
135 this.createPopupItem_(
136 popupList
, 'Event End Time', event
.endTime
+ 'ms');
138 this.createPopupItem_(popupList
, 'Source Duration',
139 this.getEndTicks() - this.getStartTicks() + 'ms');
140 this.createPopupItem_(popupList
, 'Source Start Time',
141 this.getStartTicks() + 'ms');
142 this.createPopupItem_(popupList
, 'Source End Time',
143 this.getEndTicks() + 'ms');
144 this.createPopupItem_(
145 popupList
, 'Source ID', this.sourceEntry_
.getSourceId());
146 var urlListItem
= this.createPopupItem_(
147 popupList
, 'Source Description', this.description_
);
149 urlListItem
.classList
.add('waterfall-view-popup-list-url-item');
151 // Fixes cases where the popup appears 'off-screen'.
152 var popupLeft
= mouse
.pageX
- newPopup
.offsetWidth
;
154 popupLeft
= mouse
.pageX
;
156 newPopup
.style
.left
= popupLeft
+
157 $(WaterfallView
.MAIN_BOX_ID
).scrollLeft
-
158 $(WaterfallView
.BAR_TABLE_ID
).offsetLeft
+ 'px';
160 var popupTop
= mouse
.pageY
- newPopup
.offsetHeight
-
161 POPUP_OFFSET_FROM_CURSOR
;
163 popupTop
= mouse
.pageY
;
165 newPopup
.style
.top
= popupTop
+
166 $(WaterfallView
.MAIN_BOX_ID
).scrollTop
-
167 $(WaterfallView
.BAR_TABLE_ID
).offsetTop
+ 'px';
170 createPopupItem_: function(parentPopup
, key
, popupInformation
) {
171 var popupItem
= addNode(parentPopup
, 'li');
172 addTextNode(popupItem
, key
+ ': ' + popupInformation
);
176 createRow_: function() {
178 var tr
= addNode($(WaterfallView
.BAR_TBODY_ID
), 'tr');
179 tr
.classList
.add('waterfall-view-table-row');
181 // Creates the color bar.
183 var rowCell
= addNode(tr
, 'td');
184 rowCell
.classList
.add('waterfall-view-row');
185 this.rowCell_
= rowCell
;
187 this.sourceTypeString_
= this.sourceEntry_
.getSourceTypeString();
189 var infoTr
= addNode($(WaterfallView
.INFO_TBODY_ID
), 'tr');
190 infoTr
.classList
.add('waterfall-view-information-row');
192 var idCell
= addNode(infoTr
, 'td');
193 idCell
.classList
.add('waterfall-view-id-cell');
194 var idValue
= this.sourceEntry_
.getSourceId();
195 var idLink
= addNodeWithText(idCell
, 'a', idValue
);
196 idLink
.href
= '#events&s=' + idValue
;
198 var sourceCell
= addNode(infoTr
, 'td');
199 sourceCell
.classList
.add('waterfall-view-url-cell');
200 addTextNode(sourceCell
, this.description_
);
201 this.sourceCell_
= sourceCell
;
207 createNode_: function(parentNode
, duration
, eventTypeString
, event
) {
208 var linkNode
= addNode(parentNode
, 'a');
209 linkNode
.href
= '#events&s=' + this.sourceEntry_
.getSourceId();
211 var scale
= this.parentView_
.getScaleFactor();
212 var newNode
= addNode(linkNode
, 'div');
213 setNodeWidth(newNode
, duration
* scale
);
214 newNode
.classList
.add(eventTypeToCssClass_(eventTypeString
));
215 newNode
.classList
.add('waterfall-view-bar-component');
216 newNode
.addEventListener(
218 this.createPopup_
.bind(this, newNode
, event
, eventTypeString
,
221 newNode
.addEventListener(
222 'mouseout', this.clearPopup_
.bind(this, newNode
), true);
228 * Identifies source dependencies and extracts events of interest for use in
229 * inlining in URL Request bars.
230 * Created as static function for testing purposes.
232 WaterfallRow
.findUrlRequestEvents = function(sourceEntry
) {
238 // One level down from URL Requests.
240 var httpStreamJobSources
= findDependenciesOfType_(
241 sourceEntry
, EventType
.HTTP_STREAM_REQUEST_BOUND_TO_JOB
);
243 var httpTransactionReadHeadersPairs
= findEntryPairsFromSourceEntries_(
244 [sourceEntry
], EventType
.HTTP_TRANSACTION_READ_HEADERS
);
245 eventPairs
= eventPairs
.concat(httpTransactionReadHeadersPairs
);
247 var proxyServicePairs
= findEntryPairsFromSourceEntries_(
248 httpStreamJobSources
, EventType
.PROXY_SERVICE
);
249 eventPairs
= eventPairs
.concat(proxyServicePairs
);
251 if (httpStreamJobSources
.length
> 0) {
252 for (var i
= 0; i
< httpStreamJobSources
.length
; ++i
) {
253 // Two levels down from URL Requests.
255 var socketSources
= findDependenciesOfType_(
256 httpStreamJobSources
[i
], EventType
.SOCKET_POOL_BOUND_TO_SOCKET
);
258 // TODO(mmenke): Some of these may be nested in the PROXY_SERVICE
259 // event, resulting in incorrect display, since nested
260 // events aren't handled.
261 var hostResolverImplRequestPairs
= findEntryPairsByType_(
262 httpStreamJobSources
[i
], EventType
.HOST_RESOLVER_IMPL_REQUEST
);
263 eventPairs
= eventPairs
.concat(hostResolverImplRequestPairs
);
265 // Truncate times of connection events such that they don't occur before
266 // the HTTP_STREAM_JOB event or the PROXY_SERVICE event.
267 // TODO(mmenke): The last HOST_RESOLVER_IMPL_REQUEST may still be a
269 var minTime
= httpStreamJobSources
[i
].getLogEntries()[0].time
;
270 if (proxyServicePairs
.length
> 0)
271 minTime
= proxyServicePairs
[0].endEntry
.time
;
272 // Convert to number so comparisons will be numeric, not string,
274 minTime
= Number(minTime
);
276 var tcpConnectPairs
= findEntryPairsFromSourceEntries_(
277 socketSources
, EventType
.TCP_CONNECT
);
279 var sslConnectPairs
= findEntryPairsFromSourceEntries_(
280 socketSources
, EventType
.SSL_CONNECT
);
282 var connectionPairs
= tcpConnectPairs
.concat(sslConnectPairs
);
284 // Truncates times of connection events such that they are shown after a
285 // proxy service event.
286 for (var k
= 0; k
< connectionPairs
.length
; ++k
) {
287 var eventPair
= connectionPairs
[k
];
288 var eventInRange
= false;
289 if (eventPair
.startEntry
.time
>= minTime
) {
291 } else if (eventPair
.endEntry
.time
> minTime
) {
293 // Should not modify original object.
294 eventPair
.startEntry
= shallowCloneObject(eventPair
.startEntry
);
295 // Need to have a string, for consistency.
296 eventPair
.startEntry
.time
= minTime
+ '';
299 eventPairs
.push(eventPair
);
304 eventPairs
.sort(function(a
, b
) {
305 return a
.startEntry
.time
- b
.startEntry
.time
;
310 function eventTypeToCssClass_(eventType
) {
311 return eventType
.toLowerCase().replace(/_
/g
, '-');
315 * Finds all events of input type from the input list of Source Entries.
316 * Returns an ordered list of start and end log entries.
318 function findEntryPairsFromSourceEntries_(sourceEntryList
, eventType
) {
320 for (var i
= 0; i
< sourceEntryList
.length
; ++i
) {
321 var sourceEntry
= sourceEntryList
[i
];
323 var entries
= sourceEntry
.getLogEntries();
324 var matchingEventPairs
= findEntryPairsByType_(entries
, eventType
);
325 eventPairs
= eventPairs
.concat(matchingEventPairs
);
332 * Finds all events of input type from the input list of log entries.
333 * Returns an ordered list of start and end log entries.
335 function findEntryPairsByType_(entries
, eventType
) {
336 var matchingEventPairs
= [];
337 var startEntry
= null;
338 for (var i
= 0; i
< entries
.length
; ++i
) {
339 var currentEntry
= entries
[i
];
340 if (eventType
!= currentEntry
.type
) {
343 if (currentEntry
.phase
== EventPhase
.PHASE_BEGIN
) {
344 startEntry
= currentEntry
;
346 if (startEntry
&& currentEntry
.phase
== EventPhase
.PHASE_END
) {
348 startEntry
: startEntry
,
349 endEntry
: currentEntry
,
351 matchingEventPairs
.push(event
);
355 return matchingEventPairs
;
359 * Returns an ordered list of SourceEntries that are dependencies for
360 * events of the given type.
362 function findDependenciesOfType_(sourceEntry
, eventType
) {
363 var sourceEntryList
= [];
365 var eventList
= findEventsInSourceEntry_(sourceEntry
, eventType
);
366 for (var i
= 0; i
< eventList
.length
; ++i
) {
367 var foundSourceEntry
= findSourceEntryFromEvent_(eventList
[i
]);
368 if (foundSourceEntry
) {
369 sourceEntryList
.push(foundSourceEntry
);
373 return sourceEntryList
;
377 * Returns an ordered list of events from the given sourceEntry with the
380 function findEventsInSourceEntry_(sourceEntry
, eventType
) {
381 var entries
= sourceEntry
.getLogEntries();
383 for (var i
= 0; i
< entries
.length
; ++i
) {
384 var currentEntry
= entries
[i
];
385 if (currentEntry
.type
== eventType
) {
386 events
.push(currentEntry
);
393 * Follows the event to obtain the sourceEntry that is the source
396 function findSourceEntryFromEvent_(event
) {
397 if (!('params' in event
) || !('source_dependency' in event
.params
)) {
400 var id
= event
.params
.source_dependency
.id
;
401 return SourceTracker
.getInstance().getSourceEntry(id
);