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 State and UI for trace data collection. 9 */ 10base.requireStylesheet('about_tracing.tracing_controller'); 11 12base.require('base.properties'); 13base.require('base.events'); 14base.require('ui.overlay'); 15 16base.exportTo('about_tracing', function() { 17 18 /** 19 * The tracing controller is responsible for talking to tracing_ui.cc in 20 * chrome 21 * @constructor 22 * @param {function(String, opt_Array.<String>} Function to be used to send 23 * data to chrome. 24 */ 25 function TracingController(sendFn) { 26 this.sendFn_ = sendFn; 27 this.overlay_ = new ui.Overlay(); 28 this.overlay_.className = 'tracing-overlay'; 29 30 this.statusDiv_ = document.createElement('div'); 31 this.overlay_.appendChild(this.statusDiv_); 32 33 this.bufferPercentDiv_ = document.createElement('div'); 34 this.overlay_.appendChild(this.bufferPercentDiv_); 35 36 this.stopButton_ = document.createElement('button'); 37 this.stopButton_.onclick = this.endTracing.bind(this); 38 this.stopButton_.textContent = 'Stop tracing'; 39 this.overlay_.appendChild(this.stopButton_); 40 41 this.traceEventData_ = undefined; 42 this.systemTraceEvents_ = undefined; 43 44 this.onKeydown_ = this.onKeydown_.bind(this); 45 this.onKeypress_ = this.onKeypress_.bind(this); 46 47 this.supportsSystemTracing_ = base.isChromeOS; 48 49 if (this.sendFn_) 50 this.sendFn_('tracingControllerInitialized'); 51 } 52 53 TracingController.prototype = { 54 __proto__: base.EventTarget.prototype, 55 56 gpuInfo_: undefined, 57 clientInfo_: undefined, 58 tracingEnabled_: false, 59 tracingEnding_: false, 60 systemTraceDataFilename_: undefined, 61 62 get supportsSystemTracing() { 63 return this.supportsSystemTracing_; 64 }, 65 66 onRequestBufferPercentFullComplete: function(percent_full) { 67 if (!this.overlay_.visible) 68 return; 69 70 window.setTimeout(this.beginRequestBufferPercentFull_.bind(this), 500); 71 72 var newText = 'Buffer usage: ' + 73 Math.round(100 * percent_full) + '%'; 74 if (this.bufferPercentDiv_.textContent != newText) 75 this.bufferPercentDiv_.textContent = newText; 76 }, 77 78 /** 79 * Begin requesting the buffer fullness 80 */ 81 beginRequestBufferPercentFull_: function() { 82 this.sendFn_('beginRequestBufferPercentFull'); 83 }, 84 85 /** 86 * Called by info_view to empty the trace buffer 87 * 88 * |opt_trace_categories| is a comma-delimited list of category wildcards. 89 * A category can have an optional '-' prefix to make it an excluded 90 * category. All the same rules apply above, so for example, having both 91 * included and excluded categories in the same list would not be 92 * supported. 93 * 94 * Example: beginTracing("test_MyTest*"); 95 * Example: beginTracing("test_MyTest*,test_OtherStuff"); 96 * Example: beginTracing("-excluded_category1,-excluded_category2"); 97 */ 98 beginTracing: function(opt_systemTracingEnabled, opt_trace_continuous, 99 opt_enableSampling, opt_trace_categories) { 100 if (this.tracingEnabled_) 101 throw new Error('Tracing already begun.'); 102 103 this.stopButton_.hidden = false; 104 this.statusDiv_.textContent = 'Tracing active.'; 105 this.overlay_.obeyCloseEvents = false; 106 this.overlay_.visible = true; 107 108 this.tracingEnabled_ = true; 109 110 console.log('Beginning to trace...'); 111 this.statusDiv_.textContent = 'Tracing active.'; 112 113 var trace_options = []; 114 trace_options.push(opt_trace_continuous ? 'record-continuously' : 115 'record-until-full'); 116 if (opt_enableSampling) 117 trace_options.push('enable-sampling'); 118 119 this.traceEventData_ = undefined; 120 this.systemTraceEvents_ = undefined; 121 this.sendFn_( 122 'beginTracing', 123 [ 124 opt_systemTracingEnabled || false, 125 opt_trace_categories || '-test_*', 126 trace_options.join(',') 127 ] 128 ); 129 this.beginRequestBufferPercentFull_(); 130 131 window.addEventListener('keypress', this.onKeypress_); 132 window.addEventListener('keydown', this.onKeydown_); 133 }, 134 135 onKeydown_: function(e) { 136 if (e.keyCode == 27) { 137 this.endTracing(); 138 } 139 }, 140 141 onKeypress_: function(e) { 142 if (e.keyIdentifier == 'Enter') { 143 this.endTracing(); 144 } 145 }, 146 147 /** 148 * Called from gpu c++ code when ClientInfo is updated. 149 */ 150 onClientInfoUpdate: function(clientInfo) { 151 this.clientInfo_ = clientInfo; 152 }, 153 154 /** 155 * Called from gpu c++ code when GPU Info is updated. 156 */ 157 onGpuInfoUpdate: function(gpuInfo) { 158 this.gpuInfo_ = gpuInfo; 159 }, 160 161 /** 162 * Checks whether tracing is enabled 163 */ 164 get isTracingEnabled() { 165 return this.tracingEnabled_; 166 }, 167 168 /** 169 * Gets the currently traced events. If tracing is active, then 170 * this can change on the fly. 171 */ 172 get traceEventData() { 173 return this.traceEventData_; 174 }, 175 176 /** 177 * Called to finish tracing and update all views. 178 */ 179 endTracing: function() { 180 if (!this.tracingEnabled_) throw new Error('Tracing not begun.'); 181 if (this.tracingEnding_) return; 182 this.tracingEnding_ = true; 183 184 this.statusDiv_.textContent = 'Ending trace...'; 185 console.log('Finishing trace'); 186 this.statusDiv_.textContent = 'Downloading trace data...'; 187 this.stopButton_.hidden = true; 188 // delay sending endTracingAsync until we get a chance to 189 // update the screen... 190 var that = this; 191 window.setTimeout(function() { 192 that.sendFn_('endTracingAsync'); 193 }, 100); 194 }, 195 196 /** 197 * Called by the browser when all processes complete tracing. 198 */ 199 onEndTracingComplete: function(traceDataString) { 200 window.removeEventListener('keydown', this.onKeydown_); 201 window.removeEventListener('keypress', this.onKeypress_); 202 this.overlay_.visible = false; 203 this.tracingEnabled_ = false; 204 this.tracingEnding_ = false; 205 206 if (traceDataString[traceDataString.length - 1] == ',') 207 traceDataString = traceDataString.substr(0, traceDataString.length - 1); 208 if (traceDataString[0] != '[') 209 traceDataString = '[' + traceDataString; 210 if (traceDataString[traceDataString.length - 1] != ']') 211 traceDataString = traceDataString + ']'; 212 213 this.traceEventData_ = traceDataString; 214 215 console.log('onEndTracingComplete p1 with ' + 216 this.traceEventData_.length + ' bytes of data.'); 217 var e = new base.Event('traceEnded'); 218 this.dispatchEvent(e); 219 }, 220 221 collectCategories: function() { 222 this.sendFn_('getKnownCategories'); 223 }, 224 225 onKnownCategoriesCollected: function(categories) { 226 var e = new base.Event('categoriesCollected'); 227 e.categories = categories; 228 this.dispatchEvent(e); 229 }, 230 231 232 /** 233 * Called by tracing c++ code when new system trace data arrives. 234 */ 235 onSystemTraceDataCollected: function(events) { 236 console.log('onSystemTraceDataCollected with ' + 237 events.length + ' chars of data.'); 238 this.systemTraceEvents_ = events; 239 }, 240 241 /** 242 * Gets the currentl system trace events. If tracing is active, then 243 * this can change on the fly. 244 */ 245 get systemTraceEvents() { 246 return this.systemTraceEvents_; 247 }, 248 249 /** 250 * Tells browser to put up a load dialog and load the trace file 251 */ 252 beginLoadTraceFile: function() { 253 this.sendFn_('loadTraceFile'); 254 }, 255 256 /** 257 * Called by the browser when a trace file is loaded. 258 */ 259 onLoadTraceFileComplete: function(traceDataString, opt_filename) { 260 this.traceEventData_ = traceDataString; 261 this.systemTraceEvents_ = undefined; 262 263 var e = new base.Event('loadTraceFileComplete'); 264 e.filename = opt_filename || ''; 265 this.dispatchEvent(e); 266 }, 267 268 /** 269 * Called by the browser when loading a trace file was canceled. 270 */ 271 onLoadTraceFileCanceled: function() { 272 base.dispatchSimpleEvent(this, 'loadTraceFileCanceled'); 273 }, 274 275 /** 276 * Tells browser to put up a save dialog and save the trace file 277 */ 278 beginSaveTraceFile: function() { 279 // this.traceEventData_ is already in JSON form, but now need to insert it 280 // into a data structure containing metadata about the recording. To do 281 // this "right," we should parse the traceEventData_, make the new data 282 // structure and then JSONize the lot. But, the traceEventData_ is huge so 283 // parsing it and stringifying it again is going to consume time and 284 // memory. 285 // 286 // Instead, we make the new data strcture with a placeholder string, 287 // JSONify it, then replace the placeholder string with the 288 // traceEventData_. 289 var data = { 290 traceEvents: '__TRACE_EVENT_PLACEHOLDER__', 291 systemTraceEvents: this.systemTraceEvents_, 292 clientInfo: this.clientInfo_, 293 gpuInfo: this.gpuInfo_ 294 }; 295 var dataAsString = JSON.stringify(data); 296 dataAsString = dataAsString.replace('"__TRACE_EVENT_PLACEHOLDER__"', 297 this.traceEventData_); 298 this.sendFn_('saveTraceFile', [dataAsString]); 299 }, 300 301 /** 302 * Called by the browser when a trace file is saveed. 303 */ 304 onSaveTraceFileComplete: function() { 305 base.dispatchSimpleEvent(this, 'saveTraceFileComplete'); 306 }, 307 308 /** 309 * Called by the browser when saving a trace file was canceled. 310 */ 311 onSaveTraceFileCanceled: function() { 312 base.dispatchSimpleEvent(this, 'saveTraceFileCanceled'); 313 } 314 }; 315 return { 316 TracingController: TracingController 317 }; 318}); 319