diff options
author | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-21 00:06:53 +0000 |
---|---|---|
committer | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-21 00:06:53 +0000 |
commit | 77f93a0fee5d33d31c4a28443f576f38b845194a (patch) | |
tree | 89d657f62f066c1f4c74de63ff3c19b48dc7a404 /chrome | |
parent | b83fbfe820d7c5ae9e7df246e482db957f671004 (diff) | |
download | chromium_src-77f93a0fee5d33d31c4a28443f576f38b845194a.zip chromium_src-77f93a0fee5d33d31c4a28443f576f38b845194a.tar.gz chromium_src-77f93a0fee5d33d31c4a28443f576f38b845194a.tar.bz2 |
Find feature for tracing, at long last.
BUG=104567
Review URL: https://chromiumcodereview.appspot.com/10170001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@133308 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
19 files changed, 1007 insertions, 219 deletions
diff --git a/chrome/browser/resources/tracing/interactive_tests.html b/chrome/browser/resources/tracing/interactive_tests.html new file mode 100644 index 0000000..b40c47a --- /dev/null +++ b/chrome/browser/resources/tracing/interactive_tests.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<!-- +Copyright (c) 2012 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"> +<link rel="stylesheet" href="timeline_view.css"> +<link rel="stylesheet" href="overlay.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="linux_perf_importer.js"></script> +<script src="trace_event_importer.js"></script> +<script src="sorted_array_utils.js"></script> +<script src="measuring_stick.js"></script> +<script src="overlay.js"></script> +<script src="timeline.js"></script> +<script src="timeline_view.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/tall_trace.json"> + </div> + + <div class="timeline-test" src="./tests/big_trace.json"> + </div> + + <div class="timeline-test" src="./tests/huge_trace.json"> + </div> + + <div class="timeline-test" src="./tests/main_thread_has_unclosed_slices.json"> + </div> + + <div class="timeline-test" src="./tests/async_begin_end.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 timelineViewEl = document.createElement('div'); + cr.ui.decorate(timelineViewEl, tracing.TimelineView); + timelineViewEl.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; + function createModel(data) { + timelineViewEl.model = new tracing.TimelineModel(data); + if (!create_detached) + containerEl.appendChild(timelineViewEl); + } + if (create_detached) + containerEl.appendChild(timelineViewEl); + test_utils.getAsync(src, createModel); + } + + function onLoad() { + Array.prototype.forEach.call(document.querySelectorAll('.timeline-test'), + load); + } + + document.addEventListener('DOMContentLoaded', onLoad); + window.addEventListener('hashchange', onLoad); + </script> +</body> +</html> diff --git a/chrome/browser/resources/tracing/kernel_trace_viewer.html b/chrome/browser/resources/tracing/kernel_trace_viewer.html index 39482ef..5ff986b 100644 --- a/chrome/browser/resources/tracing/kernel_trace_viewer.html +++ b/chrome/browser/resources/tracing/kernel_trace_viewer.html @@ -18,6 +18,7 @@ found in the LICENSE file. <script src="trace_event_importer.js"></script> <script src="sorted_array_utils.js"></script> <script src="measuring_stick.js"></script> +<script src="overlay.js"></script> <script src="timeline.js"></script> <script src="timeline_track.js"></script> <script src="timeline_view.js"></script> diff --git a/chrome/browser/resources/tracing/linux_perf_importer_test.html b/chrome/browser/resources/tracing/linux_perf_importer_test.html index f2e7241..0ac958f 100644 --- a/chrome/browser/resources/tracing/linux_perf_importer_test.html +++ b/chrome/browser/resources/tracing/linux_perf_importer_test.html @@ -6,7 +6,7 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <head> -<title>Perf Importer tests</title> +<title>LinuxPerfImporter tests</title> <script src= "http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"> </script> diff --git a/chrome/browser/resources/tracing/overlay.js b/chrome/browser/resources/tracing/overlay.js index bcf3771..1a911a8 100644 --- a/chrome/browser/resources/tracing/overlay.js +++ b/chrome/browser/resources/tracing/overlay.js @@ -56,7 +56,12 @@ cr.define('tracing', function() { // Bring overlay into focus. overlay.tabIndex = 0; - overlay.focus(); + var focusElement = + overlay.querySelector('button, input, list, select, a'); + if (!focusElement) { + focusElement = overlay; + } + focusElement.focus(); // Listen to key and focus events to prevent focus from // leaving the overlay. diff --git a/chrome/browser/resources/tracing/overlay_test.html b/chrome/browser/resources/tracing/overlay_test.html index 7dc7c79..8747118 100644 --- a/chrome/browser/resources/tracing/overlay_test.html +++ b/chrome/browser/resources/tracing/overlay_test.html @@ -6,7 +6,7 @@ found in the LICENSE file. --> <html> <head> -<title></title> +<title>Overlay tests</title> <link rel="stylesheet" href="overlay.css"> <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script> <script src="../shared/js/cr.js"></script> diff --git a/chrome/browser/resources/tracing/profiling_view.css b/chrome/browser/resources/tracing/profiling_view.css index db1cd49..84cfe09 100644 --- a/chrome/browser/resources/tracing/profiling_view.css +++ b/chrome/browser/resources/tracing/profiling_view.css @@ -9,22 +9,6 @@ padding: 0; } -.profiling-view > .control { - border-bottom: 1px solid #555; - display: -webkit-box; -} - -.profiling-view > .control > span { - padding-left: 5px; - padding-right: 10px; -} - -.profiling-view > .control > button { - font-size: 75%; - height: 20px; - min-height: 10px; -} - .profiling-view > .container { -webkit-box-flex: 1; display: -webkit-box; diff --git a/chrome/browser/resources/tracing/profiling_view.js b/chrome/browser/resources/tracing/profiling_view.js index 2d2cf6c..1fdc791 100644 --- a/chrome/browser/resources/tracing/profiling_view.js +++ b/chrome/browser/resources/tracing/profiling_view.js @@ -5,8 +5,8 @@ 'use strict'; /** - * @fileoverview ProfilingView visualizes TRACE_EVENT events using the - * tracing.Timeline component. + * @fileoverview ProfilingView glues the TimelineView control to + * TracingController. */ cr.define('tracing', function() { /** @@ -27,13 +27,6 @@ cr.define('tracing', function() { this.classList.add('profiling-view'); // make the <list>/add/save/record element - this.controlDiv_ = document.createElement('div'); - this.controlDiv_.className = 'control'; - this.appendChild(this.controlDiv_); - - var tracingEl = document.createElement('span'); - tracingEl.textContent = 'Tracing: '; - this.recordBn_ = document.createElement('button'); this.recordBn_.className = 'record'; this.recordBn_.textContent = 'Record'; @@ -47,16 +40,6 @@ cr.define('tracing', function() { this.loadBn_.textContent = 'Load'; this.loadBn_.addEventListener('click', this.onLoad_.bind(this)); - this.container_ = document.createElement('div'); - this.container_.className = 'container'; - - this.timelineView_ = new tracing.TimelineView(); - - this.controlDiv_.appendChild(tracingEl); - this.controlDiv_.appendChild(this.recordBn_); - this.controlDiv_.appendChild(this.loadBn_); - this.controlDiv_.appendChild(this.saveBn_); - if (cr.isChromeOS) { this.systemTracingBn_ = document.createElement('input'); this.systemTracingBn_.type = 'checkbox'; @@ -66,12 +49,16 @@ cr.define('tracing', function() { systemTracingLabelEl.className = 'label'; systemTracingLabelEl.textContent = 'System events'; systemTracingLabelEl.appendChild(this.systemTracingBn_); - - this.controlDiv_.appendChild(systemTracingLabelEl); } - this.container_.appendChild(this.timelineView_); - this.appendChild(this.container_); + this.timelineView_ = new tracing.TimelineView(); + this.timelineView_.leftControls.appendChild(this.recordBn_); + this.timelineView_.leftControls.appendChild(this.saveBn_); + this.timelineView_.leftControls.appendChild(this.loadBn_); + if (cr.isChromeOS) + this.timelineView_.leftControls.appendChild(this.systemTracingBn_); + + this.appendChild(this.timelineView_); document.addEventListener('keypress', this.onKeypress_.bind(this)); diff --git a/chrome/browser/resources/tracing/profiling_view_test.html b/chrome/browser/resources/tracing/profiling_view_test.html index 3297c4e..18121be 100644 --- a/chrome/browser/resources/tracing/profiling_view_test.html +++ b/chrome/browser/resources/tracing/profiling_view_test.html @@ -6,15 +6,29 @@ 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> +<title>ProfilingView tests</title> <link rel="stylesheet" href="profiling_view.css"> +<link rel="stylesheet" href="timeline_view.css"> +<link rel="stylesheet" href="overlay.css"> +<link rel="stylesheet" href="timeline.css"> <link rel="stylesheet" href="../shared/css/tabs.css"> <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/event_target.js"></script> <script src="../shared/js/cr/ui.js"></script> <script src="../shared/js/cr/ui/tabs.js"></script> +<script src="overlay.js"></script> +<script src="measuring_stick.js"></script> <script src="profiling_view.js"></script> +<script src="timeline_view.js"></script> +<script src="timeline_model.js"></script> +<script src="linux_perf_importer.js"></script> +<script src="trace_event_importer.js"></script> +<script src="timeline.js"></script> +<script src="timeline_track.js"></script> +<script src="sorted_array_utils.js"></script> +<script src="fast_rect_renderer.js"></script> +<script src="test_utils.js"></script> <script> goog.require('goog.testing.jsunit'); </script> @@ -32,8 +46,8 @@ found in the LICENSE file. * Just enough of the TracingController to support the tests below. */ function FakeTracingController() { - } + FakeTracingController.prototype = { __proto__: cr.EventTarget.prototype, @@ -50,74 +64,33 @@ found in the LICENSE file. get systemTraceEvents() { if (!this.wasBeginTracingCalled) - return undefined; + return []; if (!this.wasBeginTracingCalledWithSystemTracingEnabled) - return undefined; + return []; return FakeTracingController.systemTraceTestData; } }; FakeTracingController.testData = [ - "hello", - "world" + {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}, + {name: 'b', args: {}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'b', args: {}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'} ]; FakeTracingController.systemTraceTestData = [ - "the kernel", - "says it wants", - "its memory back" - ]; - - /* - * Just enough of the TimelineModel to support the tests below. - */ - function FakeTimelineModel() { - - } - FakeTimelineModel.prototype = { - __proto__: Object.prototype, - - importEvents: function(eventData, - opt_zeroAndBoost, opt_additionalEventData) { - assertEquals(eventData, FakeTracingController.testData); - if (cr.isChromeOS) { - assertEquals(1, opt_additionalEventData.length); - assertEquals(opt_additionalEventData[0], FakeTracingController.systemTraceTestData) - } - }, - - something: function() { - }, - }; - - /* - * Just enough of the TimelineView to support the tests below. - */ - var FakeTimelineView = cr.ui.define('div'); - - FakeTimelineView.prototype = { - __proto__: HTMLDivElement.prototype, - - decorate: function() { - this.statusEl_ = document.createElement('span'); - this.appendChild(this.statusEl_); - this.refresh_(); - }, - - refresh_: function() { - var status; - if (this.timelineModel) - status = "timelineModel"; - else - status = "!timelineModel"; - this.statusEl_.textContent = status; - }, - }; - - /* Monkeypatch timeline model and view so ProfilingView - * instantiates them instead. - */ - tracing.TimelineModel = FakeTimelineModel; - tracing.TimelineView = FakeTimelineView; + 'systrace.sh-8170 [001] 15180.978813: sched_switch: ' + + 'prev_comm=systrace.sh prev_pid=8170 prev_prio=120 ' + + 'prev_state=x ==> next_comm=kworker/1:0 next_pid=7873 ' + + 'next_prio=120', + ' kworker/1:0-7873 [001] 15180.978836: sched_switch: ' + + 'prev_comm=kworker/1:0 prev_pid=7873 prev_prio=120 ' + + 'prev_state=S ==> next_comm=debugd next_pid=4404 next_prio=120', + ' debugd-4404 [001] 15180.979010: sched_switch: prev_comm=debugd ' + + 'prev_pid=4404 prev_prio=120 prev_state=S ==> ' + + 'next_comm=dbus-daemon next_pid=510 next_prio=120', + 'systrace.sh-8182 [000] 15186.203900: tracing_mark_write: ' + + 'trace_event_clock_sync: parent_ts=0.0' + ].join('\n'); /* This test just instantiates a ProflingView and adds it to the DOM * to help with non-unittest UI work. @@ -125,6 +98,7 @@ found in the LICENSE file. function testInstantiate() { var view = new tracing.ProfilingView(); view.tracingController = new FakeTracingController(); + view.focusElement = view; document.body.appendChild(view); } @@ -136,7 +110,7 @@ found in the LICENSE file. assertTrue(tracingController.wasBeginTracingCalled); assertEquals(cr.isChromeOS, tracingController.wasBeginTracingCalledWithSystemTracingEnabled); - + var e = new cr.Event('traceEnded'); var didRefresh = false; e.events = tracingController.traceEvents; diff --git a/chrome/browser/resources/tracing/test_utils.js b/chrome/browser/resources/tracing/test_utils.js index a8800f1..30dd5e6 100644 --- a/chrome/browser/resources/tracing/test_utils.js +++ b/chrome/browser/resources/tracing/test_utils.js @@ -38,7 +38,27 @@ cr.define('test_utils', function() { }; req.send(null); } + + function newAsyncSlice(start, duration, startThread, endThread) { + return newAsyncSliceNamed('a', start, duration, startThread, endThread); + } + + function newAsyncSliceNamed(name, start, duration, startThread, endThread) { + var s = new tracing.TimelineAsyncSlice(name, 0, start); + s.duration = duration; + s.startThread = startThread; + s.endThread = endThread; + var subSlice = new tracing.TimelineAsyncSlice(name, 0, start); + subSlice.duration = duration; + subSlice.startThread = startThread; + subSlice.endThread = endThread; + s.subSlices = [subSlice]; + return s; + } + return { - getAsync: getAsync + getAsync: getAsync, + newAsyncSlice: newAsyncSlice, + newAsyncSliceNamed: newAsyncSliceNamed }; }); diff --git a/chrome/browser/resources/tracing/tests.html b/chrome/browser/resources/tracing/tests.html index b00ed91..ec7b771 100644 --- a/chrome/browser/resources/tracing/tests.html +++ b/chrome/browser/resources/tracing/tests.html @@ -9,11 +9,14 @@ found in the LICENSE file. <title>All Tracing Tests</title> <script> tests = [ + 'overlay_test.html', 'timeline_model_test.html', 'timeline_track_test.html', 'linux_perf_importer_test.html', 'trace_event_importer_test.html', 'profiling_view_test.html', + 'timeline_test.html', + 'timeline_view_test.html', ]; </script> <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script> @@ -28,6 +31,7 @@ found in the LICENSE file. var testRunner = new goog.testing.MultiTestRunner() .setName(document.title) .setBasePath('./') + .setPoolSize(8) .setStatsBucketSizes(5, 500) .setHidePasses(true) .addTests(tests); diff --git a/chrome/browser/resources/tracing/timeline.js b/chrome/browser/resources/tracing/timeline.js index ef80c6e..e38ec41 100644 --- a/chrome/browser/resources/tracing/timeline.js +++ b/chrome/browser/resources/tracing/timeline.js @@ -194,6 +194,13 @@ cr.define('tracing', function() { this.xPanWorldPosToViewPos(worldMax, 'right', viewWidth); }, + xSetWorldRange: function(worldMin, worldMax, viewWidth) { + var worldRange = worldMax - worldMin; + var scaleX = viewWidth / worldRange; + var panX = -worldMin; + this.setPanAndScale(panX, scaleX); + }, + get gridEnabled() { return this.gridEnabled_; }, @@ -415,16 +422,28 @@ cr.define('tracing', function() { // Set up a reasonable viewport. this.viewport_.setWhenPossible(function() { - var rangeTimestamp = this.model_.maxTimestamp - - this.model_.minTimestamp; var w = this.firstCanvas.width; - var scaleX = w / rangeTimestamp; - var panX = -this.model_.minTimestamp; - this.viewport_.setPanAndScale(panX, scaleX); + this.viewport_.xSetWorldRange(this.model_.minTimestamp, + this.model_.maxTimestamp, + w); }.bind(this)); }, /** + * @return {Array} An array of objects that match the provided + * TimelineFilter. + */ + findAllObjectsMatchingFilter: function(filter) { + var hits = []; + for (var i = 0; i < this.tracks_.children.length; ++i) { + var trackHits = + this.tracks_.children[i].findAllObjectsMatchingFilter(filter); + Array.prototype.push.apply(hits, trackHits); + } + return hits; + }, + + /** * @return {Element} The element whose focused state determines * whether to respond to keyboard inputs. * Defaults to the parent element. @@ -444,6 +463,8 @@ cr.define('tracing', function() { }, get listenToKeys_() { + if (!this.viewport_.isAttachedToDocument_) + return false; if (!this.focusElement_) return true; if (this.focusElement.tabIndex >= 0) @@ -586,6 +607,7 @@ cr.define('tracing', function() { return false; } this.selection = selection; + // Potentially move the viewport to keep the new selection in view. this.viewport_.xPanWorldRangeIntoView(minTime, maxTime, this.firstCanvas.width); @@ -630,6 +652,39 @@ cr.define('tracing', function() { this.viewport_.dispatchChangeEvent(); // Triggers a redraw. }, + getSelectionRange: function() { + var wmin = Infinity; + var wmax = -wmin; + for (var i = 0; i < this.selection_.length; i++) { + var hit = this.selection_[i]; + if (hit.slice) { + wmin = Math.min(wmin, hit.slice.start); + wmax = Math.max(wmax, hit.slice.end); + } + } + return { + min: wmin, + max: wmax + }; + }, + + setSelectionAndMakeVisible: function(selection, zoomAllowed) { + this.selection = selection; + var range = this.getSelectionRange(); + var size = this.viewport_.xWorldVectorToView(range.max - range.min); + if (zoomAllowed && size < 50) { + var worldCenter = range.min + (range.max - range.min) * 0.5; + var worldRange = (range.max - range.min) * 5; + this.viewport_.xSetWorldRange(worldCenter - worldRange * 0.5, + worldCenter + worldRange * 0.5, + this.firstCanvas.width); + return; + } + + this.viewport_.xPanWorldRangeIntoView(range.min, range.max, + this.firstCanvas.width); + }, + get firstCanvas() { return this.tracks_.firstChild ? this.tracks_.firstChild.firstCanvas : undefined; diff --git a/chrome/browser/resources/tracing/timeline_model.js b/chrome/browser/resources/tracing/timeline_model.js index 6c86331d..193cea3 100644 --- a/chrome/browser/resources/tracing/timeline_model.js +++ b/chrome/browser/resources/tracing/timeline_model.js @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +'use strict'; /** * @fileoverview TimelineModel is a parsed representation of the @@ -212,7 +213,6 @@ cr.define('tracing', function() { ', tid: ' + this.tid + (this.name ? ', name: ' + this.name : ''); } - }; /** @@ -579,7 +579,6 @@ cr.define('tracing', function() { } return groups; } - }; /** @@ -1006,7 +1005,7 @@ cr.define('tracing', function() { if (opt_zeroAndBoost === undefined) opt_zeroAndBoost = true; - activeImporters = []; + var activeImporters = []; var importer = this.importOneTrace_(eventData, false); activeImporters.push(importer); if (opt_additionalEventData) { @@ -1036,6 +1035,24 @@ cr.define('tracing', function() { } }; + /** + * @constructor A filter that can be passed into + * Timeline.findAllObjectsMatchingFilter + */ + function TimelineFilter(text) { + this.text_ = text; + } + TimelineFilter.prototype = { + __proto__: Object.prototype, + + matchSlice: function(slice) { + if (this.text_.length == 0) + return false; + return slice.title.indexOf(this.text_) != -1; + } + + }; + return { getPallette: getPallette, getPalletteHighlightIdBoost: getPalletteHighlightIdBoost, @@ -1051,7 +1068,8 @@ cr.define('tracing', function() { TimelineProcess: TimelineProcess, TimelineCpu: TimelineCpu, TimelineAsyncSliceGroup: TimelineAsyncSliceGroup, - TimelineModel: TimelineModel + TimelineModel: TimelineModel, + TimelineFilter: TimelineFilter }; }); diff --git a/chrome/browser/resources/tracing/timeline_model_test.html b/chrome/browser/resources/tracing/timeline_model_test.html index 7b62520..eed1290 100644 --- a/chrome/browser/resources/tracing/timeline_model_test.html +++ b/chrome/browser/resources/tracing/timeline_model_test.html @@ -10,6 +10,7 @@ found in the LICENSE file. <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/event_target.js"></script> +<script src="test_utils.js"></script> <script src="timeline_model.js"></script> <script> goog.require('goog.testing.jsunit'); @@ -25,22 +26,10 @@ var TimelineThreadSlice = tracing.TimelineThreadSlice; var TimelineProcess = tracing.TimelineProcess; var TimelineThread = tracing.TimelineThread; var TimelineModel = tracing.TimelineModel; +var TimelineFilter = tracing.TimelineFilter; 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; - var subSlice = new TimelineAsyncSlice('a', 0, start); - subSlice.duration = duration; - subSlice.startThread = startThread; - subSlice.endThread = endThread; - s.subSlices = [subSlice]; - return s; -} +var newAsyncSlice = test_utils.newAsyncSlice; function testThreadBounds_Empty() { var t = new TimelineThread(new TimelineProcess(7), 1); @@ -248,6 +237,20 @@ function testModelCanImportEmpty() { m = new TimelineModel([]); m = new TimelineModel(''); } + +function testTimelineFilter() { + var s0 = new TimelineSlice('a', 0, 1, {}, 3); + assertFalse(new TimelineFilter('').matchSlice(s0)); + + assertTrue(new TimelineFilter('a').matchSlice(s0)); + assertFalse(new TimelineFilter('x').matchSlice(s0)); + + var s1 = new TimelineSlice('ba', 0, 1, {}, 3); + assertTrue(new TimelineFilter('a').matchSlice(s1)); + assertTrue(new TimelineFilter('ba').matchSlice(s1)); + assertFalse(new TimelineFilter('x').matchSlice(s1)); +} + </script> </body> </html> diff --git a/chrome/browser/resources/tracing/timeline_test.html b/chrome/browser/resources/tracing/timeline_test.html index f735653..f6b718c 100644 --- a/chrome/browser/resources/tracing/timeline_test.html +++ b/chrome/browser/resources/tracing/timeline_test.html @@ -6,96 +6,87 @@ 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> +<title>Timeline tests</title> +<link rel="stylesheet" href="overlay.css"> +<link rel="stylesheet" href="timeline_view.css"> <link rel="stylesheet" href="timeline.css"> +<link rel="stylesheet" href="../shared/css/tabs.css"> +<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/event_target.js"></script> <script src="../shared/js/cr/ui.js"></script> -<script src="../shared/js/util.js"></script> +<script src="../shared/js/cr/ui/tabs.js"></script> +<script src="overlay.js"></script> +<script src="measuring_stick.js"></script> +<script src="profiling_view.js"></script> +<script src="timeline_view.js"></script> <script src="timeline_model.js"></script> <script src="linux_perf_importer.js"></script> <script src="trace_event_importer.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="sorted_array_utils.js"></script> <script src="fast_rect_renderer.js"></script> <script src="test_utils.js"></script> +<script> + goog.require('goog.testing.jsunit'); +</script> +<style> +</style> </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> + <script> + 'use strict'; - <div class="timeline-test" src="./tests/tall_trace.json"> - </div> + /* + * This test just instantiates a TimelineView and adds it to the DOM + * to help with non-unittest UI work. + */ + function testInstantiateTimeline() { + var events = [ + {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}, + {name: 'b', args: {}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'b', args: {}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'} + ]; + var model = new tracing.TimelineModel(); + model.importEvents(events); + var timeline = new tracing.Timeline(); + timeline.model = model; + document.body.appendChild(timeline); + } - <div class="timeline-test" src="./tests/big_trace.json"> - </div> + function testFindAllObjectsMatching() { + var model = new tracing.TimelineModel(); + var p1 = model.getOrCreateProcess(1); + var t1 = p1.getOrCreateThread(1); - <div class="timeline-test" src="./tests/huge_trace.json"> - </div> + t1.subRows[0].push(new tracing.TimelineThreadSlice('a', 0, 1, {}, 3)); + t1.subRows[0].push(new tracing.TimelineThreadSlice('b', 0, 1, {}, 3)); - <div class="timeline-test" src="./tests/main_thread_has_unclosed_slices.json"> - </div> + var t1asg = t1.asyncSlices; + t1asg.slices.push(test_utils.newAsyncSliceNamed('a', 0, 1, t1, t1)); + t1asg.slices.push(test_utils.newAsyncSliceNamed('b', 1, 2, t1, t1)); - <div class="timeline-test" src="./tests/async_begin_end.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 timeline = new tracing.Timeline(); + timeline.model = model; - var containerEl = document.createElement('div'); - containerEl.tabIndex = 0; - containerEl.style.border = '1px solid red'; + var expected = [{slice: t1asg.slices[0].subSlices[0]}, + {slice: t1.subRows[0][0]}]; + var result = timeline.findAllObjectsMatchingFilter(new tracing.TimelineFilter('a')); + assertEquals(2, result.length); + assertEquals(expected[0].slice, result[0].slice); + assertEquals(expected[1].slice, result[1].slice); - 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; - function createModel(data) { - timelineEl.model = new tracing.TimelineModel(data); - if (!create_detached) - containerEl.appendChild(timelineEl); + var expected = [{slice: t1asg.slices[1].subSlices[0]}, + {slice: t1.subRows[0][1]}]; + var result = timeline.findAllObjectsMatchingFilter(new tracing.TimelineFilter('b')); + assertEquals(2, result.length); + assertEquals(expected[0].slice, result[0].slice); + assertEquals(expected[1].slice, result[1].slice); } - if (create_detached) - containerEl.appendChild(timelineEl); - test_utils.getAsync(src, createModel); - } - - function onLoad() { - Array.prototype.forEach.call(document.querySelectorAll('.timeline-test'), - load); - } - document.addEventListener('DOMContentLoaded', onLoad); - window.addEventListener('hashchange', onLoad); </script> </body> </html> diff --git a/chrome/browser/resources/tracing/timeline_track.js b/chrome/browser/resources/tracing/timeline_track.js index 23057af..3cd2e06 100644 --- a/chrome/browser/resources/tracing/timeline_track.js +++ b/chrome/browser/resources/tracing/timeline_track.js @@ -108,6 +108,18 @@ cr.define('tracing', function() { if (a <= b) this.tracks_[i].pickRange(loWX, hiWX, loY, hiY, onHitCallback); } + }, + + /** + * @return {Array} Objects matching the given filter. + */ + findAllObjectsMatchingFilter: function(filter) { + var hits = []; + for (var i = 0; i < this.tracks_.length; i++) { + var trackHits = this.tracks_[i].findAllObjectsMatchingFilter(filter); + Array.prototype.push.apply(hits, trackHits); + } + return hits; } }; @@ -756,8 +768,16 @@ cr.define('tracing', function() { else if ((index != undefined) && (index > 0)) index--; return index != undefined ? this.slices_[index] : undefined; - } + }, + findAllObjectsMatchingFilter: function(filter) { + var hits = []; + for (var i = 0; i < this.slices_.length; ++i) + if (filter.matchSlice(this.slices_[i])) + hits.push({track: this, + slice: this.slices_[i]}); + return hits; + } }; /** @@ -905,6 +925,10 @@ cr.define('tracing', function() { pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { // Does nothing. There's nothing interesting to pick on the viewport // track. + }, + + findAllObjectsMatchingFilter: function(filter) { + return []; } }; @@ -1051,6 +1075,10 @@ cr.define('tracing', function() { * intersecting the interval. */ pickRange: function(loWX, hiWX, loY, hiY, onHitCallback) { + }, + + findAllObjectsMatchingFilter: function(filter) { + return []; } }; diff --git a/chrome/browser/resources/tracing/timeline_track_test.html b/chrome/browser/resources/tracing/timeline_track_test.html index 6a8fb99..855b0349 100644 --- a/chrome/browser/resources/tracing/timeline_track_test.html +++ b/chrome/browser/resources/tracing/timeline_track_test.html @@ -40,6 +40,8 @@ found in the LICENSE file. <script> </script> <script> + 'use strict'; + var TimelineAsyncSlice = tracing.TimelineAsyncSlice; var TimelineAsyncSliceGroup = tracing.TimelineAsyncSliceGroup; var TimelineCounter = tracing.TimelineCounter; @@ -89,21 +91,19 @@ found in the LICENSE file. track.clientWidth / (1.1 * track.slices[track.slices.length - 1].end)); } - function testBasicSlicesWithAsyncFlag() { - var testEl = getTestDiv('testBasicSlicesWithAsyncFlag'); + function testFindAllObjectsMatchingInSliceTrack() { 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)); + var hits = track.findAllObjectsMatchingFilter( + new tracing.TimelineFilter("b")); + assertEquals(2, hits.length); + assertEquals(track.slices[1], hits[0].slice); + assertEquals(track.slices[2], hits[1].slice); } function testShrinkingSliceSizes() { @@ -192,7 +192,6 @@ found in the LICENSE file. var ctr = new TimelineCounter(undefined, 'testBasicCounter', 'testBasicCounter'); - ctr.numSeries = 1; ctr.seriesNames = ['value1', 'value2']; ctr.seriesColors = [tracing.getStringColorId('testBasicCounter.value1'), tracing.getStringColorId('testBasicCounter.value2')]; @@ -220,8 +219,8 @@ found in the LICENSE file. function testElideVisualInspection() { var optDicts = [{ trackName: 'elideOff', elide: false }, { trackName: 'elideOn', elide: true }]; - for (dictIndex in optDicts) { - dict = optDicts[dictIndex]; + for (var dictIndex in optDicts) { + var dict = optDicts[dictIndex]; var testEl = getTestDiv(dict.trackName); var track = TimelineSliceTrack(); if (dict.elide) { diff --git a/chrome/browser/resources/tracing/timeline_view.css b/chrome/browser/resources/tracing/timeline_view.css index f5bc159..0e89f89 100644 --- a/chrome/browser/resources/tracing/timeline_view.css +++ b/chrome/browser/resources/tracing/timeline_view.css @@ -9,26 +9,40 @@ padding: 0; } -.timeline-view > .timeline { - -webkit-box-flex: 1; +.timeline-view > .control { + border-bottom: 1px solid #555; display: -webkit-box; - overflow: auto; } -.timeline-view .timeline-container { +.timeline-view > .control > span { + padding-left: 5px; + padding-right: 10px; +} + +.timeline-view > .control > button { + font-size: 75%; + height: 20px; + min-height: 10px; +} + +.timeline-view > .control > .spacer { + -webkit-box-flex: 1; +} + +.timeline-view > .timeline-container { -webkit-box-flex: 1; display: -webkit-box; overflow: auto; } -.timeline-view .timeline-container > * { +.timeline-view > .timeline-container > * { -webkit-box-flex: 1; } -.timeline-view .summary-container * { +.timeline-view > .summary-container * { -webkit-user-select: text; } -.timeline-view .summary-container { +.timeline-view > .summary-container { border-top: 1px solid black; font-family: monospace; max-height: 250px; @@ -43,3 +57,43 @@ .timeline-view .selection ul { margin: 0; } + +.timeline-find-control { + -webkit-user-select: none; + position: relative; +} + +.timeline-find-control .hit-count-label { + left: 0; + opacity: 0.25; + pointer-events: none; + position: absolute; + text-align: right; + top: 2px; + width: 170px; + z-index: 1; +} + +.timeline-find-control input { + -webkit-user-select: auto; + margin-right: 1px; + width: 170px; +} + +.timeline-find-control .find-button { + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid rgba(0, 0, 0, 0.2); + color: rgba(0,0,0,0.2); + font-size: 14px; + height: 23px; + text-align: center; + width: 23px; +} + +.timeline-find-control .find-button:hover { + background-color: rgba(255, 255, 255, 1.0); + border: 1px solid rgba(0, 0, 0, 0.8); + border-radius: 25%; + box-shadow: 0 0 .05em rgba(0, 0, 0, 0.4); + color: rgba(0, 0, 0, 1); +} diff --git a/chrome/browser/resources/tracing/timeline_view.js b/chrome/browser/resources/tracing/timeline_view.js index 1f50b9e..c593f528 100644 --- a/chrome/browser/resources/tracing/timeline_view.js +++ b/chrome/browser/resources/tracing/timeline_view.js @@ -6,7 +6,7 @@ /** * @fileoverview TimelineView visualizes TRACE_EVENT events using the - * tracing.Timeline component. + * tracing.Timeline component and adds in selection summary and control buttons. */ cr.define('tracing', function() { function tsRound(ts) { @@ -36,6 +36,189 @@ cr.define('tracing', function() { } /** + * TimelineFindControl + * @constructor + * @extends {tracing.Overlay} + */ + var TimelineFindControl = cr.ui.define('div'); + + TimelineFindControl.prototype = { + __proto__: tracing.Overlay.prototype, + + decorate: function() { + tracing.Overlay.prototype.decorate.call(this); + + this.className = 'timeline-find-control'; + + this.hitCountEl_ = document.createElement('span'); + this.hitCountEl_.className = 'hit-count-label'; + this.hitCountEl_.textContent = '1 of 7'; + + var findPreviousBn = document.createElement('span'); + findPreviousBn.className = 'find-button find-previous'; + findPreviousBn.textContent = '\u2190'; + findPreviousBn.addEventListener('click', function() { + this.controller.findPrevious(); + this.updateHitCountEl_(); + }.bind(this)); + + var findNextBn = document.createElement('span'); + findNextBn.className = 'find-button find-next'; + findNextBn.textContent = '\u2192'; + findNextBn.addEventListener('click', function() { + this.controller.findNext(); + this.updateHitCountEl_(); + }.bind(this)); + + // Filter input element. + this.filterEl_ = document.createElement('input'); + this.filterEl_.type = 'input'; + + this.filterEl_.addEventListener('input', function(e) { + this.controller.filterText = this.filterEl_.value; + this.updateHitCountEl_(); + }.bind(this)); + + this.filterEl_.addEventListener('keydown', function(e) { + if (e.keyCode == 13) { + findNextBn.click(); + } else if (e.keyCode == 27) { + this.filterEl_.blur(); + this.updateHitCountEl_(); + } + }.bind(this)); + + this.filterEl_.addEventListener('blur', function(e) { + this.updateHitCountEl_(); + }.bind(this)); + + this.filterEl_.addEventListener('focus', function(e) { + this.updateHitCountEl_(); + }.bind(this)); + + // Attach everything. + this.appendChild(this.filterEl_); + + this.appendChild(findPreviousBn); + this.appendChild(findNextBn); + this.appendChild(this.hitCountEl_); + + this.updateHitCountEl_(); + }, + + get controller() { + return this.controller_; + }, + + set controller(c) { + this.controller_ = c; + this.updateHitCountEl_(); + }, + + focus: function() { + this.filterEl_.selectionStart = 0; + this.filterEl_.selectionEnd = this.filterEl_.value.length; + this.filterEl_.focus(); + }, + + updateHitCountEl_: function() { + if (!this.controller || document.activeElement != this.filterEl_) { + this.hitCountEl_.textContent = ''; + return; + } + var i = this.controller.currentHitIndex; + var n = this.controller.filterHits.length; + if (n == 0) + this.hitCountEl_.textContent = '0 of 0'; + else + this.hitCountEl_.textContent = (i + 1) + ' of ' + n; + } + }; + + function TimelineFindController() { + this.timeline_ = undefined; + this.model_ = undefined; + this.filterText_ = ''; + this.filterHitsDirty_ = true; + this.currentHitIndex_ = 0; + }; + + TimelineFindController.prototype = { + __proto__: Object.prototype, + + get timeline() { + return this.timeline_; + }, + + set timeline(t) { + this.timeline_ = t; + this.filterHitsDirty_ = true; + }, + + get filterText() { + return this.filterText_; + }, + + set filterText(f) { + if (f == this.filterText_) + return; + this.filterText_ = f; + this.filterHitsDirty_ = true; + this.findNext(); + }, + + get filterHits() { + if (this.filterHitsDirty_) { + this.filterHitsDirty_ = false; + if (this.timeline_) { + var filter = new tracing.TimelineFilter(this.filterText); + this.filterHits_ = this.timeline.findAllObjectsMatchingFilter(filter); + this.currentHitIndex_ = this.filterHits_.length - 1; + } else { + this.filterHits_ = []; + this.currentHitIndex_ = 0; + } + } + return this.filterHits_; + }, + + get currentHitIndex() { + return this.currentHitIndex_; + }, + + find_: function(dir) { + if (!this.timeline) + return; + + var N = this.filterHits.length; + this.currentHitIndex_ = this.currentHitIndex_ + dir; + + if (this.currentHitIndex_ < 0) this.currentHitIndex_ = N - 1; + if (this.currentHitIndex_ >= N) this.currentHitIndex_ = 0; + + if (this.currentHitIndex_ < 0 || this.currentHitIndex_ >= N) { + this.timeline.selection = []; + return; + } + + var hit = this.filterHits[this.currentHitIndex_]; + + // We allow the zoom level to change on the first hit level. But, when + // then cycling through subsequent changes, restrict it to panning. + var zoomAllowed = this.currentHitIndex_ == 0; + this.timeline.setSelectionAndMakeVisible([hit], zoomAllowed); + }, + + findNext: function() { + this.find_(1); + }, + + findPrevious: function() { + this.find_(-1); + }, + }; + + /** * TimelineView * @constructor * @extends {HTMLDivElement} @@ -48,6 +231,19 @@ cr.define('tracing', function() { decorate: function() { this.classList.add('timeline-view'); + // Create individual elements. + this.titleEl_ = document.createElement('span'); + this.titleEl_.textContent = 'Tracing: '; + + this.controlDiv_ = document.createElement('div'); + this.controlDiv_.className = 'control'; + + this.leftControlsEl_ = document.createElement('div'); + this.rightControlsEl_ = document.createElement('div'); + + var spacingEl = document.createElement('div'); + spacingEl.className = 'spacer'; + this.timelineContainer_ = document.createElement('div'); this.timelineContainer_.className = 'timeline-container'; @@ -57,11 +253,42 @@ cr.define('tracing', function() { this.summaryEl_ = document.createElement('pre'); this.summaryEl_.className = 'summary'; - summaryContainer_.appendChild(this.summaryEl_); + this.findCtl_ = new TimelineFindControl(); + this.findCtl_.controller = new TimelineFindController(); + + // Connect everything up. + this.rightControls.appendChild(this.findCtl_); + this.controlDiv_.appendChild(this.titleEl_); + this.controlDiv_.appendChild(this.leftControlsEl_); + this.controlDiv_.appendChild(spacingEl); + this.controlDiv_.appendChild(this.rightControlsEl_); + this.appendChild(this.controlDiv_); + this.appendChild(this.timelineContainer_); + + summaryContainer_.appendChild(this.summaryEl_); this.appendChild(summaryContainer_); + // Bookkeeping. this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this); + document.addEventListener('keypress', this.onKeypress_.bind(this), true); + }, + + get leftControls() { + return this.leftControlsEl_; + }, + + get rightControls() { + return this.rightControlsEl_; + }, + + get title() { + return this.titleEl_.textContent.substring( + this.titleEl_.textContent.length - 2); + }, + + set title(text) { + this.titleEl_.textContent = text + ':'; }, set traceData(traceData) { @@ -84,13 +311,17 @@ cr.define('tracing', function() { this.timeline_.detach(); this.timeline_ = new tracing.Timeline(); this.timeline_.model = this.timelineModel_; - this.timeline_.focusElement = this.parentElement; + this.timeline_.focusElement = + this.focusElement_ ? this.focusElement_ : this.parentElement; this.timelineContainer_.appendChild(this.timeline_); this.timeline_.addEventListener('selectionChange', this.onSelectionChangedBoundToThis_); + + this.findCtl_.controller.timeline = this.timeline_; this.onSelectionChanged_(); } else { - this.timeline_ = null; + this.timeline_ = undefined; + this.findCtl_.controller.timeline = undefined; } }, @@ -98,6 +329,76 @@ cr.define('tracing', function() { return this.timeline_; }, + /** + * Sets the element whose focus state will determine whether + * to respond to keybaord input. + */ + set focusElement(value) { + this.focusElement_ = value; + if (this.timeline_) + this.timeline_.focusElement = value; + }, + + /** + * @return {Element} The element whose focused state determines + * whether to respond to keyboard inputs. + * Defaults to the parent element. + */ + get focusElement() { + if (this.focusElement_) + return this.focusElement_; + return this.parentElement; + }, + + /** + * @return {boolean} Whether the current timeline is attached to the + * document. + */ + get isAttachedToDocument_() { + var cur = this; + while (cur.parentNode) + cur = cur.parentNode; + return cur == this.ownerDocument; + }, + + get listenToKeys_() { + if (!this.isAttachedToDocument_) + return; + if (!this.focusElement_) + return true; + if (this.focusElement.tabIndex >= 0) + return document.activeElement == this.focusElement; + return true; + }, + + onKeypress_: function(e) { + if (!this.listenToKeys_) + return; + + if (event.keyCode == 47) { + this.findCtl_.focus(); + event.preventDefault(); + return; + } + }, + + beginFind: function() { + if (this.findInProgress_) + return; + this.findInProgress_ = true; + var dlg = TimelineFindControl(); + dlg.controller = new TimelineFindController(); + dlg.controller.timeline = this.timeline; + dlg.visible = true; + dlg.addEventListener('close', function() { + this.findInProgress_ = false; + }.bind(this)); + dlg.addEventListener('findNext', function() { + }); + dlg.addEventListener('findPrevious', function() { + }); + }, + onSelectionChanged_: function(e) { var timeline = this.timeline_; var selection = timeline.selection; @@ -193,6 +494,8 @@ cr.define('tracing', function() { }; return { + TimelineFindControl: TimelineFindControl, + TimelineFindController: TimelineFindController, TimelineView: TimelineView }; }); diff --git a/chrome/browser/resources/tracing/timeline_view_test.html b/chrome/browser/resources/tracing/timeline_view_test.html new file mode 100644 index 0000000..d24cb7f --- /dev/null +++ b/chrome/browser/resources/tracing/timeline_view_test.html @@ -0,0 +1,257 @@ +<!DOCTYPE HTML> +<html> +<!-- +Copyright (c) 2012 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>TimelineView tests</title> +<link rel="stylesheet" href="overlay.css"> +<link rel="stylesheet" href="timeline.css"> +<link rel="stylesheet" href="timeline_view.css"> +<link rel="stylesheet" href="../shared/css/tabs.css"> +<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/event_target.js"></script> +<script src="../shared/js/cr/ui.js"></script> +<script src="../shared/js/cr/ui/tabs.js"></script> +<script src="overlay.js"></script> +<script src="measuring_stick.js"></script> +<script src="profiling_view.js"></script> +<script src="timeline_view.js"></script> +<script src="timeline_model.js"></script> +<script src="linux_perf_importer.js"></script> +<script src="trace_event_importer.js"></script> +<script src="timeline.js"></script> +<script src="timeline_track.js"></script> +<script src="sorted_array_utils.js"></script> +<script src="fast_rect_renderer.js"></script> +<script src="test_utils.js"></script> +<script> + goog.require('goog.testing.jsunit'); +</script> +<style> + .timeline-view { + border: 1px solid black; + margin: 10px; + } + .timeline-find-dialog { + border: 1px solid black; + margin: 10px; + } +</style> +</head> +<body> + <script> + 'use strict'; + + /* + * Just enough of the Timeline to support the tests below. + */ + var FakeTimeline = cr.ui.define('div'); + + FakeTimeline.prototype = { + __proto__: HTMLDivElement.prototype, + + decorate: function() { + this.findAllObjectsMatchingFilterReturnValue = []; + + this.selection = []; + this.keyHelp = "<keyHelp>"; + + // Put some simple UI in for testing purposes. + var noteEl = document.createElement('div'); + noteEl.textContent = "FakeTimeline:"; + this.appendChild(noteEl); + + this.statusEl_ = document.createElement('div'); + this.appendChild(this.statusEl_); + this.refresh_(); + }, + + refresh_: function() { + var status; + if (this.model) + status = "model=set"; + else + status = "model=undefined"; + this.statusEl_.textContent = status; + }, + + setSelectionAndMakeVisible: function(selection, zoomAllowed) { + this.selection = selection; + }, + + findAllObjectsMatchingFilter: function(filter) { + return this.findAllObjectsMatchingFilterReturnValue; + } + }; + + /* + * This test just instantiates a TimelineView and adds it to the DOM + * to help with non-unittest UI work. + */ + function testInstantiateTimelineView() { + var events = [ + {name: 'a', args: {}, pid: 52, ts: 520, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'a', args: {}, pid: 52, ts: 560, cat: 'foo', tid: 53, ph: 'E'}, + {name: 'b', args: {}, pid: 52, ts: 629, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'b', args: {}, pid: 52, ts: 631, cat: 'foo', tid: 53, ph: 'E'}, + {name: 'a', args: {}, pid: 52, ts: 640, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'a', args: {}, pid: 52, ts: 700, cat: 'foo', tid: 53, ph: 'E'}, + {name: 'a', args: {}, pid: 52, ts: 710, cat: 'foo', tid: 53, ph: 'B'}, + {name: 'a', args: {}, pid: 52, ts: 740, cat: 'foo', tid: 53, ph: 'E'} + ]; + var model = new tracing.TimelineModel(); + model.importEvents(events); + var view = new tracing.TimelineView(); + view.model = model; + view.tabIndex = 0; + view.focusElement = view; + + document.body.appendChild(view); + } + + /* + * This test just instantiates a FindDialog and adds it to the DOM + * to help with non-unittest UI work. + */ + function testInstantiateTimelineFindControl() { + var ctl = new tracing.TimelineFindControl(); + var didFindPrevious = false; + var didFindNext = false; + ctl.controller = { + findNext: function() { + didFindNext = true; + }, + + findPrevious: function() { + didFindPrevious = true; + }, + + filterHits: [], + + currentHitIndex: 0, + } + document.body.appendChild(ctl); + ctl.querySelector('input').focus(); + ctl.querySelector('input').blur(); + + ctl.querySelector('.find-previous').click(); + assertTrue(didFindPrevious); + ctl.querySelector('.find-next').click(); + assertTrue(didFindNext); + } + + function testFindControllerNoTimeline() { + var controller = new tracing.TimelineFindController(); + controller.findNext(); + controller.findPrevious(); + } + + function testFindControllerEmptyHit() { + var timeline = new FakeTimeline(); + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + + timeline.selection = []; + controller.findNext(); + assertArrayEquals([], timeline.selection); + controller.findPrevious(); + assertArrayEquals([], timeline.selection); + } + + function testFindControllerOneHit() { + var timeline = new FakeTimeline(); + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + + timeline.findAllObjectsMatchingFilterReturnValue = [1]; + controller.findNext(); + assertArrayEquals([1], timeline.selection); + controller.findNext(); + assertArrayEquals([1], timeline.selection); + controller.findPrevious(); + assertArrayEquals([1], timeline.selection); + } + + function testFindControllerMultipleHits() { + var timeline = new FakeTimeline(); + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + + timeline.findAllObjectsMatchingFilterReturnValue = [1,2,3]; + + // Loop through hits then when we wrap, try moving backward. + controller.findNext(); + assertArrayEquals([1], timeline.selection); + controller.findNext(); + assertArrayEquals([2], timeline.selection); + controller.findNext(); + assertArrayEquals([3], timeline.selection); + controller.findNext(); + assertArrayEquals([1], timeline.selection); + controller.findPrevious(); + assertArrayEquals([3], timeline.selection); + controller.findPrevious(); + assertArrayEquals([2], timeline.selection); + } + + function testFindControllerChangeFilterAfterNext() { + var timeline = new FakeTimeline(); + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + + timeline.findAllObjectsMatchingFilterReturnValue = [1,2,3]; + + // Loop through hits then when we wrap, try moving backward. + controller.findNext(); + timeline.findAllObjectsMatchingFilterReturnValue = [4]; + controller.filterText = "asdfsf"; + controller.findNext(); + assertArrayEquals([4], timeline.selection); + } + + function testFindControllerSelectsFirstItemImmediately() { + var timeline = new FakeTimeline(); + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + timeline.findAllObjectsMatchingFilterReturnValue = [1,2,3]; + controller.filterText = "asdfsf"; + assertArrayEquals([1], timeline.selection); + controller.findNext(); + assertArrayEquals([2], timeline.selection); + } + + function testFindControllerWithRealTimeline() { + var model = new tracing.TimelineModel(); + var p1 = model.getOrCreateProcess(1); + var t1 = p1.getOrCreateThread(1); + t1.subRows[0].push(new tracing.TimelineThreadSlice('a', 0, 1, {}, 3)); + + var timeline = new tracing.Timeline(); + timeline.model = model; + + var controller = new tracing.TimelineFindController(); + controller.timeline = timeline; + + // Test find with no filterText. + controller.findNext(); + + // Test find with filter txt. + controller.filterText = 'a'; + controller.findNext(); + assertEquals(1, timeline.selection.length); + assertEquals(t1.subRows[0][0], timeline.selection[0].slice); + + controller.filterText = 'xxx'; + controller.findNext(); + assertEquals(0, timeline.selection.length); + controller.findNext(); + assertEquals(0, timeline.selection.length); + } + + </script> +</body> +</html> |