Commit c635a663 authored by nduca@chromium.org's avatar nduca@chromium.org

Add TRACE_COUNTER support to about:tracing with dramatically improved test coverage.

Review URL: http://codereview.chromium.org/8513009

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110671 0039d316-1c4b-4281-b951-d872f2087c98
parent 01172831
...@@ -46,10 +46,8 @@ cr.define('tracing', function() { ...@@ -46,10 +46,8 @@ cr.define('tracing', function() {
this.timelineView_ = new TimelineView(); this.timelineView_ = new TimelineView();
this.controlDiv_.appendChild(this.recordBn_); this.controlDiv_.appendChild(this.recordBn_);
if (!browserBridge.debugMode) {
this.controlDiv_.appendChild(this.loadBn_); this.controlDiv_.appendChild(this.loadBn_);
this.controlDiv_.appendChild(this.saveBn_); this.controlDiv_.appendChild(this.saveBn_);
}
this.container_.appendChild(this.timelineView_); this.container_.appendChild(this.timelineView_);
this.appendChild(this.container_); this.appendChild(this.container_);
......
// Copyright (c) 2011 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.
/**
* @fileoverview Helper functions for use in tracing tests.
*/
cr.define('test_utils', function() {
function getJSON(url, cb) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
window.setTimeout(function() {
if (req.status == 200) {
var resp = JSON.parse(req.responseText);
if (resp.traceEvents)
cb(resp.traceEvents);
else
cb(resp);
} else {
console.log('Failed to load ' + url);
}
}, 0);
}
};
req.send(null);
}
return {
getJSON: getJSON
};
});
\ No newline at end of file
[
{"name": "a", "args": {},"pid": 52, "ts": 9524, "cat": "foo", "tid": 53,
"ph": "B"},
{"name": "a", "args": {},"pid": 52, "ts": 9560, "cat": "foo", "tid": 53,
"ph": "E"},
{"name": "b", "args": {},"pid": 52, "ts": 9629, "cat": "foo", "tid": 53,
"ph": "B"},
{"name": "b", "args": {},"pid": 52, "ts": 9631, "cat": "foo", "tid": 53,
"ph": "E"},
{"name": "counter", "args": {"value": 2}, "pid": 52, "ts": 9524,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 10}, "pid": 52, "ts": 9626,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 5}, "pid": 52, "ts": 9627,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 8}, "pid": 52, "ts": 9628,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 4}, "pid": 52, "ts": 9629,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 10}, "pid": 52, "ts": 9631,
"cat": "foo", "tid": 53, "ph": "C", "id": "123"},
{"name": "counter", "args": {"value": 10}, "pid": 52, "ts": 9524,
"cat": "foo", "tid": 53, "ph": "C", "id": "b"},
{"name": "counter", "args": {"value": 0}, "pid": 52, "ts": 9631,
"cat": "foo", "tid": 53, "ph": "C", "id": "b"}
]
\ No newline at end of file
[
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 826, "ph": "B",
"name": "A", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 827, "ph": "B",
"name": "Asub", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 829, "ph": "B",
"name": "NonNest", "args": {"id": "1", "ui-nest": "0"}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 830, "ph": "B",
"name": "NonNest", "args": {"id": "2", "ui-nest": "0"}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 831, "ph": "E",
"name": "Asub", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 832, "ph": "E",
"name": "NonNest", "args": {"id": "1", "ui-nest": "0"}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 833, "ph": "E",
"name": "NonNest", "args": {"id": "2", "ui-nest": "0"}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 834, "ph": "E",
"name": "A", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 827, "ph": "B",
"name": "A", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 854, "ph": "E",
"name": "A", "args": {}}
]
\ No newline at end of file
[
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 826, "ph": "C",
"name": "counter", "args": {"value": 10}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 826, "ph": "B",
"name": "A long name that doesnt fit but is exceedingly informative",
"args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 827, "ph": "B",
"name": "Asub with a name that wont fit", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 828, "ph": "E",
"name": "Asub", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 829, "ph": "B",
"name": "Asub", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 832, "ph": "E",
"name": "Asub", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 832, "ph": "C",
"name": "counter", "args": {"value": 1}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 833, "ph": "E",
"name": "", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 835, "ph": "I",
"name": "I1", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 837, "ph": "I",
"name": "I2", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 839, "ph": "C",
"name": "counter", "args": {"value": 5}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 840, "ph": "B",
"name": "A not as long a name", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 848, "ph": "E",
"name": "A not as long a name", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 848, "ph": "C",
"name": "counter", "args": {"value": 1}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 854, "ph": "C",
"name": "counter", "args": {"value": 10}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 850, "ph": "B",
"name": "B", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22630, "ts": 854, "ph": "E",
"name": "B", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 827, "ph": "B",
"name": "A", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 835, "ph": "I",
"name": "Immediate Three", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 845, "ph": "I",
"name": "I4", "args": {}},
{"cat": "PERF", "pid": 22630, "tid": 22631, "ts": 854, "ph": "E",
"name": "A", "args": {}},
{"cat": "__metadata", "pid": 22630, "tid": 22630, "ts": 0, "ph": "M",
"name": "thread_name", "args": {"name": "threadA"}},
{"cat": "__metadata", "pid": 22630, "tid": 22631, "ts": 0, "ph": "M",
"name": "thread_name", "args": {"name": "threadB"}},
{"cat": "__metadata", "pid": 22630, "tid": 22632, "ts": 0, "ph": "M",
"name": "thread_name", "args": {"name": "threadC"}}
]
\ No newline at end of file
[
{"cat": "X", "pid": 30, "tid": 30, "ts": 826, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 30, "ts": 827, "ph": "B", "name": "Asub",
"args": {}},
{"cat": "X", "pid": 30, "tid": 30, "ts": 828, "ph": "E", "name": "Asub",
"args": {}},
{"cat": "X", "pid": 30, "tid": 30, "ts": 829, "ph": "B", "name": "Asub",
"args": {}},
{"cat": "X", "pid": 30, "tid": 30, "ts": 832, "ph": "E", "name": "Asub",
"args": {}},
{"cat": "X", "pid": 30, "tid": 30, "ts": 833, "ph": "E", "name": "",
"args": {}},
{"cat": "X", "pid": 30, "tid": 31, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 31, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 32, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 32, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 33, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 33, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 34, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 34, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 35, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 35, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 36, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 36, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 37, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 37, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 38, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 38, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 39, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 39, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 10, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 10, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 31, "tid": 11, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 31, "tid": 11, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 12, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 12, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 13, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 13, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 14, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 14, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 15, "ts": 840, "ph": "B", "name": "A",
"args": {}},
{"cat": "X", "pid": 30, "tid": 15, "ts": 848, "ph": "E", "name": "A",
"args": {}},
{"cat": "__metadata", "pid": 30, "tid": 14, "ts": 0, "ph": "M",
"name": "thread_name", "args": {"name": "thread12345678B"}},
{"cat": "__metadata", "pid": 30, "tid": 15, "ts": 0, "ph": "M",
"name": "thread_name", "args": {"name": "threadA"}}
]
\ No newline at end of file
[
{"name": "a", "args": {},"pid": 52, "ts": 9524, "cat": "foo", "tid": 53,
"ph": "B"},
{"name": "a", "args": {},"pid": 52, "ts": 9560, "cat": "foo", "tid": 53,
"ph": "E"},
{"name": "b", "args": {},"pid": 52, "ts": 9629, "cat": "foo", "tid": 53,
"ph": "B"},
{"name": "b", "args": {},"pid": 52, "ts": 9631, "cat": "foo", "tid": 53,
"ph": "E"}
]
\ No newline at end of file
...@@ -21,18 +21,17 @@ found in the LICENSE file. ...@@ -21,18 +21,17 @@ found in the LICENSE file.
border-top: 1px solid #D0D0D0; border-top: 1px solid #D0D0D0;
} }
.timeline-slice-track { .timeline-canvas-based-track {
-webkit-box-orient: horizontal; -webkit-box-orient: horizontal;
-webkit-box-align: stretch; -webkit-box-align: stretch;
background-color: white; background-color: white;
display: -webkit-box; display: -webkit-box;
height: 18px;
margin: 0; margin: 0;
padding: 0; padding: 0;
padding-right: 5px; padding-right: 5px;
} }
.timeline-slice-track-title { .timeline-canvas-based-track-title {
overflow: hidden; overflow: hidden;
padding-right: 5px; padding-right: 5px;
text-align: right; text-align: right;
...@@ -40,13 +39,21 @@ found in the LICENSE file. ...@@ -40,13 +39,21 @@ found in the LICENSE file.
white-space: nowrap; white-space: nowrap;
} }
.timeline-slice-track-canvas-container { .timeline-canvas-based-track-canvas-container {
-webkit-box-flex: 1; -webkit-box-flex: 1;
width: 100%; width: 100%;
} }
.timeline-slice-track-canvas { .timeline-canvas-based-track-canvas {
-webkit-box-flex: 1; -webkit-box-flex: 1;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.timeline-slice-track {
height: 18px;
}
.timeline-counter-track {
height: 30px;
}
...@@ -30,17 +30,99 @@ cr.define('tracing', function() { ...@@ -30,17 +30,99 @@ cr.define('tracing', function() {
* @constructor * @constructor
* @extends {cr.EventTarget} * @extends {cr.EventTarget}
*/ */
function TimelineViewport() { function TimelineViewport(parentEl) {
this.parentEl_ = parentEl;
this.scaleX_ = 1; this.scaleX_ = 1;
this.panX_ = 0; this.panX_ = 0;
this.gridTimebase_ = 0; this.gridTimebase_ = 0;
this.gridStep_ = 1000 / 60; this.gridStep_ = 1000 / 60;
this.gridEnabled_ = false; this.gridEnabled_ = false;
this.hasCalledSetupFunction_ = false;
this.onResizeBoundToThis_ = this.onResize_.bind(this);
// The following code uses an interval to detect when the parent element
// is attached to the document. That is a trigger to run the setup function
// and install a resize listener.
this.checkForAttachInterval_ = setInterval(
this.checkForAttach_.bind(this), 250);
} }
TimelineViewport.prototype = { TimelineViewport.prototype = {
__proto__: cr.EventTarget.prototype, __proto__: cr.EventTarget.prototype,
/**
* Allows initialization of the viewport when the viewport's parent element
* has been attached to the document and given a size.
* @param {Function} fn Function to call when the viewport can be safely
* initialized.
*/
setWhenPossible: function(fn) {
this.pendingSetFunction_ = fn;
},
/**
* @return {boolean} Whether the current timeline is attached to the
* document.
*/
get isAttachedToDocument_() {
var cur = this.parentEl_;
while (cur.parentNode)
cur = cur.parentNode;
return cur == this.parentEl_.ownerDocument;
},
onResize_: function() {
this.dispatchChangeEvent();
},
/**
* Checks whether the parentNode is attached to the document.
* When it is, it installs the iframe-based resize detection hook
* and then runs the pendingSetFunction_, if present.
*/
checkForAttach_: function() {
if (!this.isAttachedToDocument_ || this.clientWidth == 0)
return;
if (!this.iframe_) {
this.iframe_ = document.createElement('iframe');
this.iframe_.style.cssText =
'position:absolute;width:100%;height:0;border:0;visibility:hidden;';
this.parentEl_.appendChild(this.iframe_);
this.iframe_.contentWindow.addEventListener('resize',
this.onResizeBoundToThis_);
}
var curSize = this.clientWidth + 'x' + this.clientHeight;
if (this.pendingSetFunction_) {
this.lastSize_ = curSize;
this.pendingSetFunction_();
this.pendingSetFunction_ = undefined;
}
window.clearInterval(this.checkForAttachInterval_);
this.checkForAttachInterval_ = undefined;
},
/**
* Fires the change event on this viewport. Used to notify listeners
* to redraw when the underlying model has been mutated.
*/
dispatchChangeEvent: function() {
cr.dispatchSimpleEvent(this, 'change');
},
detach: function() {
if (this.checkForAttachInterval_) {
window.clearInterval(this.checkForAttachInterval_);
this.checkForAttachInterval_ = undefined;
}
this.iframe_.removeListener('resize', this.onResizeBoundToThis_);
this.parentEl_.removeChild(this.iframe_);
},
get scaleX() { get scaleX() {
return this.scaleX_; return this.scaleX_;
}, },
...@@ -48,7 +130,7 @@ cr.define('tracing', function() { ...@@ -48,7 +130,7 @@ cr.define('tracing', function() {
var changed = this.scaleX_ != s; var changed = this.scaleX_ != s;
if (changed) { if (changed) {
this.scaleX_ = s; this.scaleX_ = s;
cr.dispatchSimpleEvent(this, 'change'); this.dispatchChangeEvent();
} }
}, },
...@@ -59,7 +141,7 @@ cr.define('tracing', function() { ...@@ -59,7 +141,7 @@ cr.define('tracing', function() {
var changed = this.panX_ != p; var changed = this.panX_ != p;
if (changed) { if (changed) {
this.panX_ = p; this.panX_ = p;
cr.dispatchSimpleEvent(this, 'change'); this.dispatchChangeEvent();
} }
}, },
...@@ -68,7 +150,7 @@ cr.define('tracing', function() { ...@@ -68,7 +150,7 @@ cr.define('tracing', function() {
if (changed) { if (changed) {
this.scaleX_ = s; this.scaleX_ = s;
this.panX_ = p; this.panX_ = p;
cr.dispatchSimpleEvent(this, 'change'); this.dispatchChangeEvent();
} }
}, },
...@@ -111,7 +193,7 @@ cr.define('tracing', function() { ...@@ -111,7 +193,7 @@ cr.define('tracing', function() {
if (this.gridEnabled_ == enabled) if (this.gridEnabled_ == enabled)
return; return;
this.gridEnabled_ = enabled && true; this.gridEnabled_ = enabled && true;
cr.dispatchSimpleEvent(this, 'change'); this.dispatchChangeEvent();
}, },
get gridTimebase() { get gridTimebase() {
...@@ -152,16 +234,10 @@ cr.define('tracing', function() { ...@@ -152,16 +234,10 @@ cr.define('tracing', function() {
decorate: function() { decorate: function() {
this.classList.add('timeline'); this.classList.add('timeline');
this.needsViewportReset_ = false;
this.viewport_ = new TimelineViewport(); this.viewport_ = new TimelineViewport(this);
this.viewport_.addEventListener('change',
this.viewportChange_.bind(this));
this.invalidatePending_ = false;
this.tracks_ = this.ownerDocument.createElement('div'); this.tracks_ = this.ownerDocument.createElement('div');
this.tracks_.invalidate = this.invalidate.bind(this);
this.appendChild(this.tracks_); this.appendChild(this.tracks_);
this.dragBox_ = this.ownerDocument.createElement('div'); this.dragBox_ = this.ownerDocument.createElement('div');
...@@ -169,22 +245,6 @@ cr.define('tracing', function() { ...@@ -169,22 +245,6 @@ cr.define('tracing', function() {
this.appendChild(this.dragBox_); this.appendChild(this.dragBox_);
this.hideDragBox_(); this.hideDragBox_();
// The following code uses a setInterval to monitor the timeline control
// for size changes. This is so that we can keep the canvas' bitmap size
// correctly synchronized with its presentation size.
// TODO(nduca): detect this in a more efficient way, e.g. iframe hack.
this.lastSize_ = this.clientWidth + 'x' + this.clientHeight;
this.checkForResizeInterval_ =
this.ownerDocument.defaultView.setInterval(function() {
if (!this.isAttachedToDocument_)
return;
var curSize = this.clientWidth + 'x' + this.clientHeight;
if (this.clientWidth && curSize != this.lastSize_) {
this.lastSize_ = curSize;
this.onResize();
}
}.bind(this), 250);
this.bindEventListener_(document, 'keypress', this.onKeypress_, this); this.bindEventListener_(document, 'keypress', this.onKeypress_, this);
this.bindEventListener_(document, 'keydown', this.onKeydown_, this); this.bindEventListener_(document, 'keydown', this.onKeydown_, this);
this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this); this.bindEventListener_(document, 'mousedown', this.onMouseDown_, this);
...@@ -213,13 +273,19 @@ cr.define('tracing', function() { ...@@ -213,13 +273,19 @@ cr.define('tracing', function() {
}, },
detach: function() { detach: function() {
for (var i = 0; i < this.tracks_.children.length; i++)
this.tracks_.children[i].detach();
for (var i = 0; i < this.boundListeners_.length; i++) { for (var i = 0; i < this.boundListeners_.length; i++) {
var binding = this.boundListeners_[i]; var binding = this.boundListeners_[i];
binding.object.removeEventListener(binding.event, binding.boundFunc); binding.object.removeEventListener(binding.event, binding.boundFunc);
} }
this.boundListeners_ = undefined; this.boundListeners_ = undefined;
window.clearInterval(this.checkForResizeInterval_); this.viewport_.detach();
this.checkForResizeInterval_ = undefined; },
get viewport() {
return this.viewport_;
}, },
get model() { get model() {
...@@ -234,113 +300,114 @@ cr.define('tracing', function() { ...@@ -234,113 +300,114 @@ cr.define('tracing', function() {
} }
this.model_ = model; this.model_ = model;
// Create tracks and measure their heading size. // Figure out all the headings.
var threads = model.getAllThreads(); var allHeadings = [];
model.getAllThreads().forEach(function(t) {
allHeadings.push(t.userFriendlyName);
});
model.getAllCounters().forEach(function(c) {
allHeadings.push(c.name);
});
// Figure out the maximum heading size.
var maxHeadingWidth = 0; var maxHeadingWidth = 0;
var tracks = [];
var measuringStick = new tracing.MeasuringStick(); var measuringStick = new tracing.MeasuringStick();
var headingEl = document.createElement('div'); var headingEl = document.createElement('div');
headingEl.style.position = 'fixed'; headingEl.style.position = 'fixed';
headingEl.className = 'timeline-slice-track-title'; headingEl.className = 'timeline-canvas-based-track-title';
for (var tI = 0; tI < threads.length; tI++) { allHeadings.forEach(function(text) {
var thread = threads[tI]; headingEl.textContent = text + ':__';
var track = new TimelineThreadTrack();
track.thread = thread;
track.viewport = this.viewport_;
tracks.push(track);
headingEl.textContent = track.heading;
var w = measuringStick.measure(headingEl).width; var w = measuringStick.measure(headingEl).width;
// Limit heading width to 300px. // Limit heading width to 300px.
if (w > 300) if (w > 300)
w = 300; w = 300;
if (w > maxHeadingWidth) if (w > maxHeadingWidth)
maxHeadingWidth = w; maxHeadingWidth = w;
} });
var extraHeadingPadding = 4; maxHeadingWidth = maxHeadingWidth + 'px';
maxHeadingWidth += maxHeadingWidth + extraHeadingPadding;
// Attach tracks and set width. // Reset old tracks.
for (var i = 0; i < this.tracks_.children.length; i++)
this.tracks_.children[i].detach();
this.tracks_.textContent = ''; this.tracks_.textContent = '';
threads.sort(tracing.TimelineThread.compare);
for (var tI = 0; tI < tracks.length; tI++) {
var track = tracks[tI];
track.headingWidth = maxHeadingWidth + 'px';
this.tracks_.appendChild(track);
}
if (this.isAttachedToDocument_) // Get a sorted list of processes.
this.onResize(); var processes = [];
else for (var pid in model.processes)
this.needsViewportReset_ = true; processes.push(model.processes[pid]);
}, processes.sort(tracing.TimelineProcess.compare);
viewportChange_: function() { // Create tracks for each process.
this.invalidate(); processes.forEach(function(process) {
}, // Add counter tracks for this process.
var counters = [];
invalidate: function() { for (var tid in process.counters)
if (this.invalidatePending_) counters.push(process.counters[tid]);
return; counters.sort(tracing.TimelineCounter.compare);
this.invalidatePending_ = true;
if (this.isAttachedToDocument_) // Create the counters for this process.
window.setTimeout(function() { counters.forEach(function(counter) {
this.invalidatePending_ = false; var track = new tracing.TimelineCounterTrack();
this.redrawAllTracks_(); track.heading = counter.name + ':';
}.bind(this), 0); track.headingWidth = maxHeadingWidth;
}, track.viewport = this.viewport_;
track.counter = counter;
this.tracks_.appendChild(track);
}.bind(this));
/** // Get a sorted list of threads.
* @return {boolean} Whether the current timeline is attached to the var threads = [];
* document. for (var tid in process.threads)
*/ threads.push(process.threads[tid]);
get isAttachedToDocument_() { threads.sort(tracing.TimelineThread.compare);
var cur = this;
while (cur.parentNode)
cur = cur.parentNode;
return cur == this.ownerDocument;
},
onResize: function() { // Create the threads.
if (!this.isAttachedToDocument_) threads.forEach(function(thread) {
throw 'Not attached to document!'; var track = new tracing.TimelineThreadTrack();
for (var i = 0; i < this.tracks_.children.length; i++) { track.heading = thread.userFriendlyName + ':';
var track = this.tracks_.children[i]; track.tooltip = thread.userFriendlyDetials;
track.onResize(); track.headingWidth = maxHeadingWidth;
} track.viewport = this.viewport_;
if (this.invalidatePending_) { track.thread = thread;
this.invalidatePending_ = false; this.tracks_.appendChild(track);
this.redrawAllTracks_(); }.bind(this));
} }.bind(this));
},
redrawAllTracks_: function() { // Set up a reasonable viewport.
if (this.needsViewportReset_ && this.clientWidth != 0) { this.viewport_.setWhenPossible(function() {
if (!this.isAttachedToDocument_)
throw 'Not attached to document!';
this.needsViewportReset_ = false;
/* update viewport */
var rangeTimestamp = this.model_.maxTimestamp - var rangeTimestamp = this.model_.maxTimestamp -
this.model_.minTimestamp; this.model_.minTimestamp;
var w = this.firstCanvas.width; var w = this.firstCanvas.width;
var scaleX = w / rangeTimestamp; var scaleX = w / rangeTimestamp;
var panX = -this.model_.minTimestamp; var panX = -this.model_.minTimestamp;
this.viewport_.setPanAndScale(panX, scaleX); this.viewport_.setPanAndScale(panX, scaleX);
} }.bind(this));
for (var i = 0; i < this.tracks_.children.length; i++) {
this.tracks_.children[i].redraw();
}
}, },
updateChildViewports_: function() { /**
for (var cI = 0; cI < this.tracks_.children.length; cI++) { * @return {Element} The element whose focused state determines
var child = this.tracks_.children[cI]; * whether to respond to keyboard inputs.
child.setViewport(this.panX, this.scaleX); * Defaults to the parent element.
} */
get focusElement() {
if (this.focusElement_)
return this.focusElement_;
return this.parentElement;
},
/**
* Sets the element whose focus state will determine whether
* to respond to keybaord input.
*/
set focusElement(value) {
this.focusElement_ = value;
}, },
get listenToKeys_() { get listenToKeys_() {
if (this.parentElement.parentElement.tabIndex >= 0) if (!this.focusElement_)
return document.activeElement == this.parentElement.parentElement; return true;
if (this.focusElement.tabIndex >= 0)
return document.activeElement == this.focusElement;
return true; return true;
}, },
...@@ -408,7 +475,7 @@ cr.define('tracing', function() { ...@@ -408,7 +475,7 @@ cr.define('tracing', function() {
e.preventDefault(); e.preventDefault();
break; break;
case 9: // TAB case 9: // TAB
if (this.parentElement.parentElement.tabIndex == -1) { if (this.focusElement.tabIndex == -1) {
if (e.shiftKey) if (e.shiftKey)
this.selectPrevious_(e); this.selectPrevious_(e);
else else
...@@ -468,12 +535,7 @@ cr.define('tracing', function() { ...@@ -468,12 +535,7 @@ cr.define('tracing', function() {
if (adjoining != undefined) if (adjoining != undefined)
selection.push({track: track, slice: adjoining}); selection.push({track: track, slice: adjoining});
} }
// Activate the new selection. this.selection = selection;
this.selection_ = selection;
for (i = 0; i < this.selection_.length; i++)
this.selection_[i].slice.selected = true;
cr.dispatchSimpleEvent(this, 'selectionChange');
this.invalidate(); // Cause tracks to redraw.
e.preventDefault(); e.preventDefault();
}, },
...@@ -484,14 +546,13 @@ cr.define('tracing', function() { ...@@ -484,14 +546,13 @@ cr.define('tracing', function() {
' e : Center on mouse\n' + ' e : Center on mouse\n' +
' g/G : Shows grid at the start/end of the selected task\n'; ' g/G : Shows grid at the start/end of the selected task\n';
if (this.parentElement.parentElement.tabIndex) { if (this.focusElement.tabIndex) {
help += ' <- : Select previous event on current timeline\n' + help += ' <- : Select previous event on current timeline\n' +
' -> : Select next event on current timeline\n'; ' -> : Select next event on current timeline\n';
} else { } else {
help += ' <-,^TAB : Select previous event on current timeline\n' + help += ' <-,^TAB : Select previous event on current timeline\n' +
' ->, TAB : Select next event on current timeline\n'; ' ->, TAB : Select next event on current timeline\n';
} }
help += help +=
'\n' + '\n' +
'Dbl-click to zoom in; Shift dbl-click to zoom out\n'; 'Dbl-click to zoom in; Shift dbl-click to zoom out\n';
...@@ -512,7 +573,7 @@ cr.define('tracing', function() { ...@@ -512,7 +573,7 @@ cr.define('tracing', function() {
cr.dispatchSimpleEvent(this, 'selectionChange'); cr.dispatchSimpleEvent(this, 'selectionChange');
for (i = 0; i < this.selection_.length; i++) for (i = 0; i < this.selection_.length; i++)
this.selection_[i].slice.selected = true; this.selection_[i].slice.selected = true;
this.invalidate(); // Cause tracks to redraw. this.viewport_.dispatchChangeEvent(); // Triggers a redraw.
}, },
get firstCanvas() { get firstCanvas() {
...@@ -569,10 +630,12 @@ cr.define('tracing', function() { ...@@ -569,10 +630,12 @@ cr.define('tracing', function() {
}, },
onMouseDown_: function(e) { onMouseDown_: function(e) {
if (e.clientX < this.offsetLeft || rect = this.getClientRects()[0];
e.clientX >= this.offsetLeft + this.offsetWidth || if (!rect ||
e.clientY < this.offsetTop || e.clientX < rect.left ||
e.clientY >= this.offsetTop + this.offsetHeight) e.clientX >= rect.right ||
e.clientY < rect.top ||
e.clientY >= rect.bottom)
return; return;
var canv = this.firstCanvas; var canv = this.firstCanvas;
...@@ -585,8 +648,8 @@ cr.define('tracing', function() { ...@@ -585,8 +648,8 @@ cr.define('tracing', function() {
this.dragBeginEvent_ = e; this.dragBeginEvent_ = e;
e.preventDefault(); e.preventDefault();
if (this.parentElement.parentElement.tabIndex) if (this.focusElement.tabIndex >= 0)
this.parentElement.parentElement.focus(); this.focusElement.focus();
}, },
onMouseMove_: function(e) { onMouseMove_: function(e) {
...@@ -653,7 +716,7 @@ cr.define('tracing', function() { ...@@ -653,7 +716,7 @@ cr.define('tracing', function() {
scale = 1 / scale; scale = 1 / scale;
this.zoomBy_(scale); this.zoomBy_(scale);
e.preventDefault(); e.preventDefault();
}, }
}; };
/** /**
...@@ -663,6 +726,7 @@ cr.define('tracing', function() { ...@@ -663,6 +726,7 @@ cr.define('tracing', function() {
cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS); cr.defineProperty(Timeline, 'model', cr.PropertyKind.JS);
return { return {
Timeline: Timeline Timeline: Timeline,
TimelineViewport: TimelineViewport
}; };
}); });
...@@ -30,13 +30,15 @@ cr.define('tracing', function() { ...@@ -30,13 +30,15 @@ cr.define('tracing', function() {
* All time units are stored in milliseconds. * All time units are stored in milliseconds.
* @constructor * @constructor
*/ */
function TimelineSlice(title, colorId, start, args) { function TimelineSlice(title, colorId, start, args, opt_duration) {
this.title = title; this.title = title;
this.start = start; this.start = start;
this.colorId = colorId; this.colorId = colorId;
this.args = args; this.args = args;
this.didNotFinish = false; this.didNotFinish = false;
this.subSlices = []; this.subSlices = [];
if (opt_duration !== undefined)
this.duration = opt_duration;
} }
TimelineSlice.prototype = { TimelineSlice.prototype = {
...@@ -112,6 +114,23 @@ cr.define('tracing', function() { ...@@ -112,6 +114,23 @@ cr.define('tracing', function() {
this.minTimestamp = undefined; this.minTimestamp = undefined;
this.maxTimestamp = undefined; this.maxTimestamp = undefined;
} }
},
/**
* @return {String} A user-friendly name for this thread.
*/
get userFriendlyName() {
var tname = this.name || this.tid;
return this.parent.pid + ': ' + tname;
},
/**
* @return {String} User friendly details about this thread.
*/
get userFriendlyDetials() {
return 'pid: ' + this.parent.pid +
', tid: ' + this.tid +
(this.name ? ', name: ' + this.name : '');
} }
}; };
...@@ -122,7 +141,7 @@ cr.define('tracing', function() { ...@@ -122,7 +141,7 @@ cr.define('tracing', function() {
*/ */
TimelineThread.compare = function(x, y) { TimelineThread.compare = function(x, y) {
if (x.parent.pid != y.parent.pid) { if (x.parent.pid != y.parent.pid) {
return x.parent.pid - y.parent.pid; return TimelineProcess.compare(x.parent, y.parent.pid);
} }
if (x.name && y.name) { if (x.name && y.name) {
...@@ -139,6 +158,78 @@ cr.define('tracing', function() { ...@@ -139,6 +158,78 @@ cr.define('tracing', function() {
} }
}; };
/**
* Stores all the samples for a given counter.
* @constructor
*/
function TimelineCounter(parent, id, name) {
this.parent = parent;
this.id = id;
this.name = name;
this.seriesNames = [];
this.seriesColors = [];
this.timestamps = [];
this.samples = [];
}
TimelineCounter.prototype = {
__proto__: Object.prototype,
get numSeries() {
return this.seriesNames.length;
},
get numSamples() {
return this.timestamps.length;
},
/**
* Updates the bounds for this counter based on the samples it contains.
*/
updateBounds: function() {
if (this.seriesNames.length != this.seriesColors.length)
throw 'seriesNames.length must match seriesColors.length';
if (this.numSeries * this.numSamples != this.samples.length)
throw 'samples.length must be a multiple of numSamples.';
this.totals = [];
if (this.samples.length == 0) {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
this.maxTotal = 0;
return;
}
this.minTimestamp = this.timestamps[0];
this.maxTimestamp = this.timestamps[this.timestamps.length - 1];
var numSeries = this.numSeries;
var maxTotal = -Infinity;
for (var i = 0; i < this.timestamps.length; i++) {
var total = 0;
for (var j = 0; j < numSeries; j++) {
total += this.samples[i * numSeries + j];
this.totals.push(total);
}
if (total > maxTotal)
maxTotal = total;
}
this.maxTotal = maxTotal;
}
};
/**
* Comparison between counters that orders by pid, then name.
*/
TimelineCounter.compare = function(x, y) {
if (x.parent.pid != y.parent.pid) {
return TimelineProcess.compare(x.parent, y.parent.pid);
}
var tmp = x.name.localeCompare(y.name);
if (tmp == 0)
return x.tid - y.tid;
return tmp;
};
/** /**
* The TimelineProcess represents a single process in the * The TimelineProcess represents a single process in the
...@@ -149,6 +240,7 @@ cr.define('tracing', function() { ...@@ -149,6 +240,7 @@ cr.define('tracing', function() {
function TimelineProcess(pid) { function TimelineProcess(pid) {
this.pid = pid; this.pid = pid;
this.threads = {}; this.threads = {};
this.counters = {};
}; };
TimelineProcess.prototype = { TimelineProcess.prototype = {
...@@ -160,13 +252,35 @@ cr.define('tracing', function() { ...@@ -160,13 +252,35 @@ cr.define('tracing', function() {
return n; return n;
}, },
/**
* @return {TimlineThread} The thread identified by tid on this process,
* creating it if it doesn't exist.
*/
getOrCreateThread: function(tid) { getOrCreateThread: function(tid) {
if (!this.threads[tid]) if (!this.threads[tid])
this.threads[tid] = new TimelineThread(this, tid); this.threads[tid] = new TimelineThread(this, tid);
return this.threads[tid]; return this.threads[tid];
},
/**
* @return {TimlineCounter} The counter on this process named 'name',
* creating it if it doesn't exist.
*/
getOrCreateCounter: function(cat, name) {
var id = cat + '.' + name;
if (!this.counters[id])
this.counters[id] = new TimelineCounter(this, id, name);
return this.counters[id];
} }
}; };
/**
* Comparison between processes that orders by pid.
*/
TimelineProcess.compare = function(x, y) {
return x.pid - y.pid;
};
/** /**
* Computes a simplistic hashcode of the provide name. Used to chose colors * Computes a simplistic hashcode of the provide name. Used to chose colors
* for slices. * for slices.
...@@ -179,6 +293,20 @@ cr.define('tracing', function() { ...@@ -179,6 +293,20 @@ cr.define('tracing', function() {
return hash; return hash;
} }
/**
* The number of color IDs that getStringColorId can choose from.
*/
const numColorIds = 30;
/**
* @return {Number} A color ID that is stably associated to the provided via
* the getStringHash method.
*/
function getStringColorId(string) {
var hash = getStringHash(string);
return hash % numColorIds;
}
/** /**
* Builds a model from an array of TraceEvent objects. * Builds a model from an array of TraceEvent objects.
* @param {Array} events An array of TraceEvents created by * @param {Array} events An array of TraceEvents created by
...@@ -218,8 +346,7 @@ cr.define('tracing', function() { ...@@ -218,8 +346,7 @@ cr.define('tracing', function() {
// The ptid is a unique key for a thread in the trace. // The ptid is a unique key for a thread in the trace.
this.importErrors = []; this.importErrors = [];
// Threadstate // Threadstate.
const numColorIds = 30;
function ThreadState(tid) { function ThreadState(tid) {
this.openSlices = []; this.openSlices = [];
this.openNonNestedSlices = {}; this.openNonNestedSlices = {};
...@@ -229,8 +356,7 @@ cr.define('tracing', function() { ...@@ -229,8 +356,7 @@ cr.define('tracing', function() {
var nameToColorMap = {}; var nameToColorMap = {};
function getColor(name) { function getColor(name) {
if (!(name in nameToColorMap)) { if (!(name in nameToColorMap)) {
var hash = getStringHash(name); nameToColorMap[name] = getStringColorId(name);
nameToColorMap[name] = hash % numColorIds;
} }
return nameToColorMap[name]; return nameToColorMap[name];
} }
...@@ -328,6 +454,42 @@ cr.define('tracing', function() { ...@@ -328,6 +454,42 @@ cr.define('tracing', function() {
// TimelineSliceTrack's redraw() knows how to handle this. // TimelineSliceTrack's redraw() knows how to handle this.
processBegin(state, event); processBegin(state, event);
processEnd(state, event); processEnd(state, event);
} else if (event.ph == 'C') {
var ctr_name;
if (event.id !== undefined)
ctr_name = event.name + '[' + event.id + ']';
else
ctr_name = event.name;
var ctr = this.getOrCreateProcess(event.pid)
.getOrCreateCounter(event.cat, ctr_name);
// Initialize the counter's series fields if needed.
if (ctr.numSeries == 0) {
for (var seriesName in event.args) {
ctr.seriesNames.push(seriesName);
ctr.seriesColors.push(
getStringColorId(ctr.name + '.' + seriesName));
}
if (ctr.numSeries == 0) {
this.importErrors.push('Expected counter ' + event.name +
' to have at least one argument to use as a value.');
// Drop the counter.
delete ctr.parent.counters[ctr.name];
continue;
}
}
// Add the sample values.
ctr.timestamps.push(event.ts);
for (var i = 0; i < ctr.numSeries; i++) {
var seriesName = ctr.seriesNames[i];
if (event.args[seriesName] === undefined) {
ctr.samples.push(0);
continue;
}
ctr.samples.push(event.args[seriesName]);
}
} else if (event.ph == 'M') { } else if (event.ph == 'M') {
if (event.name == 'thread_name') { if (event.name == 'thread_name') {
var thread = this.getOrCreateProcess(event.pid) var thread = this.getOrCreateProcess(event.pid)
...@@ -405,6 +567,16 @@ cr.define('tracing', function() { ...@@ -405,6 +567,16 @@ cr.define('tracing', function() {
wmax = Math.max(wmax, thread.maxTimestamp); wmax = Math.max(wmax, thread.maxTimestamp);
} }
} }
var counters = this.getAllCounters();
for (var tI = 0; tI < counters.length; tI++) {
var counter = counters[tI];
counter.updateBounds();
if (counter.minTimestamp != undefined &&
counter.maxTimestamp != undefined) {
wmin = Math.min(wmin, counter.minTimestamp);
wmax = Math.max(wmax, counter.maxTimestamp);
}
}
this.minTimestamp = wmin; this.minTimestamp = wmin;
this.maxTimestamp = wmax; this.maxTimestamp = wmax;
}, },
...@@ -432,7 +604,12 @@ cr.define('tracing', function() { ...@@ -432,7 +604,12 @@ cr.define('tracing', function() {
shiftSubRow(thread.nonNestedSubRows[tSR]); shiftSubRow(thread.nonNestedSubRows[tSR]);
} }
} }
var counters = this.getAllCounters();
for (var tI = 0; tI < counters.length; tI++) {
var counter = counters[tI];
for (var sI = 0; sI < counter.timestamps.length; sI++)
counter.timestamps[sI] = (counter.timestamps[sI] - timeBase) / 1000;
}
this.updateBounds(); this.updateBounds();
}, },
...@@ -445,14 +622,30 @@ cr.define('tracing', function() { ...@@ -445,14 +622,30 @@ cr.define('tracing', function() {
} }
} }
return threads; return threads;
},
/**
* @return {Array} An array of all the counters in the model.
*/
getAllCounters: function() {
var counters = [];
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.counters) {
counters.push(process.counters[tid]);
}
}
return counters;
} }
}; };
return { return {
getStringHash: getStringHash, getStringHash: getStringHash,
getStringColorId: getStringColorId,
TimelineSlice: TimelineSlice, TimelineSlice: TimelineSlice,
TimelineThread: TimelineThread, TimelineThread: TimelineThread,
TimelineCounter: TimelineCounter,
TimelineProcess: TimelineProcess, TimelineProcess: TimelineProcess,
TimelineModel: TimelineModel TimelineModel: TimelineModel
}; };
......
...@@ -6,7 +6,7 @@ Use of this source code is governed by a BSD-style license that can be ...@@ -6,7 +6,7 @@ Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. found in the LICENSE file.
--> -->
<head> <head>
<title></title> <title>TimelineModel tests</title>
<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script> <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
<script src="../shared/js/cr.js"></script> <script src="../shared/js/cr.js"></script>
<script src="../shared/js/cr/event_target.js"></script> <script src="../shared/js/cr/event_target.js"></script>
...@@ -55,7 +55,7 @@ function testNestedParsing() { ...@@ -55,7 +55,7 @@ function testNestedParsing() {
{name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
{name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'B'}, {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'B'},
{name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'E'}, {name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'E'},
{name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}, {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var p = m.processes[1]; var p = m.processes[1];
...@@ -81,12 +81,12 @@ function testNestedParsing() { ...@@ -81,12 +81,12 @@ function testNestedParsing() {
function testAutoclosing() { function testAutoclosing() {
var events = [ var events = [
// Slice that doesn't finish // Slice that doesn't finish.
{name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
// Slice that does finish to give an 'end time' to make autoclosing work // Slice that does finish to give an 'end time' to make autoclosing work.
{name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'}, {name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'},
{name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}, {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var p = m.processes[1]; var p = m.processes[1];
...@@ -102,13 +102,13 @@ function testAutoclosing() { ...@@ -102,13 +102,13 @@ function testAutoclosing() {
function testNestedAutoclosing() { function testNestedAutoclosing() {
var events = [ var events = [
// Tasks that dont finish // Tasks that dont finish.
{name: 'a1', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a1', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
{name: 'a2', args: {}, pid: 1, ts: 1.5, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a2', args: {}, pid: 1, ts: 1.5, cat: 'foo', tid: 1, ph: 'B'},
// Slice that does finish to give an 'end time' to make autoclosing work // Slice that does finish to give an 'end time' to make autoclosing work.
{name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'}, {name: 'b', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 2, ph: 'B'},
{name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}, {name: 'b', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 2, ph: 'E'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var p = m.processes[1]; var p = m.processes[1];
...@@ -138,7 +138,7 @@ function testTaskColoring() { ...@@ -138,7 +138,7 @@ function testTaskColoring() {
{name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'}, {name: 'b', args: {}, pid: 1, ts: 3, cat: 'foo', tid: 1, ph: 'B'},
{name: 'b', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}, {name: 'b', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'},
{name: 'a', args: {}, pid: 1, ts: 5, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a', args: {}, pid: 1, ts: 5, cat: 'foo', tid: 1, ph: 'B'},
{name: 'a', args: {}, pid: 1, ts: 6, cat: 'foo', tid: 1, ph: 'E'}, {name: 'a', args: {}, pid: 1, ts: 6, cat: 'foo', tid: 1, ph: 'E'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var p = m.processes[1]; var p = m.processes[1];
...@@ -168,7 +168,7 @@ function testMultipleThreadParsing() { ...@@ -168,7 +168,7 @@ function testMultipleThreadParsing() {
assertEquals(2, p.numThreads); assertEquals(2, p.numThreads);
// Check thread 1 // Check thread 1.
var t = p.threads[1]; var t = p.threads[1];
assertNotUndefined(t); assertNotUndefined(t);
assertEquals(1, t.subRows.length); assertEquals(1, t.subRows.length);
...@@ -182,7 +182,7 @@ function testMultipleThreadParsing() { ...@@ -182,7 +182,7 @@ function testMultipleThreadParsing() {
assertEquals((2 - 1) / 1000, slice.duration); assertEquals((2 - 1) / 1000, slice.duration);
assertEquals(0, slice.subSlices.length); assertEquals(0, slice.subSlices.length);
// Check thread 2 // Check thread 2.
var t = p.threads[2]; var t = p.threads[2];
assertNotUndefined(t); assertNotUndefined(t);
assertEquals(1, t.subRows.length); assertEquals(1, t.subRows.length);
...@@ -211,7 +211,7 @@ function testMultiplePidParsing() { ...@@ -211,7 +211,7 @@ function testMultiplePidParsing() {
assertEquals(1, p.numThreads); assertEquals(1, p.numThreads);
// Check process 1 thread 1 // Check process 1 thread 1.
var t = p.threads[1]; var t = p.threads[1];
assertNotUndefined(t); assertNotUndefined(t);
assertEquals(1, t.subRows.length); assertEquals(1, t.subRows.length);
...@@ -225,7 +225,7 @@ function testMultiplePidParsing() { ...@@ -225,7 +225,7 @@ function testMultiplePidParsing() {
assertEquals((2 - 1) / 1000, slice.duration); assertEquals((2 - 1) / 1000, slice.duration);
assertEquals(0, slice.subSlices.length); assertEquals(0, slice.subSlices.length);
// Check process 2 thread 2 // Check process 2 thread 2.
var p = m.processes[2]; var p = m.processes[2];
assertNotUndefined(p); assertNotUndefined(p);
assertEquals(1, p.numThreads); assertEquals(1, p.numThreads);
...@@ -244,10 +244,10 @@ function testMultiplePidParsing() { ...@@ -244,10 +244,10 @@ function testMultiplePidParsing() {
// Check getAllThreads. // Check getAllThreads.
assertArrayEquals([m.processes[1].threads[1], m.processes[2].threads[2]], assertArrayEquals([m.processes[1].threads[1], m.processes[2].threads[2]],
m.getAllThreads()) m.getAllThreads());
} }
// Thread names // Thread names.
function testThreadNames() { function testThreadNames() {
var events = [ var events = [
{name: 'thread_name', args: {name: 'Thread 1'}, {name: 'thread_name', args: {name: 'Thread 1'},
...@@ -257,21 +257,21 @@ function testThreadNames() { ...@@ -257,21 +257,21 @@ function testThreadNames() {
{name: 'b', args: {}, pid: 2, ts: 3, cat: 'foo', tid: 2, ph: 'B'}, {name: 'b', args: {}, pid: 2, ts: 3, cat: 'foo', tid: 2, ph: 'B'},
{name: 'b', args: {}, pid: 2, ts: 4, cat: 'foo', tid: 2, ph: 'E'}, {name: 'b', args: {}, pid: 2, ts: 4, cat: 'foo', tid: 2, ph: 'E'},
{name: 'thread_name', args: {name: 'Thread 2'}, {name: 'thread_name', args: {name: 'Thread 2'},
pid: 2, ts: 0, tid: 2, ph: 'M'}, pid: 2, ts: 0, tid: 2, ph: 'M'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
assertEquals('Thread 1', m.processes[1].threads[1].name); assertEquals('Thread 1', m.processes[1].threads[1].name);
assertEquals('Thread 2', m.processes[2].threads[2].name); assertEquals('Thread 2', m.processes[2].threads[2].name);
} }
// User time // User time.
function testUserTime() { function testUserTime() {
var events = [ var events = [
{name: 'thread_name', args: {name: 'Thread 1'}, {name: 'thread_name', args: {name: 'Thread 1'},
pid: 1, ts: 0, tid: 1, ph: 'M'}, pid: 1, ts: 0, tid: 1, ph: 'M'},
{name: 'a', args: {}, pid: 1, ts: 1, uts: 70, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a', args: {}, pid: 1, ts: 1, uts: 70, cat: 'foo', tid: 1, ph: 'B'},
{name: 'a', args: {}, pid: 1, ts: 2, uts: 77, cat: 'foo', tid: 1, ph: 'E'}, {name: 'a', args: {}, pid: 1, ts: 2, uts: 77, cat: 'foo', tid: 1, ph: 'E'},
{name: 'a', args: {}, pid: 1, ts: 2 , uts: 80, cat: 'foo', tid: 1, ph: 'I'}, {name: 'a', args: {}, pid: 1, ts: 2 , uts: 80, cat: 'foo', tid: 1, ph: 'I'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var subRow = m.processes[1].threads[1].subRows[0]; var subRow = m.processes[1].threads[1].subRows[0];
...@@ -288,7 +288,7 @@ function testImmediateParsing() { ...@@ -288,7 +288,7 @@ function testImmediateParsing() {
// recentering/zeroing doesn't clobber their timestamp. // recentering/zeroing doesn't clobber their timestamp.
{name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'}, {name: 'a', args: {}, pid: 1, ts: 1, cat: 'foo', tid: 1, ph: 'B'},
{name: 'immediate', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'I'}, {name: 'immediate', args: {}, pid: 1, ts: 2, cat: 'foo', tid: 1, ph: 'I'},
{name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}, {name: 'a', args: {}, pid: 1, ts: 4, cat: 'foo', tid: 1, ph: 'E'}
]; ];
var m = new tracing.TimelineModel(events); var m = new tracing.TimelineModel(events);
var p = m.processes[1]; var p = m.processes[1];
...@@ -310,6 +310,124 @@ function testImmediateParsing() { ...@@ -310,6 +310,124 @@ function testImmediateParsing() {
subRow = t.subRows[1]; subRow = t.subRows[1];
assertEquals(immed, subRow[0]); assertEquals(immed, subRow[0]);
} }
function testSimpleCounter() {
var events = [
{name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
ph: 'C'},
{name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
ph: 'C'},
{name: 'ctr', args: {'value': 0}, pid: 1, ts: 20, cat: 'foo', tid: 1,
ph: 'C'}
];
var m = new tracing.TimelineModel(events);
var p = m.processes[1];
var ctr = m.processes[1].counters['foo.ctr'];
assertEquals('ctr', ctr.name);
assertEquals(3, ctr.numSamples);
assertEquals(1, ctr.numSeries);
assertArrayEquals(['value'], ctr.seriesNames);
assertArrayEquals([tracing.getStringColorId('ctr.value')], ctr.seriesColors);
assertArrayEquals([0, 0.01, 0.02], ctr.timestamps);
assertArrayEquals([0, 10, 0], ctr.samples);
assertArrayEquals([0, 10, 0], ctr.totals);
assertEquals(10, ctr.maxTotal);
}
function testInstanceCounter() {
var events = [
{name: 'ctr', args: {'value': 0}, pid: 1, ts: 0, cat: 'foo', tid: 1,
ph: 'C', id: 0},
{name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
ph: 'C', id: 0},
{name: 'ctr', args: {'value': 10}, pid: 1, ts: 10, cat: 'foo', tid: 1,
ph: 'C', id: 1},
{name: 'ctr', args: {'value': 20}, pid: 1, ts: 15, cat: 'foo', tid: 1,
ph: 'C', id: 1},
{name: 'ctr', args: {'value': 30}, pid: 1, ts: 18, cat: 'foo', tid: 1,
ph: 'C', id: 1}
];
var m = new tracing.TimelineModel(events);
var p = m.processes[1];
var ctr = m.processes[1].counters['foo.ctr[0]'];
assertEquals('ctr[0]', ctr.name);
assertEquals(2, ctr.numSamples);
assertEquals(1, ctr.numSeries);
assertArrayEquals([0, 0.01], ctr.timestamps);
assertArrayEquals([0, 10], ctr.samples);
var ctr = m.processes[1].counters['foo.ctr[1]'];
assertEquals('ctr[1]', ctr.name);
assertEquals(3, ctr.numSamples);
assertEquals(1, ctr.numSeries);
assertArrayEquals([0.01, 0.015, 0.018], ctr.timestamps);
assertArrayEquals([10, 20, 30], ctr.samples);
}
function testMultiCounterUpdateBounds() {
var ctr = new tracing.TimelineCounter(undefined, 'testBasicCounter',
'testBasicCounter');
ctr.numSeries = 1;
ctr.seriesNames = ['value1', 'value2'];
ctr.seriesColors = ['testBasicCounter.value1', 'testBasicCounter.value2'];
ctr.timestamps = [0, 1, 2, 3, 4, 5, 6, 7];
ctr.samples = [0, 0,
1, 0,
1, 1,
2, 1.1,
3, 0,
1, 7,
3, 0,
3.1, 0.5];
ctr.updateBounds();
assertEquals(0, ctr.minTimestamp);
assertEquals(7, ctr.maxTimestamp);
assertEquals(8, ctr.maxTotal);
assertArrayEquals([0, 0,
1, 1,
1, 2,
2, 3.1,
3, 3,
1, 8,
3, 3,
3.1, 3.6], ctr.totals);
}
function testMultiCounter() {
var events = [
{name: 'ctr', args: {'value1': 0, 'value2': 7}, pid: 1, ts: 0, cat: 'foo',
tid: 1, ph: 'C'},
{name: 'ctr', args: {'value1': 10, 'value2': 4}, pid: 1, ts: 10, cat: 'foo',
tid: 1, ph: 'C'},
{name: 'ctr', args: {'value1': 0, 'value2': 1 }, pid: 1, ts: 20, cat: 'foo',
tid: 1, ph: 'C'}
];
var m = new tracing.TimelineModel(events);
var p = m.processes[1];
var ctr = m.processes[1].counters['foo.ctr'];
assertEquals('ctr', ctr.name);
assertEquals('ctr', ctr.name);
assertEquals(3, ctr.numSamples);
assertEquals(2, ctr.numSeries);
assertArrayEquals(['value1', 'value2'], ctr.seriesNames);
assertArrayEquals([tracing.getStringColorId('ctr.value1'),
tracing.getStringColorId('ctr.value2')],
ctr.seriesColors);
assertArrayEquals([0, 0.01, 0.02], ctr.timestamps);
assertArrayEquals([0, 7,
10, 4,
0, 1], ctr.samples);
assertArrayEquals([0, 7,
10, 14,
0, 1], ctr.totals);
assertEquals(14, ctr.maxTotal);
}
</script> </script>
</body> </body>
</html> </html>
<!DOCTYPE HTML>
<html>
<!--
Copyright (c) 2011 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.
-->
<head i18n-values="dir:textdirection;">
<title>Interactive Timeline Tests</title>
<link rel="stylesheet" href="timeline.css">
<script src="../shared/js/cr.js"></script>
<script src="../shared/js/cr/event_target.js"></script>
<script src="../shared/js/cr/ui.js"></script>
<script src="../shared/js/util.js"></script>
<script src="timeline_model.js"></script>
<script src="sorted_array_utils.js"></script>
<script src="measuring_stick.js"></script>
<script src="timeline.js"></script>
<script src="timeline_track.js"></script>
<script src="fast_rect_renderer.js"></script>
<script src="test_utils.js"></script>
</head>
<body>
<div class="timeline-test" src="./tests/trivial_trace.json" create-detached=1>
</div>
<div class="timeline-test" src="./tests/trivial_trace.json">
</div>
<div class="timeline-test" src="./tests/simple_trace.json">
</div>
<div class="timeline-test" src="./tests/instance_counters.json">
</div>
<div class="timeline-test" src="./tests/nonnested_trace.json">
</div>
<div class="timeline-test" src="./tests/tall_trace.json">
</div>
<div class="timeline-test" src="./tests/big_trace.json">
</div>
<div class="timeline-test" src="./tests/huge_trace.json">
</div>
<script>
function load(parentEl) {
var src = parentEl.getAttribute('src');
if (document.location.hash && document.location.hash.substring(1) != src) {
parentEl.hidden = true;
return;
}
parentEl.hidden = false;
parentEl.textContent = '';
var titleEl = document.createElement('h3');
var linkEl = document.createElement('a');
linkEl.textContent = src;
linkEl.href = '#' + src;
titleEl.appendChild(linkEl);
var containerEl = document.createElement('div');
containerEl.tabIndex = 0;
containerEl.style.border = '1px solid red';
var timelineEl = document.createElement('div');
cr.ui.decorate(timelineEl, tracing.Timeline);
timelineEl.focusElement = containerEl;
parentEl.appendChild(titleEl);
parentEl.appendChild(containerEl);
// Creating attached vs detached stress tests the canvas- and viewport-
// setup code.
var create_detached = parentEl.getAttribute('create-attached') == 1;
if (create_detached) {
containerEl.appendChild(timelineEl);
test_utils.getJSON(src, function(events) {
var model = new tracing.TimelineModel(events);
timelineEl.model = model;
});
} else {
test_utils.getJSON(src, function(events) {
var model = new tracing.TimelineModel(events);
timelineEl.model = model;
containerEl.appendChild(timelineEl);
});
}
}
function onLoad() {
Array.prototype.forEach.call(document.querySelectorAll('.timeline-test'),
load);
}
document.addEventListener('DOMContentLoaded', onLoad);
window.addEventListener('hashchange', onLoad);
</script>
</body>
</html>
...@@ -66,64 +66,32 @@ cr.define('tracing', function() { ...@@ -66,64 +66,32 @@ cr.define('tracing', function() {
return w; return w;
} }
function addTrack(thisTrack, slices) {
var track = new TimelineSliceTrack();
track.heading = '';
track.slices = slices;
track.viewport = thisTrack.viewport_;
thisTrack.tracks_.push(track);
thisTrack.appendChild(track);
}
/** /**
* Generic base class for timeline tracks * A generic track that contains other tracks as its children.
* @constructor
*/ */
TimelineThreadTrack = cr.ui.define('div'); var TimelineContainerTrack = cr.ui.define('div');
TimelineThreadTrack.prototype = { TimelineContainerTrack.prototype = {
__proto__: HTMLDivElement.prototype, __proto__: HTMLDivElement.prototype,
decorate: function() { decorate: function() {
this.className = 'timeline-thread-track'; this.tracks_ = [];
},
set thread(thread) {
this.thread_ = thread;
this.updateChildTracks_();
}, },
/** detach: function() {
* @return {string} A human-readable name for the track. for (var i = 0; i < this.tracks_.length; i++)
*/ this.tracks_[i].detach();
get heading() {
if (!this.thread_)
return '';
var tname = this.thread_.name || this.thread_.tid;
return this.thread_.parent.pid + ': ' +
tname + ':';
}, },
set headingWidth(width) { get viewport() {
for (var i = 0; i < this.tracks_.length; i++) return this.viewport_;
this.tracks_[i].headingWidth = width;
}, },
set viewport(v) { set viewport(v) {
this.viewport_ = v; this.viewport_ = v;
for (var i = 0; i < this.tracks_.length; i++) for (var i = 0; i < this.tracks_.length; i++)
this.tracks_[i].viewport = v; this.tracks_[i].viewport = v;
this.invalidate(); this.updateChildTracks_();
},
invalidate: function() {
if (this.parentNode)
this.parentNode.invalidate();
},
onResize: function() {
for (var i = 0; i < this.tracks_.length; i++)
this.tracks_[i].onResize();
}, },
get firstCanvas() { get firstCanvas() {
...@@ -132,30 +100,6 @@ cr.define('tracing', function() { ...@@ -132,30 +100,6 @@ cr.define('tracing', function() {
return undefined; return undefined;
}, },
redraw: function() {
for (var i = 0; i < this.tracks_.length; i++)
this.tracks_[i].redraw();
},
updateChildTracks_: function() {
this.textContent = '';
this.tracks_ = [];
if (this.thread_) {
for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) {
addTrack(this, this.thread_.nonNestedSubRows[srI]);
}
for (var srI = 0; srI < this.thread_.subRows.length; ++srI) {
addTrack(this, this.thread_.subRows[srI]);
}
if (this.tracks_.length > 0) {
this.tracks_[0].heading = this.heading;
this.tracks_[0].tooltip = 'pid: ' + this.thread_.parent.pid +
', tid: ' + this.thread_.tid +
(this.thread_.name ? ', name: ' + this.thread_.name : '');
}
}
},
/** /**
* Picks a slice, if any, at a given location. * Picks a slice, if any, at a given location.
* @param {number} wX X location to search at, in worldspace. * @param {number} wX X location to search at, in worldspace.
...@@ -199,33 +143,120 @@ cr.define('tracing', function() { ...@@ -199,33 +143,120 @@ cr.define('tracing', function() {
}; };
/** /**
* Creates a new timeline track div element * Visualizes a TimelineThread using a series of of TimelineSliceTracks.
* @constructor
*/
var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack);
TimelineThreadTrack.prototype = {
__proto__: TimelineContainerTrack.prototype,
decorate: function() {
this.classList.add('timeline-thread-track');
},
get thread(thread) {
return this.thread_;
},
set thread(thread) {
this.thread_ = thread;
this.updateChildTracks_();
},
get tooltip() {
return this.tooltip_;
},
set tooltip(value) {
this.tooltip_ = value;
this.updateChildTracks_();
},
get heading() {
return this.heading_;
},
set heading(h) {
this.heading_ = h;
this.updateChildTracks_();
},
get headingWidth() {
return this.headingWidth_;
},
set headingWidth(width) {
this.headingWidth_ = width;
this.updateChildTracks_();
},
addTrack_: function(slices) {
var track = new TimelineSliceTrack();
track.heading = '';
track.slices = slices;
track.headingWidth = this.headingWidth_;
track.viewport = this.viewport_;
this.tracks_.push(track);
this.appendChild(track);
},
updateChildTracks_: function() {
this.detach();
this.textContent = '';
this.tracks_ = [];
if (this.thread_) {
for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) {
this.addTrack_(this.thread_.nonNestedSubRows[srI]);
}
for (var srI = 0; srI < this.thread_.subRows.length; ++srI) {
this.addTrack_(this.thread_.subRows[srI]);
}
if (this.tracks_.length > 0) {
this.tracks_[0].heading = this.heading_;
this.tracks_[0].tooltip = this.tooltip_;
}
}
}
};
/**
* A canvas-based track constructed. Provides the basic heading and
* invalidation-managment infrastructure. Subclasses must implement drawing
* and picking code.
* @constructor * @constructor
* @extends {HTMLDivElement} * @extends {HTMLDivElement}
*/ */
TimelineSliceTrack = cr.ui.define('div'); var CanvasBasedTrack = cr.ui.define('div');
TimelineSliceTrack.prototype = { CanvasBasedTrack.prototype = {
__proto__: HTMLDivElement.prototype, __proto__: HTMLDivElement.prototype,
decorate: function() { decorate: function() {
this.className = 'timeline-slice-track'; this.className = 'timeline-canvas-based-track';
this.slices_ = null; this.slices_ = null;
this.headingDiv_ = document.createElement('div'); this.headingDiv_ = document.createElement('div');
this.headingDiv_.className = 'timeline-slice-track-title'; this.headingDiv_.className = 'timeline-canvas-based-track-title';
this.appendChild(this.headingDiv_); this.appendChild(this.headingDiv_);
this.canvasContainer_ = document.createElement('div'); this.canvasContainer_ = document.createElement('div');
this.canvasContainer_.className = 'timeline-slice-track-canvas-container'; this.canvasContainer_.className =
'timeline-canvas-based-track-canvas-container';
this.appendChild(this.canvasContainer_); this.appendChild(this.canvasContainer_);
this.canvas_ = document.createElement('canvas'); this.canvas_ = document.createElement('canvas');
this.canvas_.className = 'timeline-slice-track-canvas'; this.canvas_.className = 'timeline-canvas-based-track-canvas';
this.canvasContainer_.appendChild(this.canvas_); this.canvasContainer_.appendChild(this.canvas_);
this.ctx_ = this.canvas_.getContext('2d'); this.ctx_ = this.canvas_.getContext('2d');
}, },
detach: function() {
if (this.viewport_)
this.viewport_.removeEventListener('change',
this.viewportChangeBoundToThis_);
},
set headingWidth(width) { set headingWidth(width) {
this.headingDiv_.style.width = width; this.headingDiv_.style.width = width;
}, },
...@@ -242,41 +273,85 @@ cr.define('tracing', function() { ...@@ -242,41 +273,85 @@ cr.define('tracing', function() {
this.headingDiv_.title = text; this.headingDiv_.title = text;
}, },
set slices(slices) { get viewport() {
this.slices_ = slices; return this.viewport_;
this.invalidate();
}, },
set viewport(v) { set viewport(v) {
this.viewport_ = v; this.viewport_ = v;
if (this.viewport_)
this.viewport_.removeEventListener('change',
this.viewportChangeBoundToThis_);
this.viewport_ = v;
if (this.viewport_) {
this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this);
this.viewport_.addEventListener('change',
this.viewportChangeBoundToThis_);
}
this.invalidate();
},
viewportChange_: function() {
this.invalidate(); this.invalidate();
}, },
invalidate: function() { invalidate: function() {
if (this.parentNode) if (this.rafPending_)
this.parentNode.invalidate(); return;
webkitRequestAnimationFrame(function() {
this.rafPending_ = false;
if (!this.viewport_)
return;
if (this.canvas_.width != this.canvasContainer_.clientWidth)
this.canvas_.width = this.canvasContainer_.clientWidth;
if (this.canvas_.height != this.canvasContainer_.clientHeight)
this.canvas_.height = this.canvasContainer_.clientHeight;
this.redraw();
}.bind(this), this);
this.rafPending_ = true;
}, },
get firstCanvas() { get firstCanvas() {
return this.canvas_; return this.canvas_;
}
};
/**
* A track that displays an array of TimelineSlice objects.
* @constructor
* @extends {CanvasBasedTrack}
*/
var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack);
TimelineSliceTrack.prototype = {
__proto__: CanvasBasedTrack.prototype,
decorate: function() {
this.classList.add('timeline-slice-track');
}, },
onResize: function() { get slices() {
this.canvas_.width = this.canvasContainer_.clientWidth; return this.slices_;
this.canvas_.height = this.canvasContainer_.clientHeight; },
set slices(slices) {
this.slices_ = slices;
this.invalidate(); this.invalidate();
}, },
redraw: function() { redraw: function() {
if (!this.viewport_)
return;
var ctx = this.ctx_; var ctx = this.ctx_;
var canvasW = this.canvas_.width; var canvasW = this.canvas_.width;
var canvasH = this.canvas_.height; var canvasH = this.canvas_.height;
ctx.clearRect(0, 0, canvasW, canvasH); ctx.clearRect(0, 0, canvasW, canvasH);
// culling... // Culling parameters.
var vp = this.viewport_; var vp = this.viewport_;
var pixWidth = vp.xViewVectorToWorld(1); var pixWidth = vp.xViewVectorToWorld(1);
var viewLWorld = vp.xViewToWorld(0); var viewLWorld = vp.xViewToWorld(0);
...@@ -301,11 +376,11 @@ cr.define('tracing', function() { ...@@ -301,11 +376,11 @@ cr.define('tracing', function() {
ctx.stroke(); ctx.stroke();
} }
// begin rendering in world space // Begin rendering in world space.
ctx.save(); ctx.save();
vp.applyTransformToCanavs(ctx); vp.applyTransformToCanavs(ctx);
// tracks // Slices.
var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth, var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth,
2 * pixWidth, viewRWorld, pallette); 2 * pixWidth, viewRWorld, pallette);
tr.setYandH(0, canvasH); tr.setYandH(0, canvasH);
...@@ -343,7 +418,7 @@ cr.define('tracing', function() { ...@@ -343,7 +418,7 @@ cr.define('tracing', function() {
tr.flush(); tr.flush();
ctx.restore(); ctx.restore();
// labels // Labels.
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.textBaseline = 'top'; ctx.textBaseline = 'top';
ctx.font = '10px sans-serif'; ctx.font = '10px sans-serif';
...@@ -362,7 +437,7 @@ cr.define('tracing', function() { ...@@ -362,7 +437,7 @@ cr.define('tracing', function() {
if (labelWidthWorld < slice.duration) { if (labelWidthWorld < slice.duration) {
var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration); var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration);
ctx.fillText(title, cX, 2.5, labelWidthWorld); ctx.fillText(title, cX, 2.5, labelWidth);
} }
} }
} }
...@@ -473,7 +548,153 @@ cr.define('tracing', function() { ...@@ -473,7 +548,153 @@ cr.define('tracing', function() {
}; };
/**
* A track that displays a TimelineCounter object.
* @constructor
* @extends {CanvasBasedTrack}
*/
var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack);
TimelineCounterTrack.prototype = {
__proto__: CanvasBasedTrack.prototype,
decorate: function() {
this.classList.add('timeline-counter-track');
},
get counter() {
return this.counter_;
},
set counter(counter) {
this.counter_ = counter;
this.invalidate();
},
redraw: function() {
var ctr = this.counter_;
var ctx = this.ctx_;
var canvasW = this.canvas_.width;
var canvasH = this.canvas_.height;
ctx.clearRect(0, 0, canvasW, canvasH);
// Culling parametrs.
var vp = this.viewport_;
var pixWidth = vp.xViewVectorToWorld(1);
var viewLWorld = vp.xViewToWorld(0);
var viewRWorld = vp.xViewToWorld(canvasW);
// Drop sampels that are less than skipDistancePix apart.
var skipDistancePix = 16;
var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix);
// Begin rendering in world space.
ctx.save();
vp.applyTransformToCanavs(ctx);
// Figure out where drawing should begin.
var numSeries = ctr.numSeries;
var numSamples = ctr.numSamples;
var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps,
function() {
},
viewLWorld);
// Draw indices one by one until we fall off the viewRWorld.
var yScale = canvasH / ctr.maxTotal;
for (var seriesIndex = ctr.numSeries - 1;
seriesIndex >= 0; seriesIndex--) {
var colorId = ctr.seriesColors[seriesIndex];
ctx.fillStyle = pallette[colorId];
ctx.beginPath();
// Set iLast and xLast such that the first sample we draw is the
// startIndex sample.
var iLast = startIndex - 1;
var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1;
var yLastView = canvasH;
// Iterate over samples from iLast onward until we either fall off the
// viewRWorld or we run out of samples. To avoid drawing too much, after
// drawing a sample at xLast, skip subsequent samples that are less than
// skipDistanceWorld from xLast.
var hasMoved = false;
while (true) {
var i = iLast + 1;
if (i >= numSamples) {
ctx.lineTo(xLast, yLastView);
ctx.lineTo(xLast + 8 * pixWidth, yLastView);
ctx.lineTo(xLast + 8 * pixWidth, canvasH);
break;
}
var x = ctr.timestamps[i];
var y = ctr.totals[i * numSeries + seriesIndex];
var yView = canvasH - (yScale * y);
if (x > viewRWorld) {
ctx.lineTo(x, yLastView);
ctx.lineTo(x, canvasH);
break;
}
if (x - xLast < skipDistanceWorld) {
iLast = i;
continue;
}
if (!hasMoved) {
ctx.moveTo(viewLWorld, canvasH);
hasMoved = true;
}
ctx.lineTo(x, yLastView);
ctx.lineTo(x, yView);
iLast = i;
xLast = x;
yLastView = yView;
}
ctx.closePath();
ctx.fill();
}
ctx.restore();
},
/**
* Picks a slice, if any, at a given location.
* @param {number} wX X location to search at, in worldspace.
* @param {number} wY Y location to search at, in offset space.
* offset space.
* @param {function():*} onHitCallback Callback to call with the slice,
* if one is found.
* @return {boolean} true if a slice was found, otherwise false.
*/
pick: function(wX, wY, onHitCallback) {
},
/**
* Finds slices intersecting the given interval.
* @param {number} loWX Lower X bound of the interval to search, in
* worldspace.
* @param {number} hiWX Upper X bound of the interval to search, in
* worldspace.
* @param {number} loY Lower Y bound of the interval to search, in
* offset space.
* @param {number} hiY Upper Y bound of the interval to search, in
* offset space.
* @param {function():*} onHitCallback Function to call for each slice
* intersecting the interval.
*/
pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) {
}
};
return { return {
TimelineCounterTrack: TimelineCounterTrack,
TimelineSliceTrack: TimelineSliceTrack, TimelineSliceTrack: TimelineSliceTrack,
TimelineThreadTrack: TimelineThreadTrack TimelineThreadTrack: TimelineThreadTrack
}; };
......
<!DOCTYPE HTML>
<html>
<!--
Copyright (c) 2010 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.
-->
<head i18n-values="dir:textdirection;">
<title>TimelineTrack tests</title>
<script
src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js">
</script>
<script>
goog.require('goog.testing.jsunit');
</script>
<style>
* {
box-sizing: border-box;
-webkit-user-select: none;
}
.timeline-container {
border: 1px solid red;
}
</style>
<link rel="stylesheet" href="timeline.css">
<script src="../shared/js/cr.js"></script>
<script src="../shared/js/cr/event_target.js"></script>
<script src="../shared/js/cr/ui.js"></script>
<script src="../shared/js/util.js"></script>
<script src="timeline_model.js"></script>
<script src="sorted_array_utils.js"></script>
<script src="measuring_stick.js"></script>
<script src="timeline.js"></script>
<script src="timeline_track.js"></script>
<script src="fast_rect_renderer.js"></script>
</head>
<body>
<script>
</script>
<script>
var TimelineCounter = tracing.TimelineCounter;
var TimelineCounterTrack = tracing.TimelineCounterTrack;
var TimelineSliceTrack = tracing.TimelineSliceTrack;
var TimelineSlice = tracing.TimelineSlice;
var TimelineViewport = tracing.TimelineViewport;
var testDivs = {};
function getTestDiv(name) {
if (!testDivs[name]) {
testDivs[name] = document.createElement('div');
document.body.appendChild(testDivs[name]);
}
testDivs[name].textContent = '';
return testDivs[name];
}
function testBasicSlices() {
var testEl = getTestDiv('testBasicSlices');
var track = TimelineSliceTrack();
testEl.appendChild(track);
track.heading = 'testBasicSlices';
track.slices = [
new TimelineSlice('a', 0, 1, {}, 1),
new TimelineSlice('b', 1, 2.1, {}, 4.8),
new TimelineSlice('b', 1, 7, {}, 0.5),
new TimelineSlice('c', 2, 7.6, {}, 0.4)
];
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end));
}
function testShrinkingSliceSizes() {
var testEl = getTestDiv('testShrinkingSliceSizes');
var track = TimelineSliceTrack();
testEl.appendChild(track);
track.heading = 'testShrinkingSliceSizes';
var x = 0;
var widths = [10, 5, 4, 3, 2, 1, 0.5, 0.4, 0.3, 0.2, 0.1, 0.05];
var slices = [];
for (var i = 0; i < widths.length; i++) {
var s = new TimelineSlice('a', 1, x, {}, widths[i]);
x += s.duration + 0.5;
slices.push(s);
}
track.slices = slices;
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end));
}
function testPick() {
var testEl = getTestDiv('testPick');
var track = TimelineSliceTrack();
testEl.appendChild(track);
track.heading = 'testPick';
track.headingWidth = '100px';
track.slices = [
new TimelineSlice('a', 0, 1, {}, 1),
new TimelineSlice('b', 1, 2.1, {}, 4.8)
];
track.style.width = '500px';
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end));
var clientRect = track.getBoundingClientRect();
var hits = [];
track.pick(1.5, clientRect.top + 5, function(x, y, z) { hits.push(z); });
assertEquals(track.slices[0], hits[0]);
hits = [];
track.pick(2, clientRect.top + 5, function(x, y, z) { hits.push(z); });
assertEquals(0, hits.length);
hits = [];
track.pick(6.8, clientRect.top + 5, function(x, y, z) { hits.push(z); });
assertEquals(track.slices[1], hits[0]);
hits = [];
track.pick(6.9, clientRect.top + 5, function(x, y, z) { hits.push(z); });
assertEquals(0, hits.length);
}
function testBasicCounter() {
var testEl = getTestDiv('testBasicCounter');
var ctr = new TimelineCounter(undefined, 'testBasicCounter', 'testBasicCounter');
ctr.numSeries = 1;
ctr.seriesNames = ['value1', 'value2'];
ctr.seriesColors = [tracing.getStringColorId('testBasicCounter.value1'),
tracing.getStringColorId('testBasicCounter.value2')];
ctr.timestamps = [0, 1, 2, 3, 4, 5, 6, 7];
ctr.samples = [0, 5,
3, 3,
1, 1,
2, 1.1,
3, 0,
1, 7,
3, 0,
3.1, 0.5];
ctr.updateBounds();
var track = TimelineCounterTrack();
testEl.appendChild(track);
track.heading = ctr.name;
track.counter = ctr;
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth / (1.1 * ctr.maxTimestamp));
}
</script>
</body>
</html>
...@@ -75,6 +75,7 @@ cr.define('tracing', function() { ...@@ -75,6 +75,7 @@ cr.define('tracing', function() {
this.timeline_.detach(); this.timeline_.detach();
this.timeline_ = new tracing.Timeline(); this.timeline_ = new tracing.Timeline();
this.timeline_.model = this.timelineModel_; this.timeline_.model = this.timelineModel_;
this.timeline_.focusElement = this.parentElement;
this.timelineContainer_.appendChild(this.timeline_); this.timelineContainer_.appendChild(this.timeline_);
this.timeline_.addEventListener('selectionChange', this.timeline_.addEventListener('selectionChange',
this.onSelectionChangedBoundToThis_); this.onSelectionChangedBoundToThis_);
......
...@@ -27,13 +27,6 @@ cr.define('tracing', function() { ...@@ -27,13 +27,6 @@ cr.define('tracing', function() {
this.traceEvents_ = []; this.traceEvents_ = [];
if (browserBridge.debugMode) {
var tracingControllerTests = document.createElement('script');
tracingControllerTests.src =
'./tracing/tracing_controller_tests.js';
document.body.appendChild(tracingControllerTests);
}
this.onKeydownBoundToThis_ = this.onKeydown_.bind(this); this.onKeydownBoundToThis_ = this.onKeydown_.bind(this);
this.onKeypressBoundToThis_ = this.onKeypress_.bind(this); this.onKeypressBoundToThis_ = this.onKeypress_.bind(this);
...@@ -81,12 +74,8 @@ cr.define('tracing', function() { ...@@ -81,12 +74,8 @@ cr.define('tracing', function() {
this.statusDiv_.textContent = 'Tracing active.'; this.statusDiv_.textContent = 'Tracing active.';
this.traceEvents_ = []; this.traceEvents_ = [];
if (!browserBridge.debugMode) {
chrome.send('beginTracing'); chrome.send('beginTracing');
this.beginRequestBufferPercentFull_(); this.beginRequestBufferPercentFull_();
} else {
tracing.tracingControllerTestHarness.beginTracing();
}
this.tracingEnabled_ = true; this.tracingEnabled_ = true;
...@@ -166,11 +155,7 @@ cr.define('tracing', function() { ...@@ -166,11 +155,7 @@ cr.define('tracing', function() {
// delay sending endTracingAsync until we get a chance to // delay sending endTracingAsync until we get a chance to
// update the screen... // update the screen...
window.setTimeout(function() { window.setTimeout(function() {
if (!browserBridge.debugMode) {
chrome.send('endTracingAsync'); chrome.send('endTracingAsync');
} else {
tracing.tracingControllerTestHarness.endTracing();
}
}, 100); }, 100);
}, },
......
// Copyright (c) 2011 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.
cr.define('tracing', function() {
var dataSets = [
{
name: 'big_trace',
events_url: './tests/big_trace.json'
},
{
name: 'trivial_trace',
events: [
{'name': 'a', 'args': {},'pid': 52, 'ts': 9524, 'cat': 'foo', 'tid': 53,
'ph': 'B'},
{'name': 'a', 'args': {},'pid': 52, 'ts': 9560, 'cat': 'foo', 'tid': 53,
'ph': 'E'},
{'name': 'b', 'args': {},'pid': 52, 'ts': 9629, 'cat': 'foo', 'tid': 53,
'ph': 'B'},
{'name': 'b', 'args': {},'pid': 52, 'ts': 9631, 'cat': 'foo', 'tid': 53,
'ph': 'E'}
]
},
{
name: 'simple_trace',
events: [
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 826, 'ph': 'B',
'name': 'A long name that doesnt fit but is exceedingly informative',
'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 827, 'ph': 'B',
'name': 'Asub with a name that wont fit', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 828, 'ph': 'E',
'name': 'Asub', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 829, 'ph': 'B',
'name': 'Asub', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 832, 'ph': 'E',
'name': 'Asub', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 833, 'ph': 'E',
'name': '', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 835, 'ph': 'I',
'name': 'I1', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 837, 'ph': 'I',
'name': 'I2', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 840, 'ph': 'B',
'name': 'A not as long a name', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 848, 'ph': 'E',
'name': 'A not as long a name', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 850, 'ph': 'B',
'name': 'B', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 854, 'ph': 'E',
'name': 'B', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 827, 'ph': 'B',
'name': 'A', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 835, 'ph': 'I',
'name': 'Immediate Three', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 845, 'ph': 'I',
'name': 'I4', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 854, 'ph': 'E',
'name': 'A', 'args': {}},
{'cat': '__metadata', 'pid': 22630, 'tid': 22630, 'ts': 0, 'ph': 'M',
'name': 'thread_name', 'args': {'name': 'threadA'}},
{'cat': '__metadata', 'pid': 22630, 'tid': 22631, 'ts': 0, 'ph': 'M',
'name': 'thread_name', 'args': {'name': 'threadB'}},
{'cat': '__metadata', 'pid': 22630, 'tid': 22632, 'ts': 0, 'ph': 'M',
'name': 'thread_name', 'args': {'name': 'threadC'}}
]
},
{
name: 'nonnested_trace',
events: [
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 826, 'ph': 'B',
'name': 'A', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 827, 'ph': 'B',
'name': 'Asub', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 829, 'ph': 'B',
'name': 'NonNest', 'args': {'id': '1', 'ui-nest': '0'}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 830, 'ph': 'B',
'name': 'NonNest', 'args': {'id': '2', 'ui-nest': '0'}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 831, 'ph': 'E',
'name': 'Asub', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 832, 'ph': 'E',
'name': 'NonNest', 'args': {'id': '1', 'ui-nest': '0'}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 833, 'ph': 'E',
'name': 'NonNest', 'args': {'id': '2', 'ui-nest': '0'}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22630, 'ts': 834, 'ph': 'E',
'name': 'A', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 827, 'ph': 'B',
'name': 'A', 'args': {}},
{'cat': 'PERF', 'pid': 22630, 'tid': 22631, 'ts': 854, 'ph': 'E',
'name': 'A', 'args': {}}
]
},
{
name: 'tall_trace',
events: [
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 826, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 827, 'ph': 'B', 'name': 'Asub',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 828, 'ph': 'E', 'name': 'Asub',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 829, 'ph': 'B', 'name': 'Asub',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 832, 'ph': 'E', 'name': 'Asub',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 30, 'ts': 833, 'ph': 'E', 'name': '',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 31, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 31, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 32, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 32, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 33, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 33, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 34, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 34, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 35, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 35, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 36, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 36, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 37, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 37, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 38, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 38, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 39, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 39, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 10, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 10, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 31, 'tid': 11, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 31, 'tid': 11, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 12, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 12, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 13, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 13, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 14, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 14, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 15, 'ts': 840, 'ph': 'B', 'name': 'A',
'args': {}},
{'cat': 'X', 'pid': 30, 'tid': 15, 'ts': 848, 'ph': 'E', 'name': 'A',
'args': {}},
{'cat': '__metadata', 'pid': 30, 'tid': 14, 'ts': 0, 'ph': 'M',
'name': 'thread_name', 'args': {'name': 'threadB'}},
{'cat': '__metadata', 'pid': 30, 'tid': 15, 'ts': 0, 'ph': 'M',
'name': 'thread_name', 'args': {'name': 'threadA'}}
]
},
{
name: 'huge_trace',
events_url: './tests/huge_trace.json'
}
];
// Create UI for controlling the test harness
var selectEl = document.createElement('select');
for (var i = 0; i < dataSets.length; ++i) {
var optionEl = document.createElement('option');
optionEl.textContent = dataSets[i].name;
optionEl.dataSet = dataSets[i];
selectEl.appendChild(optionEl);
}
selectEl.addEventListener('change', function() {
tracingController.beginTracing();
});
selectEl.addEventListener('keydown', function() {
window.setTimeout(function() {
tracingController.beginTracing();
}, 0);
});
var controlEl = document.createElement('div');
var textEl = document.createElement('span');
textEl.textContent = 'Trace:';
controlEl.appendChild(textEl);
controlEl.appendChild(selectEl);
document.querySelector('#debug-div').appendChild(controlEl,
document.body.firstChild);
return {
tracingControllerTestHarness: {
beginTracing: function() {
var dataSet = dataSets[selectEl.selectedIndex];
if (dataSet.events) {
window.setTimeout(function() {
tracingController.onTraceDataCollected(dataSet.events);
tracingController.endTracing();
window.setTimeout(function() {
tracingController.onEndTracingComplete();
},0);
}, 0);
} else {
var req = new XMLHttpRequest();
req.open('GET', './tracing/' + dataSet.events_url, true);
req.onreadystatechange = function(aEvt) {
if (req.readyState == 4) {
tracingController.endTracing();
window.setTimeout(function() {
if (req.status == 200) {
var resp = JSON.parse(req.responseText);
if (resp.traceEvents)
tracingController.onTraceDataCollected(resp.traceEvents);
else
tracingController.onTraceDataCollected(resp);
} else {
console.log('collection failed.');
}
tracingController.onEndTracingComplete();
}, 0);
}
};
req.send(null);
}
},
endTracing: function() {
}
}
};
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment