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 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
113 this.maxTimestamp = undefined; | 113 this.maxTimestamp = undefined; |
114 } | 114 } |
115 } | 115 } |
116 | 116 |
117 }; | 117 }; |
118 | 118 |
119 /** | 119 /** |
120 * Comparison between threads that orders first by pid, | 120 * Comparison between threads that orders first by pid, |
121 * then by names, then by tid. | 121 * then by names, then by tid. |
122 */ | 122 */ |
123 TimelineThread.compare = function(x,y) { | 123 TimelineThread.compare = function(x, y) { |
124 if(x.parent.pid != y.parent.pid) { | 124 if (x.parent.pid != y.parent.pid) { |
125 return x.parent.pid - y.parent.pid; | 125 return x.parent.pid - y.parent.pid; |
126 } | 126 } |
127 | 127 |
128 if (x.name && y.name) { | 128 if (x.name && y.name) { |
129 var tmp = x.name.localeCompare(y.name); | 129 var tmp = x.name.localeCompare(y.name); |
130 if (tmp == 0) | 130 if (tmp == 0) |
131 return x.tid - y.tid; | 131 return x.tid - y.tid; |
132 return tmp; | 132 return tmp; |
133 } else if (x.name) { | 133 } else if (x.name) { |
134 return -1; | 134 return -1; |
135 } else if (y.name){ | 135 } else if (y.name) { |
136 return 1; | 136 return 1; |
137 } else { | 137 } else { |
138 return x.tid - y.tid; | 138 return x.tid - y.tid; |
139 } | 139 } |
140 }; | 140 }; |
141 | 141 |
142 | 142 |
143 /** | 143 /** |
144 * The TimelineProcess represents a single process in the | 144 * The TimelineProcess represents a single process in the |
145 * trace. Right now, we keep this around purely for bookkeeping | 145 * trace. Right now, we keep this around purely for bookkeeping |
146 * reasons. | 146 * reasons. |
147 * @constructor | 147 * @constructor |
148 */ | 148 */ |
149 function TimelineProcess(pid) { | 149 function TimelineProcess(pid) { |
150 this.pid = pid; | 150 this.pid = pid; |
151 this.threads = {}; | 151 this.threads = {}; |
152 }; | 152 }; |
153 | 153 |
154 TimelineProcess.prototype = { | 154 TimelineProcess.prototype = { |
155 getThread: function(tid) { | 155 get numThreads() { |
| 156 var n = 0; |
| 157 for (var p in this.threads) { |
| 158 n++; |
| 159 } |
| 160 return n; |
| 161 }, |
| 162 |
| 163 getOrCreateThread: function(tid) { |
156 if (!this.threads[tid]) | 164 if (!this.threads[tid]) |
157 this.threads[tid] = new TimelineThread(this, tid); | 165 this.threads[tid] = new TimelineThread(this, tid); |
158 return this.threads[tid]; | 166 return this.threads[tid]; |
159 } | 167 } |
160 }; | 168 }; |
161 | 169 |
162 /** | 170 /** |
| 171 * Computes a simplistic hashcode of the provide name. Used to chose colors |
| 172 * for slices. |
| 173 * @param {string} name The string to hash. |
| 174 */ |
| 175 function getStringHash(name) { |
| 176 var hash = 0; |
| 177 for (var i = 0; i < name.length; ++i) |
| 178 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; |
| 179 return hash; |
| 180 } |
| 181 |
| 182 /** |
163 * Builds a model from an array of TraceEvent objects. | 183 * Builds a model from an array of TraceEvent objects. |
164 * @param {Array} events An array of TraceEvents created by | 184 * @param {Array} events An array of TraceEvents created by |
165 * TraceEvent.ToJSON(). | 185 * TraceEvent.ToJSON(). |
166 * @constructor | 186 * @constructor |
167 */ | 187 */ |
168 function TimelineModel(events) { | 188 function TimelineModel(events) { |
169 this.processes = {}; | 189 this.processes = {}; |
170 this.importErrors = []; | 190 this.importErrors = []; |
171 | 191 |
172 if (events) | 192 if (events) |
173 this.importEvents(events); | 193 this.importEvents(events); |
174 } | 194 } |
175 | 195 |
176 TimelineModel.prototype = { | 196 TimelineModel.prototype = { |
177 __proto__: cr.EventTarget.prototype, | 197 __proto__: cr.EventTarget.prototype, |
178 | 198 |
179 getProcess: function(pid) { | 199 get numProcesses() { |
| 200 var n = 0; |
| 201 for (var p in this.processes) |
| 202 n++; |
| 203 return n; |
| 204 }, |
| 205 |
| 206 getOrCreateProcess: function(pid) { |
180 if (!this.processes[pid]) | 207 if (!this.processes[pid]) |
181 this.processes[pid] = new TimelineProcess(pid); | 208 this.processes[pid] = new TimelineProcess(pid); |
182 return this.processes[pid]; | 209 return this.processes[pid]; |
183 }, | 210 }, |
184 | 211 |
185 /** | 212 /** |
186 * The import takes an array of json-ified TraceEvents and adds them into | 213 * The import takes an array of json-ified TraceEvents and adds them into |
187 * the TimelineModel as processes, threads, and slices. | 214 * the TimelineModel as processes, threads, and slices. |
188 */ | 215 */ |
189 importEvents: function(events) { | 216 importEvents: function(events) { |
190 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 | 217 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 |
191 // The ptid is a unique key for a thread in the trace. | 218 // The ptid is a unique key for a thread in the trace. |
192 this.importErrors = []; | 219 this.importErrors = []; |
193 | 220 |
194 // Threadstate | 221 // Threadstate |
195 const numColorIds = 30; | 222 const numColorIds = 30; |
196 function ThreadState(tid) { | 223 function ThreadState(tid) { |
197 this.openSlices = []; | 224 this.openSlices = []; |
198 this.openNonNestedSlices = {}; | 225 this.openNonNestedSlices = {}; |
199 } | 226 } |
200 var threadStateByPTID = {}; | 227 var threadStateByPTID = {}; |
201 | 228 |
202 var nameToColorMap = {}; | 229 var nameToColorMap = {}; |
203 function getColor(name) { | 230 function getColor(name) { |
204 if (!(name in nameToColorMap)) { | 231 if (!(name in nameToColorMap)) { |
205 // Compute a simplistic hashcode of the string so we get consistent | 232 var hash = getStringHash(name); |
206 // coloring across traces. | |
207 var hash = 0; | |
208 for (var i = 0; i < name.length; ++i) | |
209 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | |
210 nameToColorMap[name] = hash % numColorIds; | 233 nameToColorMap[name] = hash % numColorIds; |
211 } | 234 } |
212 return nameToColorMap[name]; | 235 return nameToColorMap[name]; |
213 } | 236 } |
214 | 237 |
215 var self = this; | 238 var self = this; |
216 | 239 |
217 /** | 240 /** |
218 * Helper to process a 'begin' event (e.g. initiate a slice). | 241 * Helper to process a 'begin' event (e.g. initiate a slice). |
219 * @param {ThreadState} state Thread state (holds slices). | 242 * @param {ThreadState} state Thread state (holds slices). |
(...skipping 26 matching lines...) Expand all Loading... |
246 if (event.args['ui-nest'] === '0') { | 269 if (event.args['ui-nest'] === '0') { |
247 var sliceID = event.name; | 270 var sliceID = event.name; |
248 for (var x in event.args) | 271 for (var x in event.args) |
249 sliceID += ';' + event.args[x]; | 272 sliceID += ';' + event.args[x]; |
250 var slice = state.openNonNestedSlices[sliceID]; | 273 var slice = state.openNonNestedSlices[sliceID]; |
251 if (!slice) | 274 if (!slice) |
252 return; | 275 return; |
253 slice.slice.duration = event.ts - slice.slice.start; | 276 slice.slice.duration = event.ts - slice.slice.start; |
254 | 277 |
255 // Store the slice in a non-nested subrow. | 278 // Store the slice in a non-nested subrow. |
256 var thread = self.getProcess(event.pid).getThread(event.tid); | 279 var thread = self.getOrCreateProcess(event.pid). |
| 280 getOrCreateThread(event.tid); |
257 thread.addNonNestedSlice(slice.slice); | 281 thread.addNonNestedSlice(slice.slice); |
258 delete state.openNonNestedSlices[name]; | 282 delete state.openNonNestedSlices[name]; |
259 } else { | 283 } else { |
260 if (state.openSlices.length == 0) { | 284 if (state.openSlices.length == 0) { |
261 // Ignore E events that are unmatched. | 285 // Ignore E events that are unmatched. |
262 return; | 286 return; |
263 } | 287 } |
264 var slice = state.openSlices.pop().slice; | 288 var slice = state.openSlices.pop().slice; |
265 slice.duration = event.ts - slice.start; | 289 slice.duration = event.ts - slice.start; |
266 | 290 |
267 // Store the slice on the correct subrow. | 291 // Store the slice on the correct subrow. |
268 var thread = self.getProcess(event.pid).getThread(event.tid); | 292 var thread = self.getOrCreateProcess(event.pid) |
| 293 .getOrCreateThread(event.tid); |
269 var subRowIndex = state.openSlices.length; | 294 var subRowIndex = state.openSlices.length; |
270 thread.getSubrow(subRowIndex).push(slice); | 295 thread.getSubrow(subRowIndex).push(slice); |
271 | 296 |
272 // Add the slice to the subSlices array of its parent. | 297 // Add the slice to the subSlices array of its parent. |
273 if (state.openSlices.length) { | 298 if (state.openSlices.length) { |
274 var parentSlice = state.openSlices[state.openSlices.length - 1]; | 299 var parentSlice = state.openSlices[state.openSlices.length - 1]; |
275 parentSlice.slice.subSlices.push(slice); | 300 parentSlice.slice.subSlices.push(slice); |
276 } | 301 } |
277 } | 302 } |
278 } | 303 } |
(...skipping 11 matching lines...) Expand all Loading... |
290 processBegin(state, event); | 315 processBegin(state, event); |
291 } else if (event.ph == 'E') { | 316 } else if (event.ph == 'E') { |
292 processEnd(state, event); | 317 processEnd(state, event); |
293 } else if (event.ph == 'I') { | 318 } else if (event.ph == 'I') { |
294 // Treat an Instant event as a duration 0 slice. | 319 // Treat an Instant event as a duration 0 slice. |
295 // TimelineSliceTrack's redraw() knows how to handle this. | 320 // TimelineSliceTrack's redraw() knows how to handle this. |
296 processBegin(state, event); | 321 processBegin(state, event); |
297 processEnd(state, event); | 322 processEnd(state, event); |
298 } else if (event.ph == 'M') { | 323 } else if (event.ph == 'M') { |
299 if (event.name == 'thread_name') { | 324 if (event.name == 'thread_name') { |
300 var thread = this.getProcess(event.pid).getThread(event.tid); | 325 var thread = this.getOrCreateProcess(event.pid) |
| 326 .getOrCreateThread(event.tid); |
301 thread.name = event.args.name; | 327 thread.name = event.args.name; |
302 } else { | 328 } else { |
303 this.importErrors.push('Unrecognized metadata name: ' + event.name); | 329 this.importErrors.push('Unrecognized metadata name: ' + event.name); |
304 } | 330 } |
305 } else { | 331 } else { |
306 this.importErrors.push('Unrecognized event phase: ' + event.ph + | 332 this.importErrors.push('Unrecognized event phase: ' + event.ph + |
307 '(' + event.name + ')'); | 333 '(' + event.name + ')'); |
308 } | 334 } |
309 } | 335 } |
310 this.pruneEmptyThreads(); | 336 this.pruneEmptyThreads(); |
311 this.updateBounds(); | 337 this.updateBounds(); |
312 | 338 |
313 // Add end events for any events that are still on the stack. These | 339 // Add end events for any events that are still on the stack. These |
314 // are events that were still open when trace was ended, and can often | 340 // are events that were still open when trace was ended, and can often |
315 // indicate deadlock behavior. | 341 // indicate deadlock behavior. |
316 for (var ptid in threadStateByPTID) { | 342 for (var ptid in threadStateByPTID) { |
317 var state = threadStateByPTID[ptid]; | 343 var state = threadStateByPTID[ptid]; |
318 while (state.openSlices.length > 0) { | 344 while (state.openSlices.length > 0) { |
319 var slice = state.openSlices.pop(); | 345 var slice = state.openSlices.pop(); |
320 slice.slice.duration = this.maxTimestamp - slice.slice.start; | 346 slice.slice.duration = this.maxTimestamp - slice.slice.start; |
321 slice.slice.didNotFinish = true; | 347 slice.slice.didNotFinish = true; |
322 var event = events[slice.index]; | 348 var event = events[slice.index]; |
323 | 349 |
324 // Store the slice on the correct subrow. | 350 // Store the slice on the correct subrow. |
325 var thread = this.getProcess(event.pid).getThread(event.tid); | 351 var thread = this.getOrCreateProcess(event.pid) |
| 352 .getOrCreateThread(event.tid); |
326 var subRowIndex = state.openSlices.length; | 353 var subRowIndex = state.openSlices.length; |
327 thread.getSubrow(subRowIndex).push(slice.slice); | 354 thread.getSubrow(subRowIndex).push(slice.slice); |
328 | 355 |
329 // Add the slice to the subSlices array of its parent. | 356 // Add the slice to the subSlices array of its parent. |
330 if (state.openSlices.length) { | 357 if (state.openSlices.length) { |
331 var parentSlice = state.openSlices[state.openSlices.length - 1]; | 358 var parentSlice = state.openSlices[state.openSlices.length - 1]; |
332 parentSlice.slice.subSlices.push(slice.slice); | 359 parentSlice.slice.subSlices.push(slice.slice); |
333 } | 360 } |
334 } | 361 } |
335 } | 362 } |
336 | 363 |
337 this.shiftWorldToMicroseconds(); | 364 this.shiftWorldToMicroseconds(); |
338 | 365 |
339 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | 366 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; |
340 this.minTimestamp = this.minTimestamp - boost; | 367 this.minTimestamp = this.minTimestamp - boost; |
341 this.maxTimestamp = this.maxTimestamp + boost; | 368 this.maxTimestamp = this.maxTimestamp + boost; |
342 }, | 369 }, |
343 | 370 |
344 /** | 371 /** |
345 * Removes threads from the model that have no subrows. | 372 * Removes threads from the model that have no subrows. |
346 */ | 373 */ |
347 pruneEmptyThreads: function() { | 374 pruneEmptyThreads: function() { |
348 for (var pid in this.processes) { | 375 for (var pid in this.processes) { |
349 var process = this.processes[pid]; | 376 var process = this.processes[pid]; |
350 var prunedThreads = []; | 377 var prunedThreads = {}; |
351 for (var tid in process.threads) { | 378 for (var tid in process.threads) { |
352 var thread = process.threads[tid]; | 379 var thread = process.threads[tid]; |
353 if (thread.subRows[0].length || thread.nonNestedSubRows.legnth) | 380 if (thread.subRows[0].length || thread.nonNestedSubRows.legnth) |
354 prunedThreads.push(thread); | 381 prunedThreads[tid] = thread; |
355 } | 382 } |
356 process.threads = prunedThreads; | 383 process.threads = prunedThreads; |
357 } | 384 } |
358 }, | 385 }, |
359 | 386 |
360 updateBounds: function() { | 387 updateBounds: function() { |
361 var wmin = Infinity; | 388 var wmin = Infinity; |
362 var wmax = -wmin; | 389 var wmax = -wmin; |
363 var threads = this.getAllThreads(); | 390 var threads = this.getAllThreads(); |
364 for (var tI = 0; tI < threads.length; tI++) { | 391 for (var tI = 0; tI < threads.length; tI++) { |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 for (var tid in process.threads) { | 431 for (var tid in process.threads) { |
405 threads.push(process.threads[tid]); | 432 threads.push(process.threads[tid]); |
406 } | 433 } |
407 } | 434 } |
408 return threads; | 435 return threads; |
409 } | 436 } |
410 | 437 |
411 }; | 438 }; |
412 | 439 |
413 return { | 440 return { |
| 441 getStringHash: getStringHash, |
414 TimelineSlice: TimelineSlice, | 442 TimelineSlice: TimelineSlice, |
415 TimelineThread: TimelineThread, | 443 TimelineThread: TimelineThread, |
416 TimelineProcess: TimelineProcess, | 444 TimelineProcess: TimelineProcess, |
417 TimelineModel: TimelineModel | 445 TimelineModel: TimelineModel |
418 }; | 446 }; |
419 }); | 447 }); |
OLD | NEW |