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 N = this.numSeries; | |
James Hawkins
2011/11/16 17:41:38
s/N/n/
nduca
2011/11/16 19:03:56
Done.
| |
205 var maxTotal = -Infinity; | |
206 for (var i = 0; i < this.timestamps.length; i++) { | |
207 var total = 0; | |
208 for (var j = 0; j < N; j++) { | |
209 total += this.samples[i * N + 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 |
163 getOrCreateThread: function(tid) { | 254 getOrCreateThread: function(tid) { |
164 if (!this.threads[tid]) | 255 if (!this.threads[tid]) |
165 this.threads[tid] = new TimelineThread(this, tid); | 256 this.threads[tid] = new TimelineThread(this, tid); |
166 return this.threads[tid]; | 257 return this.threads[tid]; |
258 }, | |
259 | |
260 getOrCreateCounter: function(name) { | |
James Hawkins
2011/11/16 17:41:38
Document this method.
nduca
2011/11/16 19:03:56
Done.
| |
261 if (!this.counters[name]) | |
262 this.counters[name] = new TimelineCounter(this, name); | |
263 return this.counters[name]; | |
167 } | 264 } |
168 }; | 265 }; |
169 | 266 |
170 /** | 267 /** |
268 * Comparison between processes that orders by pid. | |
269 */ | |
270 TimelineProcess.compare = function(x, y) { | |
271 return x.pid - y.pid; | |
272 }; | |
273 | |
274 /** | |
171 * Computes a simplistic hashcode of the provide name. Used to chose colors | 275 * Computes a simplistic hashcode of the provide name. Used to chose colors |
172 * for slices. | 276 * for slices. |
173 * @param {string} name The string to hash. | 277 * @param {string} name The string to hash. |
174 */ | 278 */ |
175 function getStringHash(name) { | 279 function getStringHash(name) { |
176 var hash = 0; | 280 var hash = 0; |
177 for (var i = 0; i < name.length; ++i) | 281 for (var i = 0; i < name.length; ++i) |
178 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | 282 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; |
179 return hash; | 283 return hash; |
180 } | 284 } |
181 | 285 |
286 const numColorIds = 30; | |
James Hawkins
2011/11/16 17:41:38
Document this var.
nduca
2011/11/16 19:03:56
Done.
| |
287 /** | |
288 * @return {Number} A color ID that is stably associated to the provided via | |
289 * the getStringHash method. | |
290 */ | |
291 function getStringColorId(string) { | |
292 var hash = getStringHash(string); | |
293 return hash % numColorIds; | |
294 } | |
295 | |
182 /** | 296 /** |
183 * Builds a model from an array of TraceEvent objects. | 297 * Builds a model from an array of TraceEvent objects. |
184 * @param {Array} events An array of TraceEvents created by | 298 * @param {Array} events An array of TraceEvents created by |
185 * TraceEvent.ToJSON(). | 299 * TraceEvent.ToJSON(). |
186 * @constructor | 300 * @constructor |
187 */ | 301 */ |
188 function TimelineModel(events) { | 302 function TimelineModel(events) { |
189 this.processes = {}; | 303 this.processes = {}; |
190 this.importErrors = []; | 304 this.importErrors = []; |
191 | 305 |
(...skipping 19 matching lines...) Expand all Loading... | |
211 | 325 |
212 /** | 326 /** |
213 * The import takes an array of json-ified TraceEvents and adds them into | 327 * The import takes an array of json-ified TraceEvents and adds them into |
214 * the TimelineModel as processes, threads, and slices. | 328 * the TimelineModel as processes, threads, and slices. |
215 */ | 329 */ |
216 importEvents: function(events) { | 330 importEvents: function(events) { |
217 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 | 331 // 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. | 332 // The ptid is a unique key for a thread in the trace. |
219 this.importErrors = []; | 333 this.importErrors = []; |
220 | 334 |
221 // Threadstate | 335 // Threadstate. |
222 const numColorIds = 30; | |
223 function ThreadState(tid) { | 336 function ThreadState(tid) { |
224 this.openSlices = []; | 337 this.openSlices = []; |
225 this.openNonNestedSlices = {}; | 338 this.openNonNestedSlices = {}; |
226 } | 339 } |
227 var threadStateByPTID = {}; | 340 var threadStateByPTID = {}; |
228 | 341 |
229 var nameToColorMap = {}; | 342 var nameToColorMap = {}; |
230 function getColor(name) { | 343 function getColor(name) { |
231 if (!(name in nameToColorMap)) { | 344 if (!(name in nameToColorMap)) { |
232 var hash = getStringHash(name); | 345 nameToColorMap[name] = getStringColorId(name); |
233 nameToColorMap[name] = hash % numColorIds; | |
234 } | 346 } |
235 return nameToColorMap[name]; | 347 return nameToColorMap[name]; |
236 } | 348 } |
237 | 349 |
238 var self = this; | 350 var self = this; |
239 | 351 |
240 /** | 352 /** |
241 * Helper to process a 'begin' event (e.g. initiate a slice). | 353 * Helper to process a 'begin' event (e.g. initiate a slice). |
242 * @param {ThreadState} state Thread state (holds slices). | 354 * @param {ThreadState} state Thread state (holds slices). |
243 * @param {Object} event The current trace event. | 355 * @param {Object} event The current trace event. |
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
321 | 433 |
322 if (event.ph == 'B') { | 434 if (event.ph == 'B') { |
323 processBegin(state, event); | 435 processBegin(state, event); |
324 } else if (event.ph == 'E') { | 436 } else if (event.ph == 'E') { |
325 processEnd(state, event); | 437 processEnd(state, event); |
326 } else if (event.ph == 'I') { | 438 } else if (event.ph == 'I') { |
327 // Treat an Instant event as a duration 0 slice. | 439 // Treat an Instant event as a duration 0 slice. |
328 // TimelineSliceTrack's redraw() knows how to handle this. | 440 // TimelineSliceTrack's redraw() knows how to handle this. |
329 processBegin(state, event); | 441 processBegin(state, event); |
330 processEnd(state, event); | 442 processEnd(state, event); |
443 } else if (event.ph == 'C') { | |
444 var ctr = this.getOrCreateProcess(event.pid) | |
445 .getOrCreateCounter(event.name); | |
446 // Initialize the counter's series fields if needed. | |
447 if (ctr.numSeries == 0) { | |
448 for (var seriesName in event.args) { | |
449 ctr.seriesNames.push(seriesName); | |
450 ctr.seriesColors.push(getStringHash(ctr.name + '.' + seriesName)); | |
451 } | |
452 if (ctr.numSeries == 0) { | |
453 this.importErrors.push('Expected counter ' + event.name + | |
454 ' to have at least one argument to use as a value.'); | |
455 // Drop the counter. | |
456 delete ctr.parent.counters[ctr.name]; | |
457 continue; | |
458 } | |
459 } | |
460 | |
461 // Add the sample values. | |
462 ctr.timestamps.push(event.ts); | |
463 for (var i = 0; i < ctr.numSeries; i++) { | |
464 var seriesName = ctr.seriesNames[i]; | |
465 if (event.args[seriesName] === undefined) { | |
466 ctr.samples.push(0); | |
467 continue; | |
468 } | |
469 ctr.samples.push(event.args[seriesName]); | |
470 } | |
471 | |
331 } else if (event.ph == 'M') { | 472 } else if (event.ph == 'M') { |
332 if (event.name == 'thread_name') { | 473 if (event.name == 'thread_name') { |
333 var thread = this.getOrCreateProcess(event.pid) | 474 var thread = this.getOrCreateProcess(event.pid) |
334 .getOrCreateThread(event.tid); | 475 .getOrCreateThread(event.tid); |
335 thread.name = event.args.name; | 476 thread.name = event.args.name; |
336 } else { | 477 } else { |
337 this.importErrors.push('Unrecognized metadata name: ' + event.name); | 478 this.importErrors.push('Unrecognized metadata name: ' + event.name); |
338 } | 479 } |
339 } else { | 480 } else { |
340 this.importErrors.push('Unrecognized event phase: ' + event.ph + | 481 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(); | 539 var threads = this.getAllThreads(); |
399 for (var tI = 0; tI < threads.length; tI++) { | 540 for (var tI = 0; tI < threads.length; tI++) { |
400 var thread = threads[tI]; | 541 var thread = threads[tI]; |
401 thread.updateBounds(); | 542 thread.updateBounds(); |
402 if (thread.minTimestamp != undefined && | 543 if (thread.minTimestamp != undefined && |
403 thread.maxTimestamp != undefined) { | 544 thread.maxTimestamp != undefined) { |
404 wmin = Math.min(wmin, thread.minTimestamp); | 545 wmin = Math.min(wmin, thread.minTimestamp); |
405 wmax = Math.max(wmax, thread.maxTimestamp); | 546 wmax = Math.max(wmax, thread.maxTimestamp); |
406 } | 547 } |
407 } | 548 } |
549 var counters = this.getAllCounters(); | |
550 for (var tI = 0; tI < counters.length; tI++) { | |
551 var counter = counters[tI]; | |
552 counter.updateBounds(); | |
553 if (counter.minTimestamp != undefined && | |
554 counter.maxTimestamp != undefined) { | |
555 wmin = Math.min(wmin, counter.minTimestamp); | |
556 wmax = Math.max(wmax, counter.maxTimestamp); | |
557 } | |
558 } | |
408 this.minTimestamp = wmin; | 559 this.minTimestamp = wmin; |
409 this.maxTimestamp = wmax; | 560 this.maxTimestamp = wmax; |
410 }, | 561 }, |
411 | 562 |
412 shiftWorldToMicroseconds: function() { | 563 shiftWorldToMicroseconds: function() { |
413 var timeBase = this.minTimestamp; | 564 var timeBase = this.minTimestamp; |
414 var threads = this.getAllThreads(); | 565 var threads = this.getAllThreads(); |
415 for (var tI = 0; tI < threads.length; tI++) { | 566 for (var tI = 0; tI < threads.length; tI++) { |
416 var thread = threads[tI]; | 567 var thread = threads[tI]; |
417 var shiftSubRow = function(subRow) { | 568 var shiftSubRow = function(subRow) { |
418 for (var tS = 0; tS < subRow.length; tS++) { | 569 for (var tS = 0; tS < subRow.length; tS++) { |
419 var slice = subRow[tS]; | 570 var slice = subRow[tS]; |
420 slice.start = (slice.start - timeBase) / 1000; | 571 slice.start = (slice.start - timeBase) / 1000; |
421 slice.duration /= 1000; | 572 slice.duration /= 1000; |
422 if (slice.startInUserTime) | 573 if (slice.startInUserTime) |
423 slice.startInUserTime /= 1000; | 574 slice.startInUserTime /= 1000; |
424 if (slice.durationInUserTime) | 575 if (slice.durationInUserTime) |
425 slice.durationInUserTime /= 1000; | 576 slice.durationInUserTime /= 1000; |
426 } | 577 } |
427 }; | 578 }; |
428 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { | 579 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { |
429 shiftSubRow(thread.subRows[tSR]); | 580 shiftSubRow(thread.subRows[tSR]); |
430 } | 581 } |
431 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { | 582 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { |
432 shiftSubRow(thread.nonNestedSubRows[tSR]); | 583 shiftSubRow(thread.nonNestedSubRows[tSR]); |
433 } | 584 } |
434 } | 585 } |
435 | 586 var counters = this.getAllCounters(); |
587 for (var tI = 0; tI < counters.length; tI++) { | |
588 var counter = counters[tI]; | |
589 for (var sI = 0; sI < counter.timestamps.length; sI++) | |
590 counter.timestamps[sI] = (counter.timestamps[sI] - timeBase) / 1000; | |
591 } | |
436 this.updateBounds(); | 592 this.updateBounds(); |
437 }, | 593 }, |
438 | 594 |
439 getAllThreads: function() { | 595 getAllThreads: function() { |
440 var threads = []; | 596 var threads = []; |
441 for (var pid in this.processes) { | 597 for (var pid in this.processes) { |
442 var process = this.processes[pid]; | 598 var process = this.processes[pid]; |
443 for (var tid in process.threads) { | 599 for (var tid in process.threads) { |
444 threads.push(process.threads[tid]); | 600 threads.push(process.threads[tid]); |
445 } | 601 } |
446 } | 602 } |
447 return threads; | 603 return threads; |
604 }, | |
605 | |
606 /** | |
607 * @return {Array} An array of all the counters in the model. | |
608 */ | |
609 getAllCounters: function() { | |
610 var counters = []; | |
611 for (var pid in this.processes) { | |
612 var process = this.processes[pid]; | |
613 for (var tid in process.counters) { | |
614 counters.push(process.counters[tid]); | |
615 } | |
616 } | |
617 return counters; | |
448 } | 618 } |
449 | 619 |
450 }; | 620 }; |
451 | 621 |
452 return { | 622 return { |
453 getStringHash: getStringHash, | 623 getStringHash: getStringHash, |
624 getStringColorId: getStringColorId, | |
454 TimelineSlice: TimelineSlice, | 625 TimelineSlice: TimelineSlice, |
455 TimelineThread: TimelineThread, | 626 TimelineThread: TimelineThread, |
627 TimelineCounter: TimelineCounter, | |
456 TimelineProcess: TimelineProcess, | 628 TimelineProcess: TimelineProcess, |
457 TimelineModel: TimelineModel | 629 TimelineModel: TimelineModel |
458 }; | 630 }; |
459 }); | 631 }); |
OLD | NEW |