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