1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 'use strict'; 6 7 /** 8 * @fileoverview Provides the Thread class. 9 */ 10 base.require('range'); 11 base.require('guid'); 12 base.require('model.slice'); 13 base.require('model.slice_group'); 14 base.require('model.async_slice_group'); 15 base.require('model.sample'); 16 base.exportTo('tracing.model', function() { 17 18 var Slice = tracing.model.Slice; 19 var SliceGroup = tracing.model.SliceGroup; 20 var AsyncSlice = tracing.model.AsyncSlice; 21 var AsyncSliceGroup = tracing.model.AsyncSliceGroup; 22 23 /** 24 * A ThreadSlice represents an interval of time on a thread resource 25 * with associated nestinged slice information. 26 * 27 * ThreadSlices are typically associated with a specific trace event pair on a 28 * specific thread. 29 * For example, 30 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms 31 * TRACE_EVENT_END0() at time=0.3ms 32 * This results in a single slice from 0.1 with duration 0.2 on a 33 * specific thread. 34 * 35 * @constructor 36 */ 37 function ThreadSlice(cat, title, colorId, start, args, opt_duration) { 38 Slice.call(this, cat, title, colorId, start, args, opt_duration); 39 // Do not modify this directly. 40 // subSlices is configured by SliceGroup.rebuildSubRows_. 41 this.subSlices = []; 42 } 43 44 ThreadSlice.prototype = { 45 __proto__: Slice.prototype 46 }; 47 48 /** 49 * A Thread stores all the trace events collected for a particular 50 * thread. We organize the synchronous slices on a thread by "subrows," where 51 * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on. 52 * The asynchronous slices are stored in an AsyncSliceGroup object. 53 * 54 * The slices stored on a Thread should be instances of 55 * ThreadSlice. 56 * 57 * @constructor 58 */ 59 function Thread(parent, tid) { 60 SliceGroup.call(this, ThreadSlice); 61 this.guid_ = tracing.GUID.allocate(); 62 if (!parent) 63 throw new Error('Parent must be provided.'); 64 this.parent = parent; 65 this.tid = tid; 66 this.cpuSlices = undefined; 67 this.samples_ = []; 68 this.kernelSlices = new SliceGroup(); 69 this.asyncSlices = new AsyncSliceGroup(); 70 this.bounds = new base.Range(); 71 } 72 73 Thread.prototype = { 74 75 __proto__: SliceGroup.prototype, 76 77 /* 78 * @return {Number} A globally unique identifier for this counter. 79 */ 80 get guid() { 81 return this.guid_; 82 }, 83 84 compareTo: function(that) { 85 return Thread.compare(this, that); 86 }, 87 88 toJSON: function() { 89 var obj = new Object(); 90 var keys = Object.keys(this); 91 for (var i = 0; i < keys.length; i++) { 92 var key = keys[i]; 93 if (typeof this[key] == 'function') 94 continue; 95 if (key == 'parent') { 96 obj[key] = this[key].guid; 97 continue; 98 } 99 obj[key] = this[key]; 100 } 101 return obj; 102 }, 103 104 /** 105 * Adds a new sample in the thread's samples. 106 * 107 * Calls to addSample must be made with non-monotonically-decreasing 108 * timestamps. 109 * 110 * @param {String} category Category of the sample to add. 111 * @param {String} title Title of the sample to add. 112 * @param {Number} ts The timetsamp of the sample, in milliseconds. 113 * @param {Object.<string, Object>} opt_args Arguments associated with 114 * the sample. 115 */ 116 addSample: function(category, title, ts, opt_args) { 117 if (this.samples_.length) { 118 var lastSample = this.samples_[this.samples_.length - 1]; 119 if (ts < lastSample.start) { 120 throw new 121 Error('Samples must be added in increasing timestamp order.'); 122 } 123 } 124 var colorId = tracing.getStringColorId(title); 125 var sample = new tracing.model.Sample(category, title, colorId, ts, 126 opt_args ? opt_args : {}); 127 this.samples_.push(sample); 128 return sample; 129 }, 130 131 /** 132 * Returns the array of samples added to this thread. If no samples 133 * have been added, an empty array is returned. 134 * 135 * @return {Array<Sample>} array of samples. 136 */ 137 get samples() { 138 return this.samples_; 139 }, 140 141 /** 142 * Name of the thread, if present. 143 */ 144 name: undefined, 145 146 /** 147 * Shifts all the timestamps inside this thread forward by the amount 148 * specified. 149 */ 150 shiftTimestampsForward: function(amount) { 151 SliceGroup.prototype.shiftTimestampsForward.call(this, amount); 152 153 if (this.cpuSlices) { 154 for (var i = 0; i < this.cpuSlices.length; i++) { 155 var slice = this.cpuSlices[i]; 156 slice.start += amount; 157 } 158 } 159 160 if (this.samples_.length) { 161 for (var i = 0; i < this.samples_.length; i++) { 162 var sample = this.samples_[i]; 163 sample.start += amount; 164 } 165 } 166 167 this.kernelSlices.shiftTimestampsForward(amount); 168 this.asyncSlices.shiftTimestampsForward(amount); 169 }, 170 171 /** 172 * Determins whether this thread is empty. If true, it usually implies 173 * that it should be pruned from the model. 174 */ 175 get isEmpty() { 176 if (this.slices.length) 177 return false; 178 if (this.openSliceCount) 179 return false; 180 if (this.cpuSlices && this.cpuSlices.length) 181 return false; 182 if (this.kernelSlices.length) 183 return false; 184 if (this.asyncSlices.length) 185 return false; 186 if (this.samples_.length) 187 return false; 188 return true; 189 }, 190 191 /** 192 * Updates the bounds based on the 193 * current objects associated with the thread. 194 */ 195 updateBounds: function() { 196 SliceGroup.prototype.updateBounds.call(this); 197 198 this.kernelSlices.updateBounds(); 199 this.bounds.addRange(this.kernelSlices.bounds); 200 201 this.asyncSlices.updateBounds(); 202 this.bounds.addRange(this.asyncSlices.bounds); 203 204 if (this.cpuSlices && this.cpuSlices.length) { 205 this.bounds.addValue(this.cpuSlices[0].start); 206 this.bounds.addValue( 207 this.cpuSlices[this.cpuSlices.length - 1].end); 208 } 209 if (this.samples_.length) { 210 this.bounds.addValue(this.samples_[0].start); 211 this.bounds.addValue( 212 this.samples_[this.samples_.length - 1].end); 213 } 214 }, 215 216 addCategoriesToDict: function(categoriesDict) { 217 for (var i = 0; i < this.slices.length; i++) 218 categoriesDict[this.slices[i].category] = true; 219 for (var i = 0; i < this.kernelSlices.length; i++) 220 categoriesDict[this.kernelSlices.slices[i].category] = true; 221 for (var i = 0; i < this.asyncSlices.length; i++) 222 categoriesDict[this.asyncSlices.slices[i].category] = true; 223 for (var i = 0; i < this.samples_.length; i++) 224 categoriesDict[this.samples_[i].category] = true; 225 }, 226 227 mergeKernelWithUserland: function() { 228 if (this.kernelSlices.length > 0) { 229 var newSlices = SliceGroup.merge(this, this.kernelSlices); 230 this.slices = newSlices.slices; 231 this.kernelSlices = new SliceGroup(); 232 this.updateBounds(); 233 } 234 }, 235 236 /** 237 * @return {String} A user-friendly name for this thread. 238 */ 239 get userFriendlyName() { 240 var tname = this.name || this.tid; 241 return this.parent.userFriendlyName + ': ' + tname; 242 }, 243 244 /** 245 * @return {String} User friendly details about this thread. 246 */ 247 get userFriendlyDetails() { 248 return this.parent.userFriendlyDetails + 249 ', tid: ' + this.tid + 250 (this.name ? ', name: ' + this.name : ''); 251 } 252 }; 253 254 /** 255 * Comparison between threads that orders first by parent.compareTo, 256 * then by names, then by tid. 257 */ 258 Thread.compare = function(x, y) { 259 var tmp = x.parent.compareTo(y.parent); 260 if (tmp != 0) 261 return tmp; 262 263 if (x.name && y.name) { 264 var tmp = x.name.localeCompare(y.name); 265 if (tmp == 0) 266 return x.tid - y.tid; 267 return tmp; 268 } else if (x.name) { 269 return -1; 270 } else if (y.name) { 271 return 1; 272 } else { 273 return x.tid - y.tid; 274 } 275 }; 276 277 return { 278 ThreadSlice: ThreadSlice, 279 Thread: Thread 280 }; 281 }); 282