model.html revision 46b43bff003ceda46cf9a5d40a47f7674996d2e0
1<!DOCTYPE html> 2<!-- 3Copyright (c) 2012 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/base.html"> 9<link rel="import" href="/tracing/base/event.html"> 10<link rel="import" href="/tracing/base/interval_tree.html"> 11<link rel="import" href="/tracing/base/range.html"> 12<link rel="import" href="/tracing/base/task.html"> 13<link rel="import" href="/tracing/base/units/units.html"> 14<link rel="import" href="/tracing/core/auditor.html"> 15<link rel="import" href="/tracing/core/filter.html"> 16<link rel="import" href="/tracing/model/alert.html"> 17<link rel="import" href="/tracing/model/device.html"> 18<link rel="import" href="/tracing/model/flow_event.html"> 19<link rel="import" href="/tracing/model/frame.html"> 20<link rel="import" href="/tracing/model/global_memory_dump.html"> 21<link rel="import" href="/tracing/model/instant_event.html"> 22<link rel="import" href="/tracing/model/interaction_record.html"> 23<link rel="import" href="/tracing/model/kernel.html"> 24<link rel="import" href="/tracing/model/model_indices.html"> 25<link rel="import" href="/tracing/model/process.html"> 26<link rel="import" href="/tracing/model/process_memory_dump.html"> 27<link rel="import" href="/tracing/model/sample.html"> 28<link rel="import" href="/tracing/model/stack_frame.html"> 29<link rel="import" href="/tracing/ui/base/overlay.html"> 30 31<script> 32'use strict'; 33 34/** 35 * @fileoverview Model is a parsed representation of the 36 * TraceEvents obtained from base/trace_event in which the begin-end 37 * tokens are converted into a hierarchy of processes, threads, 38 * subrows, and slices. 39 * 40 * The building block of the model is a slice. A slice is roughly 41 * equivalent to function call executing on a specific thread. As a 42 * result, slices may have one or more subslices. 43 * 44 * A thread contains one or more subrows of slices. Row 0 corresponds to 45 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that 46 * are nested 1 deep in the stack, and so on. We use these subrows to draw 47 * nesting tasks. 48 * 49 */ 50tr.exportTo('tr', function() { 51 var Process = tr.model.Process; 52 var Device = tr.model.Device; 53 var Kernel = tr.model.Kernel; 54 var GlobalMemoryDump = tr.model.GlobalMemoryDump; 55 var GlobalInstantEvent = tr.model.GlobalInstantEvent; 56 var FlowEvent = tr.model.FlowEvent; 57 var Alert = tr.model.Alert; 58 var InteractionRecord = tr.model.InteractionRecord; 59 var Sample = tr.model.Sample; 60 61 function ClockSyncRecord(name, ts, args) { 62 this.name = name; 63 this.ts = ts; 64 this.args = args; 65 } 66 67 /** 68 * @constructor 69 */ 70 function Model() { 71 tr.model.EventContainer.call(this); 72 tr.b.EventTarget.decorate(this); 73 74 this.timestampShiftToZeroAmount_ = 0; 75 76 this.faviconHue = 'blue'; // Should be a key from favicons.html 77 78 this.device = new Device(this); 79 this.kernel = new Kernel(this); 80 this.processes = {}; 81 this.metadata = []; 82 this.categories = []; 83 this.instantEvents = []; 84 this.flowEvents = []; 85 this.clockSyncRecords = []; 86 this.intrinsicTimeUnit_ = undefined; 87 88 this.stackFrames = {}; 89 this.samples = []; 90 91 this.alerts = []; 92 this.interactionRecords = []; 93 94 this.flowIntervalTree = new tr.b.IntervalTree( 95 function(f) { return f.start; }, 96 function(f) { return f.end; }); 97 98 this.globalMemoryDumps = []; 99 100 this.annotationsByGuid_ = {}; 101 this.modelIndices = undefined; 102 103 this.importWarnings_ = []; 104 this.reportedImportWarnings_ = {}; 105 } 106 107 Model.prototype = { 108 __proto__: tr.model.EventContainer.prototype, 109 110 iterateAllEventsInThisContainer: function(eventTypePredicate, 111 callback, opt_this) { 112 if (eventTypePredicate.call(opt_this, GlobalMemoryDump)) 113 this.globalMemoryDumps.forEach(callback, opt_this); 114 115 if (eventTypePredicate.call(opt_this, GlobalInstantEvent)) 116 this.instantEvents.forEach(callback, opt_this); 117 118 if (eventTypePredicate.call(opt_this, FlowEvent)) 119 this.flowEvents.forEach(callback, opt_this); 120 121 if (eventTypePredicate.call(opt_this, Alert)) 122 this.alerts.forEach(callback, opt_this); 123 124 if (eventTypePredicate.call(opt_this, InteractionRecord)) 125 this.interactionRecords.forEach(callback, opt_this); 126 127 if (eventTypePredicate.call(opt_this, Sample)) 128 this.samples.forEach(callback, opt_this); 129 }, 130 131 iterateAllChildEventContainers: function(callback, opt_this) { 132 callback.call(opt_this, this.device); 133 callback.call(opt_this, this.kernel); 134 for (var pid in this.processes) 135 callback.call(opt_this, this.processes[pid]); 136 }, 137 138 /** 139 * Some objects in the model can persist their state in ModelSettings. 140 * 141 * This iterates through them. 142 */ 143 iterateAllPersistableObjects: function(callback) { 144 this.kernel.iterateAllPersistableObjects(callback); 145 for (var pid in this.processes) 146 this.processes[pid].iterateAllPersistableObjects(callback); 147 }, 148 149 updateBounds: function() { 150 this.bounds.reset(); 151 var bounds = this.bounds; 152 153 this.iterateAllChildEventContainers(function(ec) { 154 ec.updateBounds(); 155 bounds.addRange(ec.bounds); 156 }); 157 this.iterateAllEventsInThisContainer( 158 function(eventConstructor) { return true; }, 159 function(event) { 160 event.addBoundsToRange(bounds); 161 }); 162 }, 163 164 shiftWorldToZero: function() { 165 var shiftAmount = -this.bounds.min; 166 this.timestampShiftToZeroAmount_ = shiftAmount; 167 this.iterateAllChildEventContainers(function(ec) { 168 ec.shiftTimestampsForward(shiftAmount); 169 }); 170 this.iterateAllEventsInThisContainer( 171 function(eventConstructor) { return true; }, 172 function(event) { 173 event.start += shiftAmount; 174 }); 175 this.updateBounds(); 176 }, 177 178 convertTimestampToModelTime: function(sourceClockDomainName, ts) { 179 if (sourceClockDomainName !== 'traceEventClock') 180 throw new Error('Only traceEventClock is supported.'); 181 return tr.b.u.Units.timestampFromUs(ts) + 182 this.timestampShiftToZeroAmount_; 183 }, 184 185 get numProcesses() { 186 var n = 0; 187 for (var p in this.processes) 188 n++; 189 return n; 190 }, 191 192 /** 193 * @return {Process} Gets a TimelineProcess for a specified pid. Returns 194 * undefined if the process doesn't exist. 195 */ 196 getProcess: function(pid) { 197 return this.processes[pid]; 198 }, 199 200 /** 201 * @return {Process} Gets a TimelineProcess for a specified pid or 202 * creates one if it does not exist. 203 */ 204 getOrCreateProcess: function(pid) { 205 if (!this.processes[pid]) 206 this.processes[pid] = new Process(this, pid); 207 return this.processes[pid]; 208 }, 209 210 pushInstantEvent: function(instantEvent) { 211 this.instantEvents.push(instantEvent); 212 }, 213 214 addStackFrame: function(stackFrame) { 215 if (this.stackFrames[stackFrame.id]) 216 throw new Error('Stack frame already exists'); 217 this.stackFrames[stackFrame.id] = stackFrame; 218 return stackFrame; 219 }, 220 221 addInteractionRecord: function(ir) { 222 this.interactionRecords.push(ir); 223 return ir; 224 }, 225 226 getClockSyncRecordsNamed: function(name) { 227 return this.clockSyncRecords.filter(function(x) { 228 return x.name === name; 229 }); 230 }, 231 232 /** 233 * Generates the set of categories from the slices and counters. 234 */ 235 updateCategories_: function() { 236 var categoriesDict = {}; 237 this.device.addCategoriesToDict(categoriesDict); 238 this.kernel.addCategoriesToDict(categoriesDict); 239 for (var pid in this.processes) 240 this.processes[pid].addCategoriesToDict(categoriesDict); 241 242 this.categories = []; 243 for (var category in categoriesDict) 244 if (category != '') 245 this.categories.push(category); 246 }, 247 248 getAllThreads: function() { 249 var threads = []; 250 for (var tid in this.kernel.threads) { 251 threads.push(process.threads[tid]); 252 } 253 for (var pid in this.processes) { 254 var process = this.processes[pid]; 255 for (var tid in process.threads) { 256 threads.push(process.threads[tid]); 257 } 258 } 259 return threads; 260 }, 261 262 /** 263 * @return {Array} An array of all processes in the model. 264 */ 265 getAllProcesses: function() { 266 var processes = []; 267 for (var pid in this.processes) 268 processes.push(this.processes[pid]); 269 return processes; 270 }, 271 272 /** 273 * @return {Array} An array of all the counters in the model. 274 */ 275 getAllCounters: function() { 276 var counters = []; 277 counters.push.apply( 278 counters, tr.b.dictionaryValues(this.device.counters)); 279 counters.push.apply( 280 counters, tr.b.dictionaryValues(this.kernel.counters)); 281 for (var pid in this.processes) { 282 var process = this.processes[pid]; 283 for (var tid in process.counters) { 284 counters.push(process.counters[tid]); 285 } 286 } 287 return counters; 288 }, 289 290 getAnnotationByGUID: function(guid) { 291 return this.annotationsByGuid_[guid]; 292 }, 293 294 addAnnotation: function(annotation) { 295 if (!annotation.guid) 296 throw new Error('Annotation with undefined guid given'); 297 298 this.annotationsByGuid_[annotation.guid] = annotation; 299 tr.b.dispatchSimpleEvent(this, 'annotationChange'); 300 }, 301 302 removeAnnotation: function(annotation) { 303 this.annotationsByGuid_[annotation.guid].onRemove(); 304 delete this.annotationsByGuid_[annotation.guid]; 305 tr.b.dispatchSimpleEvent(this, 'annotationChange'); 306 }, 307 308 getAllAnnotations: function() { 309 return tr.b.dictionaryValues(this.annotationsByGuid_); 310 }, 311 312 /** 313 * @param {String} The name of the thread to find. 314 * @return {Array} An array of all the matched threads. 315 */ 316 findAllThreadsNamed: function(name) { 317 var namedThreads = []; 318 namedThreads.push.apply( 319 namedThreads, 320 this.kernel.findAllThreadsNamed(name)); 321 for (var pid in this.processes) { 322 namedThreads.push.apply( 323 namedThreads, 324 this.processes[pid].findAllThreadsNamed(name)); 325 } 326 return namedThreads; 327 }, 328 329 set importOptions(options) { 330 this.importOptions_ = options; 331 }, 332 333 /** 334 * Returns a time unit that is used to format values and determines the 335 * precision of the timestamp values. 336 */ 337 get intrinsicTimeUnit() { 338 if (this.intrinsicTimeUnit_ === undefined) 339 return tr.b.u.TimeDisplayModes.ms; 340 return this.intrinsicTimeUnit_; 341 }, 342 343 set intrinsicTimeUnit(value) { 344 if (this.intrinsicTimeUnit_ === value) 345 return; 346 if (this.intrinsicTimeUnit_ !== undefined) 347 throw new Error('Intrinsic time unit already set'); 348 this.intrinsicTimeUnit_ = value; 349 }, 350 351 /** 352 * @param {Object} data The import warning data. Data must provide two 353 * accessors: type, message. The types are used to determine if we 354 * should output the message, we'll only output one message of each type. 355 * The message is the actual warning content. 356 */ 357 importWarning: function(data) { 358 this.importWarnings_.push(data); 359 360 // Only log each warning type once. We may want to add some kind of 361 // flag to allow reporting all importer warnings. 362 if (this.reportedImportWarnings_[data.type] === true) 363 return; 364 365 if (this.importOptions_.showImportWarnings) 366 console.warn(data.message); 367 368 this.reportedImportWarnings_[data.type] = true; 369 }, 370 371 get hasImportWarnings() { 372 return (this.importWarnings_.length > 0); 373 }, 374 375 get importWarnings() { 376 return this.importWarnings_; 377 }, 378 379 autoCloseOpenSlices: function() { 380 // Sort the samples. 381 this.samples.sort(function(x, y) { 382 return x.start - y.start; 383 }); 384 385 this.updateBounds(); 386 this.kernel.autoCloseOpenSlices(this.bounds.max); 387 for (var pid in this.processes) 388 this.processes[pid].autoCloseOpenSlices(this.bounds.max); 389 }, 390 391 createSubSlices: function() { 392 this.kernel.createSubSlices(); 393 for (var pid in this.processes) 394 this.processes[pid].createSubSlices(); 395 }, 396 397 preInitializeObjects: function() { 398 for (var pid in this.processes) 399 this.processes[pid].preInitializeObjects(); 400 }, 401 402 initializeObjects: function() { 403 for (var pid in this.processes) 404 this.processes[pid].initializeObjects(); 405 }, 406 407 pruneEmptyContainers: function() { 408 this.kernel.pruneEmptyContainers(); 409 for (var pid in this.processes) 410 this.processes[pid].pruneEmptyContainers(); 411 }, 412 413 mergeKernelWithUserland: function() { 414 for (var pid in this.processes) 415 this.processes[pid].mergeKernelWithUserland(); 416 }, 417 418 computeWorldBounds: function(shiftWorldToZero) { 419 this.updateBounds(); 420 this.updateCategories_(); 421 422 if (shiftWorldToZero) 423 this.shiftWorldToZero(); 424 }, 425 426 buildFlowEventIntervalTree: function() { 427 for (var i = 0; i < this.flowEvents.length; ++i) { 428 var flowEvent = this.flowEvents[i]; 429 this.flowIntervalTree.insert(flowEvent); 430 } 431 this.flowIntervalTree.updateHighValues(); 432 }, 433 434 cleanupUndeletedObjects: function() { 435 for (var pid in this.processes) 436 this.processes[pid].autoDeleteObjects(this.bounds.max); 437 }, 438 439 sortMemoryDumps: function() { 440 this.globalMemoryDumps.sort(function(x, y) { 441 return x.start - y.start; 442 }); 443 444 for (var pid in this.processes) 445 this.processes[pid].sortMemoryDumps(); 446 }, 447 448 calculateMemoryGraphAttributes: function() { 449 this.globalMemoryDumps.forEach(function(dump) { 450 dump.calculateGraphAttributes(); 451 }); 452 }, 453 454 buildEventIndices: function() { 455 this.modelIndices = new tr.model.ModelIndices(this); 456 }, 457 458 sortInteractionRecords: function() { 459 this.interactionRecords.sort(function(x, y) { 460 return x.start - y.start; 461 }); 462 }, 463 464 sortAlerts: function() { 465 this.alerts.sort(function(x, y) { 466 return x.start - y.start; 467 }); 468 } 469 }; 470 471 return { 472 ClockSyncRecord: ClockSyncRecord, 473 Model: Model 474 }; 475}); 476</script> 477