Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(564)

Side by Side Diff: chrome/browser/resources/tracing/timeline_model.js

Issue 8513009: Add TRACE_COUNTER support to about:tracing (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 5
6 /** 6 /**
7 * @fileoverview TimelineModel is a parsed representation of the 7 * @fileoverview TimelineModel is a parsed representation of the
8 * TraceEvents obtained from base/trace_event in which the begin-end 8 * TraceEvents obtained from base/trace_event in which the begin-end
9 * tokens are converted into a hierarchy of processes, threads, 9 * tokens are converted into a hierarchy of processes, threads,
10 * subrows, and slices. 10 * subrows, and slices.
(...skipping 12 matching lines...) Expand all
23 /** 23 /**
24 * A TimelineSlice represents an interval of time on a given thread 24 * A TimelineSlice represents an interval of time on a given thread
25 * associated with a specific trace event. For example, 25 * associated with a specific trace event. For example,
26 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms 26 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
27 * TRACE_EVENT_END() at time=0.3ms 27 * TRACE_EVENT_END() at time=0.3ms
28 * Results in a single timeline slice from 0.1 with duration 0.2. 28 * Results in a single timeline slice from 0.1 with duration 0.2.
29 * 29 *
30 * All time units are stored in milliseconds. 30 * All time units are stored in milliseconds.
31 * @constructor 31 * @constructor
32 */ 32 */
33 function TimelineSlice(title, colorId, start, args) { 33 function TimelineSlice(title, colorId, start, args, opt_duration) {
34 this.title = title; 34 this.title = title;
35 this.start = start; 35 this.start = start;
36 this.colorId = colorId; 36 this.colorId = colorId;
37 this.args = args; 37 this.args = args;
38 this.didNotFinish = false; 38 this.didNotFinish = false;
39 this.subSlices = []; 39 this.subSlices = [];
40 if (opt_duration !== undefined)
41 this.duration = opt_duration;
40 } 42 }
41 43
42 TimelineSlice.prototype = { 44 TimelineSlice.prototype = {
43 selected: false, 45 selected: false,
44 46
45 duration: undefined, 47 duration: undefined,
46 48
47 get end() { 49 get end() {
48 return this.start + this.duration; 50 return this.start + this.duration;
49 } 51 }
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
105 values.push(slices[0].start); 107 values.push(slices[0].start);
106 values.push(slices[slices.length - 1].end); 108 values.push(slices[slices.length - 1].end);
107 } 109 }
108 if (values.length) { 110 if (values.length) {
109 this.minTimestamp = Math.min.apply(Math, values); 111 this.minTimestamp = Math.min.apply(Math, values);
110 this.maxTimestamp = Math.max.apply(Math, values); 112 this.maxTimestamp = Math.max.apply(Math, values);
111 } else { 113 } else {
112 this.minTimestamp = undefined; 114 this.minTimestamp = undefined;
113 this.maxTimestamp = undefined; 115 this.maxTimestamp = undefined;
114 } 116 }
117 },
118
119 /**
120 * @return {String} A user-friendly name for this thread.
121 */
122 get userFriendlyName() {
123 var tname = this.name || this.tid;
124 return this.parent.pid + ': ' + tname;
125 },
126
127 /**
128 * @return {String} User friendly details about this thread.
129 */
130 get userFriendlyDetials() {
131 return 'pid: ' + this.parent.pid +
132 ', tid: ' + this.tid +
133 (this.name ? ', name: ' + this.name : '');
115 } 134 }
116 135
117 }; 136 };
118 137
119 /** 138 /**
120 * Comparison between threads that orders first by pid, 139 * Comparison between threads that orders first by pid,
121 * then by names, then by tid. 140 * then by names, then by tid.
122 */ 141 */
123 TimelineThread.compare = function(x, y) { 142 TimelineThread.compare = function(x, y) {
124 if (x.parent.pid != y.parent.pid) { 143 if (x.parent.pid != y.parent.pid) {
125 return x.parent.pid - y.parent.pid; 144 return TimelineProcess.compare(x.parent, y.parent.pid);
126 } 145 }
127 146
128 if (x.name && y.name) { 147 if (x.name && y.name) {
129 var tmp = x.name.localeCompare(y.name); 148 var tmp = x.name.localeCompare(y.name);
130 if (tmp == 0) 149 if (tmp == 0)
131 return x.tid - y.tid; 150 return x.tid - y.tid;
132 return tmp; 151 return tmp;
133 } else if (x.name) { 152 } else if (x.name) {
134 return -1; 153 return -1;
135 } else if (y.name) { 154 } else if (y.name) {
136 return 1; 155 return 1;
137 } else { 156 } else {
138 return x.tid - y.tid; 157 return x.tid - y.tid;
139 } 158 }
140 }; 159 };
141 160
161 /**
162 * Stores all the samples for a given counter.
163 * @constructor
164 */
165 function TimelineCounter(parent, name) {
166 this.parent = parent;
167 this.name = name;
168 this.seriesNames = [];
169 this.seriesColors = [];
170 this.timestamps = [];
171 this.samples = [];
172 }
173
174 TimelineCounter.prototype = {
175 __proto__: Object.prototype,
176
177 get numSeries() {
178 return this.seriesNames.length;
179 },
180
181 get numSamples() {
182 return this.timestamps.length;
183 },
184
185 /**
186 * Updates the bounds for this counter based on the samples it contains.
187 */
188 updateBounds: function() {
189 if (this.seriesNames.length != this.seriesColors.length)
190 throw 'seriesNames.length must match seriesColors.length';
191 if (this.numSeries * this.numSamples != this.samples.length)
192 throw 'samples.length must be a multiple of numSamples.';
193
194 this.totals = [];
195 if (this.samples.length == 0) {
196 this.minTimestamp = undefined;
197 this.maxTimestamp = undefined;
198 this.maxTotal = 0;
199 return;
200 }
201 this.minTimestamp = this.timestamps[0];
202 this.maxTimestamp = this.timestamps[this.timestamps.length - 1];
203
204 var numSeries = this.numSeries;
205 var maxTotal = -Infinity;
206 for (var i = 0; i < this.timestamps.length; i++) {
207 var total = 0;
208 for (var j = 0; j < numSeries; j++) {
209 total += this.samples[i * numSeries + j];
210 this.totals.push(total);
211 }
212 if (total > maxTotal)
213 maxTotal = total;
214 }
215 this.maxTotal = maxTotal;
216 }
217
218 };
219
220 /**
221 * Comparison between counters that orders by pid, then name.
222 */
223 TimelineCounter.compare = function(x, y) {
224 if (x.parent.pid != y.parent.pid) {
225 return TimelineProcess.compare(x.parent, y.parent.pid);
226 }
227 var tmp = x.name.localeCompare(y.name);
228 if (tmp == 0)
229 return x.tid - y.tid;
230 return tmp;
231 };
142 232
143 /** 233 /**
144 * The TimelineProcess represents a single process in the 234 * The TimelineProcess represents a single process in the
145 * trace. Right now, we keep this around purely for bookkeeping 235 * trace. Right now, we keep this around purely for bookkeeping
146 * reasons. 236 * reasons.
147 * @constructor 237 * @constructor
148 */ 238 */
149 function TimelineProcess(pid) { 239 function TimelineProcess(pid) {
150 this.pid = pid; 240 this.pid = pid;
151 this.threads = {}; 241 this.threads = {};
242 this.counters = {};
152 }; 243 };
153 244
154 TimelineProcess.prototype = { 245 TimelineProcess.prototype = {
155 get numThreads() { 246 get numThreads() {
156 var n = 0; 247 var n = 0;
157 for (var p in this.threads) { 248 for (var p in this.threads) {
158 n++; 249 n++;
159 } 250 }
160 return n; 251 return n;
161 }, 252 },
162 253
254 /**
255 * @return {TimlineThread} The thread identified by tid on this process,
256 * creating it if it doesn't exist.
257 */
163 getOrCreateThread: function(tid) { 258 getOrCreateThread: function(tid) {
164 if (!this.threads[tid]) 259 if (!this.threads[tid])
165 this.threads[tid] = new TimelineThread(this, tid); 260 this.threads[tid] = new TimelineThread(this, tid);
166 return this.threads[tid]; 261 return this.threads[tid];
262 },
263
264 /**
265 * @return {TimlineCounter} The counter on this process named 'name',
266 * creating it if it doesn't exist.
267 */
268 getOrCreateCounter: function(name) {
269 if (!this.counters[name])
270 this.counters[name] = new TimelineCounter(this, name);
271 return this.counters[name];
167 } 272 }
168 }; 273 };
169 274
170 /** 275 /**
276 * Comparison between processes that orders by pid.
277 */
278 TimelineProcess.compare = function(x, y) {
279 return x.pid - y.pid;
280 };
281
282 /**
171 * Computes a simplistic hashcode of the provide name. Used to chose colors 283 * Computes a simplistic hashcode of the provide name. Used to chose colors
172 * for slices. 284 * for slices.
173 * @param {string} name The string to hash. 285 * @param {string} name The string to hash.
174 */ 286 */
175 function getStringHash(name) { 287 function getStringHash(name) {
176 var hash = 0; 288 var hash = 0;
177 for (var i = 0; i < name.length; ++i) 289 for (var i = 0; i < name.length; ++i)
178 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; 290 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
179 return hash; 291 return hash;
180 } 292 }
181 293
182 /** 294 /**
295 * The number of color IDs that getStringColorId can choose from.
296 */
297 const numColorIds = 30;
298
299 /**
300 * @return {Number} A color ID that is stably associated to the provided via
301 * the getStringHash method.
302 */
303 function getStringColorId(string) {
304 var hash = getStringHash(string);
305 return hash % numColorIds;
306 }
307
308 /**
183 * Builds a model from an array of TraceEvent objects. 309 * Builds a model from an array of TraceEvent objects.
184 * @param {Array} events An array of TraceEvents created by 310 * @param {Array} events An array of TraceEvents created by
185 * TraceEvent.ToJSON(). 311 * TraceEvent.ToJSON().
186 * @constructor 312 * @constructor
187 */ 313 */
188 function TimelineModel(events) { 314 function TimelineModel(events) {
189 this.processes = {}; 315 this.processes = {};
190 this.importErrors = []; 316 this.importErrors = [];
191 317
192 if (events) 318 if (events)
(...skipping 18 matching lines...) Expand all
211 337
212 /** 338 /**
213 * The import takes an array of json-ified TraceEvents and adds them into 339 * The import takes an array of json-ified TraceEvents and adds them into
214 * the TimelineModel as processes, threads, and slices. 340 * the TimelineModel as processes, threads, and slices.
215 */ 341 */
216 importEvents: function(events) { 342 importEvents: function(events) {
217 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 343 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130
218 // The ptid is a unique key for a thread in the trace. 344 // The ptid is a unique key for a thread in the trace.
219 this.importErrors = []; 345 this.importErrors = [];
220 346
221 // Threadstate 347 // Threadstate.
222 const numColorIds = 30;
223 function ThreadState(tid) { 348 function ThreadState(tid) {
224 this.openSlices = []; 349 this.openSlices = [];
225 this.openNonNestedSlices = {}; 350 this.openNonNestedSlices = {};
226 } 351 }
227 var threadStateByPTID = {}; 352 var threadStateByPTID = {};
228 353
229 var nameToColorMap = {}; 354 var nameToColorMap = {};
230 function getColor(name) { 355 function getColor(name) {
231 if (!(name in nameToColorMap)) { 356 if (!(name in nameToColorMap)) {
232 var hash = getStringHash(name); 357 nameToColorMap[name] = getStringColorId(name);
233 nameToColorMap[name] = hash % numColorIds;
234 } 358 }
235 return nameToColorMap[name]; 359 return nameToColorMap[name];
236 } 360 }
237 361
238 var self = this; 362 var self = this;
239 363
240 /** 364 /**
241 * Helper to process a 'begin' event (e.g. initiate a slice). 365 * Helper to process a 'begin' event (e.g. initiate a slice).
242 * @param {ThreadState} state Thread state (holds slices). 366 * @param {ThreadState} state Thread state (holds slices).
243 * @param {Object} event The current trace event. 367 * @param {Object} event The current trace event.
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
321 445
322 if (event.ph == 'B') { 446 if (event.ph == 'B') {
323 processBegin(state, event); 447 processBegin(state, event);
324 } else if (event.ph == 'E') { 448 } else if (event.ph == 'E') {
325 processEnd(state, event); 449 processEnd(state, event);
326 } else if (event.ph == 'I') { 450 } else if (event.ph == 'I') {
327 // Treat an Instant event as a duration 0 slice. 451 // Treat an Instant event as a duration 0 slice.
328 // TimelineSliceTrack's redraw() knows how to handle this. 452 // TimelineSliceTrack's redraw() knows how to handle this.
329 processBegin(state, event); 453 processBegin(state, event);
330 processEnd(state, event); 454 processEnd(state, event);
455 } else if (event.ph == 'C') {
456 var ctr = this.getOrCreateProcess(event.pid)
457 .getOrCreateCounter(event.name);
jbates 2011/11/16 23:25:39 Do we care that ("cat1", "name1") is the same coun
nduca 2011/11/18 08:58:19 ooo great point. On 2011/11/16 23:25:39, jbates wr
458 // Initialize the counter's series fields if needed.
459 if (ctr.numSeries == 0) {
460 for (var seriesName in event.args) {
461 ctr.seriesNames.push(seriesName);
462 ctr.seriesColors.push(getStringHash(ctr.name + '.' + seriesName));
463 }
464 if (ctr.numSeries == 0) {
465 this.importErrors.push('Expected counter ' + event.name +
466 ' to have at least one argument to use as a value.');
467 // Drop the counter.
468 delete ctr.parent.counters[ctr.name];
469 continue;
470 }
471 }
472
473 // Add the sample values.
474 ctr.timestamps.push(event.ts);
475 for (var i = 0; i < ctr.numSeries; i++) {
476 var seriesName = ctr.seriesNames[i];
477 if (event.args[seriesName] === undefined) {
478 ctr.samples.push(0);
479 continue;
480 }
481 ctr.samples.push(event.args[seriesName]);
482 }
483
331 } else if (event.ph == 'M') { 484 } else if (event.ph == 'M') {
332 if (event.name == 'thread_name') { 485 if (event.name == 'thread_name') {
333 var thread = this.getOrCreateProcess(event.pid) 486 var thread = this.getOrCreateProcess(event.pid)
334 .getOrCreateThread(event.tid); 487 .getOrCreateThread(event.tid);
335 thread.name = event.args.name; 488 thread.name = event.args.name;
336 } else { 489 } else {
337 this.importErrors.push('Unrecognized metadata name: ' + event.name); 490 this.importErrors.push('Unrecognized metadata name: ' + event.name);
338 } 491 }
339 } else { 492 } else {
340 this.importErrors.push('Unrecognized event phase: ' + event.ph + 493 this.importErrors.push('Unrecognized event phase: ' + event.ph +
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
398 var threads = this.getAllThreads(); 551 var threads = this.getAllThreads();
399 for (var tI = 0; tI < threads.length; tI++) { 552 for (var tI = 0; tI < threads.length; tI++) {
400 var thread = threads[tI]; 553 var thread = threads[tI];
401 thread.updateBounds(); 554 thread.updateBounds();
402 if (thread.minTimestamp != undefined && 555 if (thread.minTimestamp != undefined &&
403 thread.maxTimestamp != undefined) { 556 thread.maxTimestamp != undefined) {
404 wmin = Math.min(wmin, thread.minTimestamp); 557 wmin = Math.min(wmin, thread.minTimestamp);
405 wmax = Math.max(wmax, thread.maxTimestamp); 558 wmax = Math.max(wmax, thread.maxTimestamp);
406 } 559 }
407 } 560 }
561 var counters = this.getAllCounters();
562 for (var tI = 0; tI < counters.length; tI++) {
563 var counter = counters[tI];
564 counter.updateBounds();
565 if (counter.minTimestamp != undefined &&
566 counter.maxTimestamp != undefined) {
567 wmin = Math.min(wmin, counter.minTimestamp);
568 wmax = Math.max(wmax, counter.maxTimestamp);
569 }
570 }
408 this.minTimestamp = wmin; 571 this.minTimestamp = wmin;
409 this.maxTimestamp = wmax; 572 this.maxTimestamp = wmax;
410 }, 573 },
411 574
412 shiftWorldToMicroseconds: function() { 575 shiftWorldToMicroseconds: function() {
413 var timeBase = this.minTimestamp; 576 var timeBase = this.minTimestamp;
414 var threads = this.getAllThreads(); 577 var threads = this.getAllThreads();
415 for (var tI = 0; tI < threads.length; tI++) { 578 for (var tI = 0; tI < threads.length; tI++) {
416 var thread = threads[tI]; 579 var thread = threads[tI];
417 var shiftSubRow = function(subRow) { 580 var shiftSubRow = function(subRow) {
418 for (var tS = 0; tS < subRow.length; tS++) { 581 for (var tS = 0; tS < subRow.length; tS++) {
419 var slice = subRow[tS]; 582 var slice = subRow[tS];
420 slice.start = (slice.start - timeBase) / 1000; 583 slice.start = (slice.start - timeBase) / 1000;
421 slice.duration /= 1000; 584 slice.duration /= 1000;
422 if (slice.startInUserTime) 585 if (slice.startInUserTime)
423 slice.startInUserTime /= 1000; 586 slice.startInUserTime /= 1000;
424 if (slice.durationInUserTime) 587 if (slice.durationInUserTime)
425 slice.durationInUserTime /= 1000; 588 slice.durationInUserTime /= 1000;
426 } 589 }
427 }; 590 };
428 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { 591 for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
429 shiftSubRow(thread.subRows[tSR]); 592 shiftSubRow(thread.subRows[tSR]);
430 } 593 }
431 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { 594 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) {
432 shiftSubRow(thread.nonNestedSubRows[tSR]); 595 shiftSubRow(thread.nonNestedSubRows[tSR]);
433 } 596 }
434 } 597 }
435 598 var counters = this.getAllCounters();
599 for (var tI = 0; tI < counters.length; tI++) {
600 var counter = counters[tI];
601 for (var sI = 0; sI < counter.timestamps.length; sI++)
602 counter.timestamps[sI] = (counter.timestamps[sI] - timeBase) / 1000;
603 }
436 this.updateBounds(); 604 this.updateBounds();
437 }, 605 },
438 606
439 getAllThreads: function() { 607 getAllThreads: function() {
440 var threads = []; 608 var threads = [];
441 for (var pid in this.processes) { 609 for (var pid in this.processes) {
442 var process = this.processes[pid]; 610 var process = this.processes[pid];
443 for (var tid in process.threads) { 611 for (var tid in process.threads) {
444 threads.push(process.threads[tid]); 612 threads.push(process.threads[tid]);
445 } 613 }
446 } 614 }
447 return threads; 615 return threads;
616 },
617
618 /**
619 * @return {Array} An array of all the counters in the model.
620 */
621 getAllCounters: function() {
622 var counters = [];
623 for (var pid in this.processes) {
624 var process = this.processes[pid];
625 for (var tid in process.counters) {
626 counters.push(process.counters[tid]);
627 }
628 }
629 return counters;
448 } 630 }
449 631
450 }; 632 };
451 633
452 return { 634 return {
453 getStringHash: getStringHash, 635 getStringHash: getStringHash,
636 getStringColorId: getStringColorId,
454 TimelineSlice: TimelineSlice, 637 TimelineSlice: TimelineSlice,
455 TimelineThread: TimelineThread, 638 TimelineThread: TimelineThread,
639 TimelineCounter: TimelineCounter,
456 TimelineProcess: TimelineProcess, 640 TimelineProcess: TimelineProcess,
457 TimelineModel: TimelineModel 641 TimelineModel: TimelineModel
458 }; 642 };
459 }); 643 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698