OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 }); |
OLD | NEW |