1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 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/model/event_container.html"> 9<link rel="import" href="/tracing/model/async_slice_group.html"> 10<link rel="import" href="/tracing/model/slice_group.html"> 11<link rel="import" href="/tracing/model/thread_slice.html"> 12<link rel="import" href="/tracing/base/guid.html"> 13<link rel="import" href="/tracing/base/range.html"> 14 15<script> 16'use strict'; 17 18/** 19 * @fileoverview Provides the Thread class. 20 */ 21tr.exportTo('tr.model', function() { 22 var AsyncSlice = tr.model.AsyncSlice; 23 var AsyncSliceGroup = tr.model.AsyncSliceGroup; 24 var Slice = tr.model.Slice; 25 var SliceGroup = tr.model.SliceGroup; 26 var ThreadSlice = tr.model.ThreadSlice; 27 var ThreadTimeSlice = tr.model.ThreadTimeSlice; 28 29 /** 30 * A Thread stores all the trace events collected for a particular 31 * thread. We organize the synchronous slices on a thread by "subrows," where 32 * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on. 33 * The asynchronous slices are stored in an AsyncSliceGroup object. 34 * 35 * The slices stored on a Thread should be instances of 36 * ThreadSlice. 37 * 38 * @constructor 39 * @extends {tr.model.EventContainer} 40 */ 41 function Thread(parent, tid) { 42 if (!parent) 43 throw new Error('Parent must be provided.'); 44 45 tr.model.EventContainer.call(this); 46 this.parent = parent; 47 this.sortIndex = 0; 48 this.tid = tid; 49 this.name = undefined; 50 this.samples_ = undefined; // Set during createSubSlices 51 52 var that = this; 53 54 this.sliceGroup = new SliceGroup(this, ThreadSlice, 'slices'); 55 this.timeSlices = undefined; 56 this.kernelSliceGroup = new SliceGroup( 57 this, ThreadSlice, 'kernel-slices'); 58 this.asyncSliceGroup = new AsyncSliceGroup(this, 'async-slices'); 59 } 60 61 Thread.prototype = { 62 __proto__: tr.model.EventContainer.prototype, 63 64 get model() { 65 return this.parent.model; 66 }, 67 68 get stableId() { 69 return this.parent.stableId + '.' + this.tid; 70 }, 71 72 compareTo: function(that) { 73 return Thread.compare(this, that); 74 }, 75 76 iterateAllChildEventContainers: function(callback, opt_this) { 77 if (this.sliceGroup.length) 78 callback.call(opt_this, this.sliceGroup); 79 if (this.kernelSliceGroup.length) 80 callback.call(opt_this, this.kernelSliceGroup); 81 if (this.asyncSliceGroup.length) 82 callback.call(opt_this, this.asyncSliceGroup); 83 }, 84 85 iterateAllEventsInThisContainer: function(eventTypePredicate, 86 callback, opt_this) { 87 if (this.timeSlices && this.timeSlices.length) { 88 if (eventTypePredicate.call(opt_this, ThreadTimeSlice)) 89 this.timeSlices.forEach(callback, opt_this); 90 } 91 }, 92 93 iterateAllPersistableObjects: function(cb) { 94 cb(this); 95 if (this.sliceGroup.length) 96 cb(this.sliceGroup); 97 this.asyncSliceGroup.viewSubGroups.forEach(cb); 98 }, 99 100 /** 101 * Shifts all the timestamps inside this thread forward by the amount 102 * specified. 103 */ 104 shiftTimestampsForward: function(amount) { 105 this.sliceGroup.shiftTimestampsForward(amount); 106 107 if (this.timeSlices) { 108 for (var i = 0; i < this.timeSlices.length; i++) { 109 var slice = this.timeSlices[i]; 110 slice.start += amount; 111 } 112 } 113 114 this.kernelSliceGroup.shiftTimestampsForward(amount); 115 this.asyncSliceGroup.shiftTimestampsForward(amount); 116 }, 117 118 /** 119 * Determines whether this thread is empty. If true, it usually implies 120 * that it should be pruned from the model. 121 */ 122 get isEmpty() { 123 if (this.sliceGroup.length) 124 return false; 125 if (this.sliceGroup.openSliceCount) 126 return false; 127 if (this.timeSlices && this.timeSlices.length) 128 return false; 129 if (this.kernelSliceGroup.length) 130 return false; 131 if (this.asyncSliceGroup.length) 132 return false; 133 if (this.samples_.length) 134 return false; 135 return true; 136 }, 137 138 /** 139 * Updates the bounds based on the 140 * current objects associated with the thread. 141 */ 142 updateBounds: function() { 143 this.bounds.reset(); 144 145 this.sliceGroup.updateBounds(); 146 this.bounds.addRange(this.sliceGroup.bounds); 147 148 this.kernelSliceGroup.updateBounds(); 149 this.bounds.addRange(this.kernelSliceGroup.bounds); 150 151 this.asyncSliceGroup.updateBounds(); 152 this.bounds.addRange(this.asyncSliceGroup.bounds); 153 154 if (this.timeSlices && this.timeSlices.length) { 155 this.bounds.addValue(this.timeSlices[0].start); 156 this.bounds.addValue( 157 this.timeSlices[this.timeSlices.length - 1].end); 158 } 159 160 if (this.samples_ && this.samples_.length) { 161 this.bounds.addValue(this.samples_[0].start); 162 this.bounds.addValue( 163 this.samples_[this.samples_.length - 1].end); 164 } 165 }, 166 167 addCategoriesToDict: function(categoriesDict) { 168 for (var i = 0; i < this.sliceGroup.length; i++) 169 categoriesDict[this.sliceGroup.slices[i].category] = true; 170 for (var i = 0; i < this.kernelSliceGroup.length; i++) 171 categoriesDict[this.kernelSliceGroup.slices[i].category] = true; 172 for (var i = 0; i < this.asyncSliceGroup.length; i++) 173 categoriesDict[this.asyncSliceGroup.slices[i].category] = true; 174 if (this.samples_) { 175 for (var i = 0; i < this.samples_.length; i++) 176 categoriesDict[this.samples_[i].category] = true; 177 } 178 }, 179 180 autoCloseOpenSlices: function() { 181 this.sliceGroup.autoCloseOpenSlices(); 182 this.kernelSliceGroup.autoCloseOpenSlices(); 183 }, 184 185 mergeKernelWithUserland: function() { 186 if (this.kernelSliceGroup.length > 0) { 187 var newSlices = SliceGroup.merge( 188 this.sliceGroup, this.kernelSliceGroup); 189 this.sliceGroup.slices = newSlices.slices; 190 this.kernelSliceGroup = new SliceGroup(this); 191 this.updateBounds(); 192 } 193 }, 194 195 createSubSlices: function() { 196 this.sliceGroup.createSubSlices(); 197 this.samples_ = this.parent.model.samples.filter(function(sample) { 198 return sample.thread == this; 199 }, this); 200 }, 201 202 /** 203 * @return {String} A user-friendly name for this thread. 204 */ 205 get userFriendlyName() { 206 return this.name || this.tid; 207 }, 208 209 /** 210 * @return {String} User friendly details about this thread. 211 */ 212 get userFriendlyDetails() { 213 return 'tid: ' + this.tid + 214 (this.name ? ', name: ' + this.name : ''); 215 }, 216 217 getSettingsKey: function() { 218 if (!this.name) 219 return undefined; 220 var parentKey = this.parent.getSettingsKey(); 221 if (!parentKey) 222 return undefined; 223 return parentKey + '.' + this.name; 224 }, 225 226 getProcess: function() { 227 return this.parent; 228 }, 229 230 /* 231 * Returns the index of the slice in the timeSlices array, or undefined. 232 */ 233 indexOfTimeSlice: function(timeSlice) { 234 var i = tr.b.findLowIndexInSortedArray( 235 this.timeSlices, 236 function(slice) { return slice.start; }, 237 timeSlice.start); 238 if (this.timeSlices[i] !== timeSlice) 239 return undefined; 240 return i; 241 }, 242 243 /* 244 * Returns an object with the CPU number used as keys, 245 * and the value of each key object is the amount of milliseconds spent 246 * running on this CPU. 247 * Additionally, stats.total contains the total time 248 * spent running on all CPUs. 249 */ 250 getCpuStatsForRange: function(range) { 251 var stats = {}; 252 stats.total = 0; 253 254 if (!this.timeSlices) 255 return stats; 256 257 function addStatsForSlice(threadTimeSlice) { 258 var freqRange = tr.b.Range.fromExplicitRange(threadTimeSlice.start, 259 threadTimeSlice.end); 260 var intersection = freqRange.findIntersection(range); 261 262 if (threadTimeSlice.schedulingState == 263 tr.model.SCHEDULING_STATE.RUNNING) { 264 var cpu = threadTimeSlice.cpuOnWhichThreadWasRunning; 265 if (!(cpu.cpuNumber in stats)) 266 stats[cpu.cpuNumber] = 0; 267 268 stats[cpu.cpuNumber] += intersection.duration; 269 stats.total += intersection.duration; 270 } 271 } 272 273 tr.b.iterateOverIntersectingIntervals(this.timeSlices, 274 function(x) { return x.start; }, 275 function(x) { return x.end; }, 276 range.min, 277 range.max, 278 addStatsForSlice); 279 return stats; 280 }, 281 282 getSchedulingStatsForRange: function(start, end) { 283 var stats = {}; 284 285 if (!this.timeSlices) return stats; 286 287 function addStatsForSlice(threadTimeSlice) { 288 var overlapStart = Math.max(threadTimeSlice.start, start); 289 var overlapEnd = Math.min(threadTimeSlice.end, end); 290 var schedulingState = threadTimeSlice.schedulingState; 291 292 if (!(schedulingState in stats)) 293 stats[schedulingState] = 0; 294 stats[schedulingState] += overlapEnd - overlapStart; 295 } 296 297 tr.b.iterateOverIntersectingIntervals(this.timeSlices, 298 function(x) { return x.start; }, 299 function(x) { return x.end; }, 300 start, 301 end, 302 addStatsForSlice); 303 return stats; 304 }, 305 306 get samples() { 307 return this.samples_; 308 } 309 }; 310 311 /** 312 * Comparison between threads that orders first by parent.compareTo, 313 * then by names, then by tid. 314 */ 315 Thread.compare = function(x, y) { 316 var tmp = x.parent.compareTo(y.parent); 317 if (tmp) 318 return tmp; 319 320 tmp = x.sortIndex - y.sortIndex; 321 if (tmp) 322 return tmp; 323 324 tmp = tr.b.comparePossiblyUndefinedValues( 325 x.name, y.name, 326 function(x, y) { return x.localeCompare(y); }); 327 if (tmp) 328 return tmp; 329 330 return x.tid - y.tid; 331 }; 332 333 return { 334 Thread: Thread 335 }; 336}); 337</script> 338