Commit 6c53291a authored by nduca@chromium.org's avatar nduca@chromium.org

about:tracing support for TRACE_ASYNC_START/FINISH events.


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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@126978 0039d316-1c4b-4281-b951-d872f2087c98
parent 07414c07
// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // 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.
...@@ -191,6 +191,13 @@ cr.define('tracing', function() { ...@@ -191,6 +191,13 @@ cr.define('tracing', function() {
this.buildPerThreadCpuSlicesFromCpuState(); this.buildPerThreadCpuSlicesFromCpuState();
}, },
/**
* Called by the TimelineModel after all other importers have imported their
* events.
*/
finalizeImport: function() {
},
/** /**
* Builds the cpuSlices array on each thread based on our knowledge of what * Builds the cpuSlices array on each thread based on our knowledge of what
* each Cpu is doing. This is done only for TimelineThreads that are * each Cpu is doing. This is done only for TimelineThreads that are
......
[
{"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
/* /*
Copyright (c) 2012 The Chromium Authors. All rights reserved. * Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be * 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.
*/ */
.timeline * {
-webkit-user-select: none;
cursor: default;
}
.timeline-drag-box { .timeline-drag-box {
background-color: rgba(0, 0, 255, 0.25); background-color: rgba(0, 0, 255, 0.25);
...@@ -15,6 +20,27 @@ found in the LICENSE file. ...@@ -15,6 +20,27 @@ found in the LICENSE file.
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
display: -webkit-box; display: -webkit-box;
padding: 1px 0; padding: 1px 0;
position: relative;
}
.timeline-track-close-button {
left: 0px;
position: absolute;
top: 0px;
}
.timeline-track-button {
color: rgba(0,0,0,0.1);
font-size: 10px;
height: 12px;
text-align: center;
width: 12px;
}
.timeline-track-button:hover {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 25%;
box-shadow: 0 0 .05em rgba(0,0,0,0.4);
color: rgba(0,0,0,0.5);
} }
.timeline-thread-track:not(:first-child) { .timeline-thread-track:not(:first-child) {
...@@ -61,4 +87,5 @@ found in the LICENSE file. ...@@ -61,4 +87,5 @@ found in the LICENSE file.
.timeline-counter-track { .timeline-counter-track {
height: 30px; height: 30px;
position: relative;
} }
...@@ -396,7 +396,7 @@ cr.define('tracing', function() { ...@@ -396,7 +396,7 @@ cr.define('tracing', function() {
threads.forEach(function(thread) { threads.forEach(function(thread) {
var track = new tracing.TimelineThreadTrack(); var track = new tracing.TimelineThreadTrack();
track.heading = thread.userFriendlyName + ':'; track.heading = thread.userFriendlyName + ':';
track.tooltip = thread.userFriendlyDetials; track.tooltip = thread.userFriendlyDetails;
track.headingWidth = maxHeadingWidth; track.headingWidth = maxHeadingWidth;
track.viewport = this.viewport_; track.viewport = this.viewport_;
track.thread = thread; track.thread = thread;
...@@ -661,16 +661,17 @@ cr.define('tracing', function() { ...@@ -661,16 +661,17 @@ cr.define('tracing', function() {
}, },
onMouseDown_: function(e) { onMouseDown_: function(e) {
rect = this.tracks_.getClientRects()[0]; var canv = this.firstCanvas;
var rect = this.tracks_.getClientRects()[0];
var inside = rect && var inside = rect &&
e.clientX >= rect.left && e.clientX >= rect.left &&
e.clientX < rect.right && e.clientX < rect.right &&
e.clientY >= rect.top && e.clientY >= rect.top &&
e.clientY < rect.bottom; e.clientY < rect.bottom &&
e.x >= canv.offsetLeft;
if (!inside) if (!inside)
return; return;
var canv = this.firstCanvas;
var pos = { var pos = {
x: e.clientX - canv.offsetLeft, x: e.clientX - canv.offsetLeft,
y: e.clientY - canv.offsetTop y: e.clientY - canv.offsetTop
...@@ -743,6 +744,10 @@ cr.define('tracing', function() { ...@@ -743,6 +744,10 @@ cr.define('tracing', function() {
}, },
onDblClick_: function(e) { onDblClick_: function(e) {
var canv = this.firstCanvas;
if (e.x < canv.offsetLeft)
return;
var scale = 4; var scale = 4;
if (e.shiftKey) if (e.shiftKey)
scale = 1 / scale; scale = 1 / scale;
......
...@@ -18,44 +18,47 @@ found in the LICENSE file. ...@@ -18,44 +18,47 @@ found in the LICENSE file.
</head> </head>
<body> <body>
<script> <script>
'use strict';
var TimelineCpu = tracing.TimelineCpu; var TimelineCpu = tracing.TimelineCpu;
var TimelineSlice = tracing.TimelineSlice; var TimelineSlice = tracing.TimelineSlice;
var TimelineThreadSlice = tracing.TimelineThreadSlice;
var TimelineProcess = tracing.TimelineProcess; var TimelineProcess = tracing.TimelineProcess;
var TimelineThread = tracing.TimelineThread; var TimelineThread = tracing.TimelineThread;
var TimelineModel = tracing.TimelineModel; var TimelineModel = tracing.TimelineModel;
var TimelineAsyncSlice = tracing.TimelineAsyncSlice;
var TimelineAsyncSliceGroup = tracing.TimelineAsyncSliceGroup;
// Helper function to create a slice.
function newAsyncSlice(start, duration, startThread, endThread) {
var s = new TimelineAsyncSlice('a', 0, start);
s.duration = duration;
s.startThread = startThread;
s.endThread = endThread;
return s;
}
function testThreadBounds_Empty() { function testThreadBounds_Empty() {
var t = new TimelineThread(undefined, 1); var t = new TimelineThread(new TimelineProcess(7), 1);
t.updateBounds(); t.updateBounds();
assertEquals(undefined, t.minTimestamp); assertEquals(undefined, t.minTimestamp);
assertEquals(undefined, t.maxTimestamp); assertEquals(undefined, t.maxTimestamp);
} }
function testThreadBounds_SubRow() { function testThreadBounds_SubRow() {
var t = new TimelineThread(undefined, 1); var t = new TimelineThread(new TimelineProcess(7), 1);
t.subRows[0].push(new TimelineSlice('a', 0, 1, {}, 3)); t.subRows[0].push(new TimelineThreadSlice('a', 0, 1, {}, 3));
t.updateBounds(); t.updateBounds();
assertEquals(1, t.minTimestamp); assertEquals(1, t.minTimestamp);
assertEquals(4, t.maxTimestamp); assertEquals(4, t.maxTimestamp);
} }
function testThreadBounds_NestedSubrow() { function testThreadBounds_AsyncSliceGroup() {
var t = new TimelineThread(undefined, 1); var t = new TimelineThread(new TimelineProcess(7), 1);
t.nonNestedSubRows.push([]); t.subRows[0].push(new TimelineThreadSlice('a', 0, 1, {}, 3));
t.nonNestedSubRows[0].push(new TimelineSlice('a', 0, 1, {}, 3)); t.asyncSlices.push(newAsyncSlice(0.1, 5, t, t));
t.updateBounds(); t.updateBounds();
assertEquals(1, t.minTimestamp); assertEquals(0.1, t.minTimestamp);
assertEquals(4, t.maxTimestamp); assertEquals(5.1, t.maxTimestamp);
}
function testThreadBounds_SubRowAndNonNestedSubRow() {
var t = new TimelineThread(undefined, 1);
t.subRows[0].push(new TimelineSlice('a', 0, 0.5, {}, 3));
t.nonNestedSubRows.push([]);
t.nonNestedSubRows[0].push(new TimelineSlice('b', 0, 1, {}, 4.5));
t.updateBounds();
assertEquals(0.5, t.minTimestamp);
assertEquals(5.5, t.maxTimestamp);
} }
function testModelBounds_EmptyModel() { function testModelBounds_EmptyModel() {
...@@ -73,10 +76,10 @@ function testModelBounds_OneEmptyThread() { ...@@ -73,10 +76,10 @@ function testModelBounds_OneEmptyThread() {
assertEquals(undefined, m.maxTimestamp); assertEquals(undefined, m.maxTimestamp);
} }
function testModelBounds_OneThrad() { function testModelBounds_OneThread() {
var m = new TimelineModel(); var m = new TimelineModel();
var t = m.getOrCreateProcess(1).getOrCreateThread(1); var t = m.getOrCreateProcess(1).getOrCreateThread(1);
t.subRows[0].push(new TimelineSlice('a', 0, 1, {}, 3)); t.subRows[0].push(new TimelineThreadSlice('a', 0, 1, {}, 3));
m.updateBounds(); m.updateBounds();
assertEquals(1, m.minTimestamp); assertEquals(1, m.minTimestamp);
assertEquals(4, m.maxTimestamp); assertEquals(4, m.maxTimestamp);
...@@ -85,7 +88,7 @@ function testModelBounds_OneThrad() { ...@@ -85,7 +88,7 @@ function testModelBounds_OneThrad() {
function testModelBounds_OneThreadAndOneEmptyThread() { function testModelBounds_OneThreadAndOneEmptyThread() {
var m = new TimelineModel(); var m = new TimelineModel();
var t1 = m.getOrCreateProcess(1).getOrCreateThread(1); var t1 = m.getOrCreateProcess(1).getOrCreateThread(1);
t1.subRows[0].push(new TimelineSlice('a', 0, 1, {}, 3)); t1.subRows[0].push(new TimelineThreadSlice('a', 0, 1, {}, 3));
var t2 = m.getOrCreateProcess(1).getOrCreateThread(1); var t2 = m.getOrCreateProcess(1).getOrCreateThread(1);
m.updateBounds(); m.updateBounds();
assertEquals(1, m.minTimestamp); assertEquals(1, m.minTimestamp);
...@@ -99,7 +102,6 @@ function testCpuBounds_Empty() { ...@@ -99,7 +102,6 @@ function testCpuBounds_Empty() {
assertEquals(undefined, cpu.maxTimestamp); assertEquals(undefined, cpu.maxTimestamp);
} }
function testCpuBounds_OneSlice() { function testCpuBounds_OneSlice() {
var cpu = new TimelineCpu(undefined, 1); var cpu = new TimelineCpu(undefined, 1);
cpu.slices.push(new TimelineSlice('a', 0, 1, {}, 3)); cpu.slices.push(new TimelineSlice('a', 0, 1, {}, 3));
...@@ -109,16 +111,120 @@ function testCpuBounds_OneSlice() { ...@@ -109,16 +111,120 @@ function testCpuBounds_OneSlice() {
} }
function testModelBounds_OneCpu() { function testModelBounds_OneCpu() {
var m = new TimelineModel();
var cpu = m.getOrCreateCpu(1);
cpu.slices.push(new TimelineSlice('a', 0, 1, {}, 3));
m.updateBounds();
assertEquals(1, m.minTimestamp);
assertEquals(4, m.maxTimestamp);
} }
function testModelBounds_OneCpuOneThread() { function testModelBounds_OneCpuOneThread() {
var m = new TimelineModel();
var cpu = m.getOrCreateCpu(1);
cpu.slices.push(new TimelineSlice('a', 0, 1, {}, 3));
var t = m.getOrCreateProcess(1).getOrCreateThread(1);
t.subRows[0].push(new TimelineThreadSlice('a', 0, 1, {}, 4));
m.updateBounds();
assertEquals(1, m.minTimestamp);
assertEquals(5, m.maxTimestamp);
}
function testPTIDFromPidAndTid() {
assertEquals('1:2', TimelineThread.getPTIDFromPidAndTid(1, 2));
}
function testAsyncSliceGroupBounds_Empty() {
var g = new TimelineAsyncSliceGroup(name);
g.updateBounds();
assertEquals(undefined, g.minTimestamp);
assertEquals(undefined, g.maxTimestamp);
}
function testAsyncSliceGroupBounds_Basic() {
var p1 = new TimelineProcess(1);
var t1 = new TimelineThread(p1, 1);
var g = new TimelineAsyncSliceGroup('a');
g.push(newAsyncSlice(0, 1, t1, t1));
g.push(newAsyncSlice(1, 1.5, t1, t1));
assertEquals(2, g.length);
g.updateBounds();
assertEquals(0, g.minTimestamp);
assertEquals(2.5, g.maxTimestamp);
}
function testAsyncSliceGroup_rebuildSubRows_twoNonOverlappingSlices() {
var p1 = new TimelineProcess(1);
var t1 = new TimelineThread(p1, 1);
var g = new TimelineAsyncSliceGroup('a');
g.slices.push(newAsyncSlice(0, 1, t1, t1));
g.slices.push(newAsyncSlice(1, 1, t1, t1));
assertEquals(1, g.subRows.length);
assertEquals(2, g.subRows[0].length);
assertEquals(g.slices[0], g.subRows[0][0]);
assertEquals(g.slices[1], g.subRows[0][1]);
}
function testAsyncSliceGroup_rebuildSubRows_twoOverlappingSlices() {
var p1 = new TimelineProcess(1);
var t1 = new TimelineThread(p1, 1);
var g = new TimelineAsyncSliceGroup('a');
g.slices.push(newAsyncSlice(0, 1, t1, t1));
g.slices.push(newAsyncSlice(0, 1.5, t1, t1));
g.updateBounds();
assertEquals(2, g.subRows.length);
assertEquals(1, g.subRows[0].length);
assertEquals(g.slices[0], g.subRows[0][0]);
assertEquals(1, g.subRows[1].length);
assertEquals(g.slices[1], g.subRows[1][0]);
}
function testAsyncSliceGroup_rebuildSubRows_threePartlyOverlappingSlices() {
var p1 = new TimelineProcess(1);
var t1 = new TimelineThread(p1, 1);
var g = new TimelineAsyncSliceGroup('a');
g.slices.push(newAsyncSlice(0, 1, t1, t1));
g.slices.push(newAsyncSlice(0, 1.5, t1, t1));
g.slices.push(newAsyncSlice(1, 1.5, t1, t1));
g.updateBounds();
assertEquals(2, g.subRows.length);
assertEquals(2, g.subRows[0].length);
assertEquals(g.slices[0], g.subRows[0][0]);
assertEquals(g.slices[2], g.subRows[0][1]);
assertEquals(1, g.subRows[1].length);
assertEquals(g.slices[1], g.subRows[1][0]);
}
function testAsyncSliceGroup_computeSubGroups_twoThreadSpecificSlices() {
var p1 = new TimelineProcess(1);
var t1 = new TimelineThread(p1, 1);
var t2 = new TimelineThread(p1, 2);
var g = new TimelineAsyncSliceGroup('a');
g.slices.push(newAsyncSlice(0, 1, t1, t1));
g.slices.push(newAsyncSlice(0, 1, t2, t2));
var subGroups = g.computeSubGroups();
assertEquals(2, subGroups.length);
assertEquals(g.name, subGroups[0].name);
assertEquals(1, subGroups[0].slices.length);
assertEquals(g.slices[0], subGroups[0].slices[0]);
assertEquals(g.name, subGroups[1].name);
assertEquals(1, subGroups[1].slices.length);
assertEquals(g.slices[1], subGroups[1].slices[0]);
} }
function testModelCanImportEmpty() { function testModelCanImportEmpty() {
var m; var m;
m = new TimelineModel([]); m = new TimelineModel([]);
m = new TimelineModel(""); m = new TimelineModel('');
} }
</script> </script>
</body> </body>
......
<!DOCTYPE HTML> <!DOCTYPE HTML>
<html> <html>
<!-- <!--
Copyright (c) 2011 The Chromium Authors. All rights reserved. Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be 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.
--> -->
...@@ -35,9 +35,6 @@ found in the LICENSE file. ...@@ -35,9 +35,6 @@ found in the LICENSE file.
<div class="timeline-test" src="./tests/instance_counters.json"> <div class="timeline-test" src="./tests/instance_counters.json">
</div> </div>
<div class="timeline-test" src="./tests/nonnested_trace.json">
</div>
<div class="timeline-test" src="./tests/tall_trace.json"> <div class="timeline-test" src="./tests/tall_trace.json">
</div> </div>
...@@ -50,7 +47,7 @@ found in the LICENSE file. ...@@ -50,7 +47,7 @@ found in the LICENSE file.
<div class="timeline-test" src="./tests/main_thread_has_unclosed_slices.json"> <div class="timeline-test" src="./tests/main_thread_has_unclosed_slices.json">
</div> </div>
<div class="timeline-test" src="./tests/linux_perf_simple.txt"> <div class="timeline-test" src="./tests/async_begin_end.json">
</div> </div>
<script> <script>
......
...@@ -111,6 +111,17 @@ cr.define('tracing', function() { ...@@ -111,6 +111,17 @@ cr.define('tracing', function() {
} }
}; };
function addCloseButtonElement(el) {
var closeEl = document.createElement('div');
closeEl.classList.add('timeline-track-button');
closeEl.classList.add('timeline-track-close-button');
closeEl.textContent = String.fromCharCode(215); // &times;
closeEl.addEventListener('click', function() {
el.style.display = 'None';
});
el.appendChild(closeEl);
}
/** /**
* Visualizes a TimelineThread using a series of of TimelineSliceTracks. * Visualizes a TimelineThread using a series of of TimelineSliceTracks.
* @constructor * @constructor
...@@ -181,12 +192,18 @@ cr.define('tracing', function() { ...@@ -181,12 +192,18 @@ cr.define('tracing', function() {
track.height = '4px'; track.height = '4px';
} }
for (var srI = 0; srI < this.thread_.nonNestedSubRows.length; ++srI) { if (this.thread_.asyncSlices.length) {
this.addTrack_(this.thread_.nonNestedSubRows[srI]); var subRows = this.thread_.asyncSlices.subRows;
for (var srI = 0; srI < subRows.length; srI++) {
var track = this.addTrack_(subRows[srI]);
track.asyncStyle = true;
} }
for (var srI = 0; srI < this.thread_.subRows.length; ++srI) { }
for (var srI = 0; srI < this.thread_.subRows.length; srI++) {
this.addTrack_(this.thread_.subRows[srI]); this.addTrack_(this.thread_.subRows[srI]);
} }
if (this.tracks_.length > 0) { if (this.tracks_.length > 0) {
if (this.thread_.cpuSlices) { if (this.thread_.cpuSlices) {
this.tracks_[1].heading = this.heading_; this.tracks_[1].heading = this.heading_;
...@@ -197,6 +214,7 @@ cr.define('tracing', function() { ...@@ -197,6 +214,7 @@ cr.define('tracing', function() {
} }
} }
} }
addCloseButtonElement(this);
} }
}; };
...@@ -264,6 +282,7 @@ cr.define('tracing', function() { ...@@ -264,6 +282,7 @@ cr.define('tracing', function() {
this.tracks_[0].heading = this.heading_; this.tracks_[0].heading = this.heading_;
this.tracks_[0].tooltip = this.tooltip_; this.tracks_[0].tooltip = this.tooltip_;
} }
addCloseButtonElement(this);
} }
}; };
...@@ -400,7 +419,12 @@ cr.define('tracing', function() { ...@@ -400,7 +419,12 @@ cr.define('tracing', function() {
elidedDict = {}; elidedDict = {};
elidedTitleCacheDict[title] = elidedDict; elidedTitleCacheDict[title] = elidedDict;
} }
var stringWidthPair = elidedDict[sliceDuration]; var elidedDictForPixWidth = elidedDict[pixWidth];
if (!elidedDictForPixWidth) {
elidedDict[pixWidth] = {};
elidedDictForPixWidth = elidedDict[pixWidth];
}
var stringWidthPair = elidedDictForPixWidth[sliceDuration];
if (stringWidthPair === undefined) { if (stringWidthPair === undefined) {
var newtitle = title; var newtitle = title;
var elided = false; var elided = false;
...@@ -413,10 +437,10 @@ cr.define('tracing', function() { ...@@ -413,10 +437,10 @@ cr.define('tracing', function() {
stringWidthPair = new ElidedStringWidthPair( stringWidthPair = new ElidedStringWidthPair(
newtitle, newtitle,
track.labelWidth(newtitle)); track.labelWidth(newtitle));
elidedDict[sliceDuration] = stringWidthPair; elidedDictForPixWidth[sliceDuration] = stringWidthPair;
} }
return stringWidthPair; return stringWidthPair;
}, }
}; };
/** /**
...@@ -443,6 +467,16 @@ cr.define('tracing', function() { ...@@ -443,6 +467,16 @@ cr.define('tracing', function() {
decorate: function() { decorate: function() {
this.classList.add('timeline-slice-track'); this.classList.add('timeline-slice-track');
this.elidedTitleCache = new ElidedTitleCache(); this.elidedTitleCache = new ElidedTitleCache();
this.asyncStyle_ = false;
},
get asyncStyle() {
return this.asyncStyle_;
},
set asyncStyle(v) {
this.asyncStyle_ = !!v;
this.invalidate();
}, },
get slices() { get slices() {
...@@ -503,6 +537,8 @@ cr.define('tracing', function() { ...@@ -503,6 +537,8 @@ cr.define('tracing', function() {
vp.applyTransformToCanavs(ctx); vp.applyTransformToCanavs(ctx);
// Slices. // Slices.
if (this.asyncStyle_)
ctx.globalAlpha = 0.25;
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);
...@@ -691,7 +727,7 @@ cr.define('tracing', function() { ...@@ -691,7 +727,7 @@ cr.define('tracing', function() {
const logOf10 = Math.log(10); const logOf10 = Math.log(10);
function log10(x) { function log10(x) {
return Math.log(x) / logOf10;; return Math.log(x) / logOf10;
} }
TimelineViewportTrack.prototype = { TimelineViewportTrack.prototype = {
...@@ -729,10 +765,10 @@ cr.define('tracing', function() { ...@@ -729,10 +765,10 @@ cr.define('tracing', function() {
// exceeds the ideal mark distance. // exceeds the ideal mark distance.
var divisors = [10, 5, 2, 1]; var divisors = [10, 5, 2, 1];
for (var i = 0; i < divisors.length; ++i) { for (var i = 0; i < divisors.length; ++i) {
var tightenedGuess = conservativeGuess / divisors[i] var tightenedGuess = conservativeGuess / divisors[i];
if (vp.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix) if (vp.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix)
continue; continue;
majorMarkDistanceWorld = conservativeGuess / divisors[i-1]; majorMarkDistanceWorld = conservativeGuess / divisors[i - 1];
break; break;
} }
if (majorMarkDistanceWorld < 100) { if (majorMarkDistanceWorld < 100) {
...@@ -841,6 +877,7 @@ cr.define('tracing', function() { ...@@ -841,6 +877,7 @@ cr.define('tracing', function() {
decorate: function() { decorate: function() {
this.classList.add('timeline-counter-track'); this.classList.add('timeline-counter-track');
addCloseButtonElement(this);
}, },
get counter() { get counter() {
......
...@@ -40,15 +40,30 @@ found in the LICENSE file. ...@@ -40,15 +40,30 @@ found in the LICENSE file.
<script> <script>
</script> </script>
<script> <script>
var TimelineAsyncSlice = tracing.TimelineAsyncSlice;
var TimelineAsyncSliceGroup = tracing.TimelineAsyncSliceGroup;
var TimelineCounter = tracing.TimelineCounter; var TimelineCounter = tracing.TimelineCounter;
var TimelineCounterTrack = tracing.TimelineCounterTrack; var TimelineCounterTrack = tracing.TimelineCounterTrack;
var TimelineCpu = tracing.TimelineCpu; var TimelineCpu = tracing.TimelineCpu;
var TimelineCpuTrack = tracing.TimelineCpuTrack; var TimelineCpuTrack = tracing.TimelineCpuTrack;
var TimelineProcess = tracing.TimelineProcess;
var TimelineSliceTrack = tracing.TimelineSliceTrack; var TimelineSliceTrack = tracing.TimelineSliceTrack;
var TimelineSlice = tracing.TimelineSlice; var TimelineSlice = tracing.TimelineSlice;
var TimelineThread = tracing.TimelineThread;
var TimelineThreadSlice = tracing.TimelineThreadSlice;
var TimelineThreadTrack = tracing.TimelineThreadTrack;
var TimelineViewport = tracing.TimelineViewport; var TimelineViewport = tracing.TimelineViewport;
var testDivs = {}; var testDivs = {};
// Helper function to create a slice.
function newAsyncSlice(start, duration, startThread, endThread) {
var s = new TimelineAsyncSlice('a', 0, start);
s.duration = duration;
s.startThread = startThread;
s.endThread = endThread;
return s;
}
function getTestDiv(name) { function getTestDiv(name) {
if (!testDivs[name]) { if (!testDivs[name]) {
testDivs[name] = document.createElement('div'); testDivs[name] = document.createElement('div');
...@@ -74,6 +89,23 @@ found in the LICENSE file. ...@@ -74,6 +89,23 @@ found in the LICENSE file.
track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end)); track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end));
} }
function testBasicSlicesWithAsyncFlag() {
var testEl = getTestDiv('testBasicSlicesWithAsyncFlag');
var track = TimelineSliceTrack();
testEl.appendChild(track);
track.asyncStyle = true;
track.heading = 'testBasicSlices+AsyncFlag';
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() { function testShrinkingSliceSizes() {
var testEl = getTestDiv('testShrinkingSliceSizes'); var testEl = getTestDiv('testShrinkingSliceSizes');
var track = TimelineSliceTrack(); var track = TimelineSliceTrack();
...@@ -186,8 +218,8 @@ found in the LICENSE file. ...@@ -186,8 +218,8 @@ found in the LICENSE file.
/* You'll need visual inspection to test eliding with this one. */ /* You'll need visual inspection to test eliding with this one. */
function testElideVisualInspection() { function testElideVisualInspection() {
var optDicts = [ { trackName: 'elideOff', elide: false }, var optDicts = [{ trackName: 'elideOff', elide: false },
{ trackName: 'elideOn', elide: true } ]; { trackName: 'elideOn', elide: true }];
for (dictIndex in optDicts) { for (dictIndex in optDicts) {
dict = optDicts[dictIndex]; dict = optDicts[dictIndex];
var testEl = getTestDiv(dict.trackName); var testEl = getTestDiv(dict.trackName);
...@@ -227,7 +259,7 @@ found in the LICENSE file. ...@@ -227,7 +259,7 @@ found in the LICENSE file.
track.slices = [ track.slices = [
// title, colorId, start, args, opt_duration // title, colorId, start, args, opt_duration
new TimelineSlice(bigtitle, 0, 1, {}, 1), new TimelineSlice(bigtitle, 0, 1, {}, 1),
new TimelineSlice(smalltitle, 1, 2, {}, 1), new TimelineSlice(smalltitle, 1, 2, {}, 1)
]; ];
track.viewport = new TimelineViewport(testEl); track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0, track.viewport.setPanAndScale(0,
...@@ -261,7 +293,65 @@ found in the LICENSE file. ...@@ -261,7 +293,65 @@ found in the LICENSE file.
assertTrue(stringWidthPair.string.length < superBigTitle.length); assertTrue(stringWidthPair.string.length < superBigTitle.length);
// And elided text ends with ... // And elided text ends with ...
var len = stringWidthPair.string.length; var len = stringWidthPair.string.length;
assertEquals('...', stringWidthPair.string.substring(len-3, len)); assertEquals('...', stringWidthPair.string.substring(len - 3, len));
}
function testTimelineThreadTrackWithRegularSlices() {
var testEl = getTestDiv('testTimelineThreadTrackWithRegularSlices');
var track = TimelineThreadTrack();
testEl.appendChild(track);
track.heading = 'testTimelineThreadTrackWithRegularSlices';
var thread = new TimelineThread(new TimelineProcess(7), 1);
thread.subRows = [
[
new TimelineThreadSlice('a', 0, 1, {}, 1),
new TimelineThreadSlice('b', 1, 2.1, {}, 4.8),
new TimelineThreadSlice('b', 1, 7, {}, 0.5),
new TimelineThreadSlice('c', 2, 7.6, {}, 0.4)
],
[
new TimelineThreadSlice('d', 3, 1.1, {}, 0.8),
new TimelineThreadSlice('e', 4, 7.1, {}, 0.3)
]
];
thread.updateBounds();
track.heading = 'thread regular';
track.headingWidth = '150px';
track.toolTip = thread.userFriendlyDetails + ':';
track.thread = thread;
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth / (1.1 * (thread.maxTimestamp - thread.minTimestamp));
}
function testTimelineThreadTrackWithRegularAndAsyncSlices() {
var testEl = getTestDiv('testTimelineThreadTrackWithAsyncSlices');
var track = TimelineThreadTrack();
testEl.appendChild(track);
var thread = new TimelineThread(new TimelineProcess(7), 1);
thread.subRows = [
[
new TimelineThreadSlice('a', 0, 1, {}, 1),
new TimelineThreadSlice('b', 1, 2.1, {}, 4.8),
new TimelineThreadSlice('b', 1, 7, {}, 0.5),
new TimelineThreadSlice('c', 2, 7.6, {}, 0.4)
],
[
new TimelineThreadSlice('d', 3, 1.1, {}, 0.8),
new TimelineThreadSlice('e', 4, 7.1, {}, 0.3)
]
];
thread.asyncSlices.push(newAsyncSlice(1.2, 7.2 - 1.2, thread, thread));
thread.asyncSlices.push(newAsyncSlice(1.3, 7.3 - 1.3, thread, thread));
thread.updateBounds();
track.heading = 'thread regular + async';
track.headingWidth = '150px';
track.toolTip = thread.userFriendlyDetails + ':';
track.thread = thread;
track.viewport = new TimelineViewport(testEl);
track.viewport.setPanAndScale(0,
track.clientWidth /
(1.1 * (thread.maxTimestamp - thread.minTimestamp)));
} }
</script> </script>
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
cr.define('tracing', function() { cr.define('tracing', function() {
function ThreadState(tid) { function ThreadState(tid) {
this.openSlices = []; this.openSlices = [];
this.openNonNestedSlices = {};
} }
function TraceEventImporter(model, eventData) { function TraceEventImporter(model, eventData) {
...@@ -49,6 +48,9 @@ cr.define('tracing', function() { ...@@ -49,6 +48,9 @@ cr.define('tracing', function() {
// PTID. A ptid is a pid and tid joined together x:y fashion, eg // PTID. A ptid is a pid and tid joined together x:y fashion, eg
// 1024:130. The ptid is a unique key for a thread in the trace. // 1024:130. The ptid is a unique key for a thread in the trace.
this.threadStateByPTID_ = {}; this.threadStateByPTID_ = {};
// Async events need to be processed durign finalizeEvents
this.allAsyncEvents_ = [];
} }
/** /**
...@@ -85,11 +87,11 @@ cr.define('tracing', function() { ...@@ -85,11 +87,11 @@ cr.define('tracing', function() {
* @param {ThreadState} state Thread state (holds slices). * @param {ThreadState} state Thread state (holds slices).
* @param {Object} event The current trace event. * @param {Object} event The current trace event.
*/ */
processBegin: function(index, state, event) { processBeginEvent: function(index, state, event) {
var colorId = tracing.getStringColorId(event.name); var colorId = tracing.getStringColorId(event.name);
var slice = var slice =
{ index: index, { index: index,
slice: new tracing.TimelineSlice(event.name, colorId, slice: new tracing.TimelineThreadSlice(event.name, colorId,
event.ts / 1000, event.ts / 1000,
event.args) }; event.args) };
...@@ -97,15 +99,11 @@ cr.define('tracing', function() { ...@@ -97,15 +99,11 @@ cr.define('tracing', function() {
slice.slice.startInUserTime = event.uts / 1000; slice.slice.startInUserTime = event.uts / 1000;
if (event.args['ui-nest'] === '0') { if (event.args['ui-nest'] === '0') {
var sliceID = event.name; this.model_.importErrors.push('ui-nest no longer supported.');
for (var x in event.args) return;
sliceID += ';' + event.args[x];
if (state.openNonNestedSlices[sliceID])
this.model_.importErrors.push('Event ' + sliceID + ' already open.');
state.openNonNestedSlices[sliceID] = slice;
} else {
state.openSlices.push(slice);
} }
state.openSlices.push(slice);
}, },
/** /**
...@@ -113,26 +111,11 @@ cr.define('tracing', function() { ...@@ -113,26 +111,11 @@ cr.define('tracing', function() {
* @param {ThreadState} state Thread state (holds slices). * @param {ThreadState} state Thread state (holds slices).
* @param {Object} event The current trace event. * @param {Object} event The current trace event.
*/ */
processEnd: function(state, event) { processEndEvent: function(state, event) {
if (event.args['ui-nest'] === '0') { if (event.args['ui-nest'] === '0') {
var sliceID = event.name; this.model_.importErrors.push('ui-nest no longer supported.');
for (var x in event.args)
sliceID += ';' + event.args[x];
var slice = state.openNonNestedSlices[sliceID];
if (!slice)
return; return;
slice.slice.duration = (event.ts / 1000) - slice.slice.start; }
if (event.uts)
slice.durationInUserTime = (event.uts / 1000) -
slice.slice.startInUserTime;
// Store the slice in a non-nested subrow.
var thread =
this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
thread.addNonNestedSlice(slice.slice);
delete state.openNonNestedSlices[name];
} else {
if (state.openSlices.length == 0) { if (state.openSlices.length == 0) {
// Ignore E events that are unmatched. // Ignore E events that are unmatched.
return; return;
...@@ -153,7 +136,18 @@ cr.define('tracing', function() { ...@@ -153,7 +136,18 @@ cr.define('tracing', function() {
var parentSlice = state.openSlices[state.openSlices.length - 1]; var parentSlice = state.openSlices[state.openSlices.length - 1];
parentSlice.slice.subSlices.push(slice); parentSlice.slice.subSlices.push(slice);
} }
} },
/**
* Helper to process an 'async finish' event, which will close an open slice
* on a TimelineAsyncSliceGroup object.
**/
processAsyncEvent: function(index, state, event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allAsyncEvents_.push({
event: event,
thread: thread});
}, },
/** /**
...@@ -170,8 +164,8 @@ cr.define('tracing', function() { ...@@ -170,8 +164,8 @@ cr.define('tracing', function() {
// The model's max value in the trace is wrong at this point if there are // The model's max value in the trace is wrong at this point if there are
// un-closed events. To close those events, we need the true global max // un-closed events. To close those events, we need the true global max
// value. To compute this, build a list of timestamps that weren't // value. To compute this, build a list of timestamps that weren't
// included in the max calculation, then compute the real maximum based // included in the max calculation, then compute the real maximum based on
// on that. // that.
var openTimestamps = []; var openTimestamps = [];
for (var ptid in this.threadStateByPTID_) { for (var ptid in this.threadStateByPTID_) {
var state = this.threadStateByPTID_[ptid]; var state = this.threadStateByPTID_[ptid];
...@@ -228,7 +222,7 @@ cr.define('tracing', function() { ...@@ -228,7 +222,7 @@ cr.define('tracing', function() {
* Helper that creates and adds samples to a TimelineCounter object based on * Helper that creates and adds samples to a TimelineCounter object based on
* 'C' phase events. * 'C' phase events.
*/ */
processCounter: function(event) { processCounterEvent: function(event) {
var ctr_name; var ctr_name;
if (event.id !== undefined) if (event.id !== undefined)
ctr_name = event.name + '[' + event.id + ']'; ctr_name = event.name + '[' + event.id + ']';
...@@ -272,25 +266,33 @@ cr.define('tracing', function() { ...@@ -272,25 +266,33 @@ cr.define('tracing', function() {
importEvents: function() { importEvents: function() {
// Walk through events // Walk through events
var events = this.events_; var events = this.events_;
// Some events cannot be handled until we have done a first pass over the
// data set. So, accumulate them into a temporary data structure.
var second_pass_events = [];
for (var eI = 0; eI < events.length; eI++) { for (var eI = 0; eI < events.length; eI++) {
var event = events[eI]; var event = events[eI];
var ptid = event.pid + ':' + event.tid; var ptid = tracing.TimelineThread.getPTIDFromPidAndTid(
event.pid, event.tid);
if (!(ptid in this.threadStateByPTID_)) if (!(ptid in this.threadStateByPTID_))
this.threadStateByPTID_[ptid] = new ThreadState(); this.threadStateByPTID_[ptid] = new ThreadState();
var state = this.threadStateByPTID_[ptid]; var state = this.threadStateByPTID_[ptid];
if (event.ph == 'B') { if (event.ph == 'B') {
this.processBegin(eI, state, event); this.processBeginEvent(eI, state, event);
} else if (event.ph == 'E') { } else if (event.ph == 'E') {
this.processEnd(state, event); this.processEndEvent(state, event);
} else if (event.ph == 'S') {
this.processAsyncEvent(eI, state, event);
} else if (event.ph == 'F') {
this.processAsyncEvent(eI, state, event);
} else if (event.ph == 'I') { } else if (event.ph == 'I') {
// Treat an Instant event as a duration 0 slice. // Treat an Instant event as a duration 0 slice.
// TimelineSliceTrack's redraw() knows how to handle this. // TimelineSliceTrack's redraw() knows how to handle this.
this.processBegin(eI, state, event); this.processBeginEvent(eI, state, event);
this.processEnd(state, event); this.processEndEvent(state, event);
} else if (event.ph == 'C') { } else if (event.ph == 'C') {
this.processCounter(event); this.processCounterEvent(event);
} else if (event.ph == 'M') { } else if (event.ph == 'M') {
if (event.name == 'thread_name') { if (event.name == 'thread_name') {
var thread = this.model_.getOrCreateProcess(event.pid) var thread = this.model_.getOrCreateProcess(event.pid)
...@@ -315,6 +317,88 @@ cr.define('tracing', function() { ...@@ -315,6 +317,88 @@ cr.define('tracing', function() {
} }
if (hasOpenSlices) if (hasOpenSlices)
this.autoCloseOpenSlices(); this.autoCloseOpenSlices();
},
/**
* Called by the TimelineModel after all other importers have imported their
* events. This function creates async slices for any async events we saw.
*/
finalizeImport: function() {
if (this.allAsyncEvents_.length == 0)
return;
this.allAsyncEvents_.sort(function(x, y) {
return x.event.ts - y.event.ts;
});
var startEventStatesByNameThenID = {};
var allAsyncEvents = this.allAsyncEvents_;
for (var i = 0; i < allAsyncEvents.length; i++) {
var asyncEventState = allAsyncEvents[i];
var event = asyncEventState.event;
var name = event.name;
if (name === undefined) {
this.model_.importErrors.push(
'Async events (ph: S or F) require an name parameter.');
continue;
}
var id = event.id;
if (id === undefined) {
this.model_.importErrors.push(
'Async events (ph: S or F) require an id parameter.');
continue;
}
if (event.ph == 'S') {
if (startEventStatesByNameThenID[name] === undefined)
startEventStatesByNameThenID[name] = {};
if (startEventStatesByNameThenID[name][id]) {
this.model_.importErrors.push(
'At ' + event.ts + ', an slice of the same id ' + id +
' was alrady open.');
continue;
}
startEventStatesByNameThenID[name][id] = asyncEventState;
} else {
if (startEventStatesByNameThenID[name] === undefined) {
this.model_.importErrors.push(
'At ' + event.ts + ', no slice named ' + name +
' was open.');
continue;
}
if (startEventStatesByNameThenID[name][id] === undefined) {
this.model_.importErrors.push(
'At ' + event.ts + ', no slice named ' + name +
' with id=' + id + ' was open.');
continue;
}
var startAsyncEventState = startEventStatesByNameThenID[name][id];
delete startEventStatesByNameThenID[name][id];
// Create a slice from startAsyncEventState to asyncEventState
var slice = new tracing.TimelineAsyncSlice(
name,
tracing.getStringColorId(name),
startAsyncEventState.event.ts / 1000);
slice.duration =
(event.ts / 1000) - (startAsyncEventState.event.ts / 1000);
slice.startThread = startAsyncEventState.thread;
slice.endThread = asyncEventState.thread;
slice.id = id;
if (startAsyncEventState.event.args)
slice.args = startAsyncEventState.event.args;
else
slice.args = {};
// Add it to the start-thread's asyncSlices.
slice.startThread.asyncSlices.push(slice);
}
}
} }
}; };
......
...@@ -23,7 +23,7 @@ found in the LICENSE file. ...@@ -23,7 +23,7 @@ found in the LICENSE file.
function testCanImportEmpty() { function testCanImportEmpty() {
self.assertFalse(tracing.TraceEventImporter.canImport([])); self.assertFalse(tracing.TraceEventImporter.canImport([]));
self.assertFalse(tracing.TraceEventImporter.canImport("")); self.assertFalse(tracing.TraceEventImporter.canImport(''));
} }
function testBasicSingleThreadNonnestedParsing() { function testBasicSingleThreadNonnestedParsing() {
...@@ -559,6 +559,33 @@ function testImportStringWithMissingCloseSquareBracketAndNewline() { ...@@ -559,6 +559,33 @@ function testImportStringWithMissingCloseSquareBracketAndNewline() {
assertEquals(1, m.numProcesses); assertEquals(1, m.numProcesses);
} }
function testStartFinishOneSliceOneThread() {
var events = [
// Time is intentionally out of order.
{name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53,
ph: 'F', id: 72},
{name: 'a', args: {}, pid: 52, ts: 524, cat: 'foo', tid: 53,
ph: 'S', id: 72, args: {foo: 'bar'}}
];
var m = new tracing.TimelineModel(events);
var t = m.processes[52].threads[53];
assertNotUndefined(t);
assertEquals(1, t.asyncSlices.slices.length);
assertEquals('a', t.asyncSlices.slices[0].title);
assertEquals(72, t.asyncSlices.slices[0].id);
assertEquals('bar', t.asyncSlices.slices[0].args.foo);
assertEquals(0, t.asyncSlices.slices[0].start);
assertAlmostEquals((60 - 24) / 1000, t.asyncSlices.slices[0].duration);
assertEquals(t, t.asyncSlices.slices[0].startThread);
assertEquals(t, t.asyncSlices.slices[0].endThread);
}
// TODO(nduca): one slice, two threads
// TODO(nduca): one slice, two pids
// TODO(nduca): one slice that doesnt end
// TODO(nduca): one slice that is missing a start
</script> </script>
</body> </body>
</html> </html>
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