1<!DOCTYPE html> 2<!-- 3Copyright 2015 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/timing.html'> 10<link rel="import" href="/tracing/importer/empty_importer.html"> 11<link rel="import" href="/tracing/importer/importer.html"> 12<link rel="import" href="/tracing/importer/user_model_builder.html"> 13 14<script> 15'use strict'; 16 17tr.exportTo('tr.importer', function() { 18 var Timing = tr.b.Timing; 19 20 function ImportOptions() { 21 this.shiftWorldToZero = true; 22 this.pruneEmptyContainers = true; 23 this.showImportWarnings = true; 24 this.trackDetailedModelStats = false; 25 26 // Callback called after 27 // importers run in which more data can be added to the model, before it is 28 // finalized. 29 this.customizeModelCallback = undefined; 30 31 var auditorTypes = tr.c.Auditor.getAllRegisteredTypeInfos(); 32 this.auditorConstructors = auditorTypes.map(function(typeInfo) { 33 return typeInfo.constructor; 34 }); 35 } 36 37 function Import(model, opt_options) { 38 if (model === undefined) 39 throw new Error('Must provide model to import into.'); 40 41 // TODO(dsinclair): Check the model is empty. 42 43 this.importing_ = false; 44 this.importOptions_ = opt_options || new ImportOptions(); 45 46 this.model_ = model; 47 this.model_.importOptions = this.importOptions_; 48 } 49 50 Import.prototype = { 51 __proto__: Object.prototype, 52 53 /** 54 * Imports the provided traces into the model. The eventData type 55 * is undefined and will be passed to all the importers registered 56 * via Importer.register. The first importer that returns true 57 * for canImport(events) will be used to import the events. 58 * 59 * The primary trace is provided via the eventData variable. If multiple 60 * traces are to be imported, specify the first one as events, and the 61 * remainder in the opt_additionalEventData array. 62 * 63 * @param {Array} traces An array of eventData to be imported. Each 64 * eventData should correspond to a single trace file and will be handled by 65 * a separate importer. 66 */ 67 importTraces: function(traces) { 68 var progressMeter = { 69 update: function(msg) {} 70 }; 71 72 tr.b.Task.RunSynchronously( 73 this.createImportTracesTask(progressMeter, traces)); 74 }, 75 76 /** 77 * Imports a trace with the usual options from importTraces, but 78 * does so using idle callbacks, putting up an import dialog 79 * during the import process. 80 */ 81 importTracesWithProgressDialog: function(traces) { 82 if (tr.isHeadless) 83 throw new Error('Cannot use this method in headless mode.'); 84 85 var overlay = tr.ui.b.Overlay(); 86 overlay.title = 'Importing...'; 87 overlay.userCanClose = false; 88 overlay.msgEl = document.createElement('div'); 89 overlay.appendChild(overlay.msgEl); 90 overlay.msgEl.style.margin = '20px'; 91 overlay.update = function(msg) { 92 this.msgEl.textContent = msg; 93 }; 94 overlay.visible = true; 95 96 var promise = 97 tr.b.Task.RunWhenIdle(this.createImportTracesTask(overlay, traces)); 98 promise.then( 99 function() { overlay.visible = false; }, 100 function(err) { overlay.visible = false; } 101 ); 102 return promise; 103 }, 104 105 /** 106 * Creates a task that will import the provided traces into the model, 107 * updating the progressMeter as it goes. Parameters are as defined in 108 * importTraces. 109 */ 110 createImportTracesTask: function(progressMeter, traces) { 111 if (this.importing_) 112 throw new Error('Already importing.'); 113 this.importing_ = true; 114 115 // Just some simple setup. It is useful to have a no-op first 116 // task so that we can set up the lastTask = lastTask.after() 117 // pattern that follows. 118 var importTask = new tr.b.Task(function prepareImport() { 119 progressMeter.update('I will now import your traces for you...'); 120 }, this); 121 var lastTask = importTask; 122 123 var importers = []; 124 125 lastTask = lastTask.timedAfter('TraceImport', function createImports() { 126 // Copy the traces array, we may mutate it. 127 traces = traces.slice(0); 128 progressMeter.update('Creating importers...'); 129 // Figure out which importers to use. 130 for (var i = 0; i < traces.length; ++i) 131 importers.push(this.createImporter_(traces[i])); 132 133 // Some traces have other traces inside them. Before doing the full 134 // import, ask the importer if it has any subtraces, and if so, create 135 // importers for them, also. 136 for (var i = 0; i < importers.length; i++) { 137 var subtraces = importers[i].extractSubtraces(); 138 for (var j = 0; j < subtraces.length; j++) { 139 try { 140 traces.push(subtraces[j]); 141 importers.push(this.createImporter_(subtraces[j])); 142 } catch (error) { 143 // TODO(kphanee): Log the subtrace file which has failed. 144 console.warn(error.name + ': ' + error.message); 145 continue; 146 } 147 } 148 } 149 150 if (traces.length && !this.hasEventDataDecoder_(importers)) { 151 throw new Error( 152 'Could not find an importer for the provided eventData.'); 153 } 154 155 // Sort them on priority. This ensures importing happens in a 156 // predictable order, e.g. ftrace_importer before 157 // trace_event_importer. 158 importers.sort(function(x, y) { 159 return x.importPriority - y.importPriority; 160 }); 161 }, this); 162 163 // We import clock sync markers before all other events. This is necessary 164 // because we need the clock sync markers in order to know by how much we 165 // need to shift the timestamps of other events. 166 lastTask = lastTask.timedAfter('TraceImport', 167 function importClockSyncMarkers(task) { 168 importers.forEach(function(importer, index) { 169 task.subTask(Timing.wrapNamedFunction( 170 'TraceImport', importer.importerName, 171 function runImportClockSyncMarkersOnOneImporter() { 172 progressMeter.update( 173 'Importing clock sync markers ' + (index + 1) + ' of ' + 174 importers.length); 175 importer.importClockSyncMarkers(); 176 }), this); 177 }, this); 178 }, this); 179 180 // Run the import. 181 lastTask = lastTask.timedAfter('TraceImport', function runImport(task) { 182 importers.forEach(function(importer, index) { 183 task.subTask(Timing.wrapNamedFunction( 184 'TraceImport', importer.importerName, 185 function runImportEventsOnOneImporter() { 186 progressMeter.update( 187 'Importing ' + (index + 1) + ' of ' + importers.length); 188 importer.importEvents(); 189 }), this); 190 }, this); 191 }, this); 192 193 // Run the cusomizeModelCallback if needed. 194 if (this.importOptions_.customizeModelCallback) { 195 lastTask = lastTask.timedAfter('TraceImport', 196 function runCustomizeCallbacks(task) { 197 this.importOptions_.customizeModelCallback(this.model_); 198 }, this); 199 } 200 201 // Import sample data. 202 lastTask = lastTask.timedAfter('TraceImport', 203 function importSampleData(task) { 204 importers.forEach(function(importer, index) { 205 progressMeter.update( 206 'Importing sample data ' + (index + 1) + '/' + importers.length); 207 importer.importSampleData(); 208 }, this); 209 }, this); 210 211 // Autoclose open slices and create subSlices. 212 lastTask = lastTask.timedAfter('TraceImport', function runAutoclosers() { 213 progressMeter.update('Autoclosing open slices...'); 214 this.model_.autoCloseOpenSlices(); 215 this.model_.createSubSlices(); 216 }, this); 217 218 // Finalize import. 219 lastTask = lastTask.timedAfter('TraceImport', 220 function finalizeImport(task) { 221 importers.forEach(function(importer, index) { 222 progressMeter.update( 223 'Finalizing import ' + (index + 1) + '/' + importers.length); 224 importer.finalizeImport(); 225 }, this); 226 }, this); 227 228 // Run preinit. 229 lastTask = lastTask.timedAfter('TraceImport', function runPreinits() { 230 progressMeter.update('Initializing objects (step 1/2)...'); 231 this.model_.preInitializeObjects(); 232 }, this); 233 234 // Prune empty containers. 235 if (this.importOptions_.pruneEmptyContainers) { 236 lastTask = lastTask.timedAfter('TraceImport', 237 function runPruneEmptyContainers() { 238 progressMeter.update('Pruning empty containers...'); 239 this.model_.pruneEmptyContainers(); 240 }, this); 241 } 242 243 // Merge kernel and userland slices on each thread. 244 lastTask = lastTask.timedAfter('TraceImport', 245 function runMergeKernelWithuserland() { 246 progressMeter.update('Merging kernel with userland...'); 247 this.model_.mergeKernelWithUserland(); 248 }, this); 249 250 // Create auditors 251 var auditors = []; 252 lastTask = lastTask.timedAfter('TraceImport', 253 function createAuditorsAndRunAnnotate() { 254 progressMeter.update('Adding arbitrary data to model...'); 255 auditors = this.importOptions_.auditorConstructors.map( 256 function(auditorConstructor) { 257 return new auditorConstructor(this.model_); 258 }, this); 259 auditors.forEach(function(auditor) { 260 auditor.runAnnotate(); 261 auditor.installUserFriendlyCategoryDriverIfNeeded(); 262 }); 263 }, this); 264 265 lastTask = lastTask.timedAfter('TraceImport', 266 function computeWorldBounds() { 267 progressMeter.update('Computing final world bounds...'); 268 this.model_.computeWorldBounds(this.importOptions_.shiftWorldToZero); 269 }, this); 270 271 // Build the flow event interval tree. 272 lastTask = lastTask.timedAfter('TraceImport', 273 function buildFlowEventIntervalTree() { 274 progressMeter.update('Building flow event map...'); 275 this.model_.buildFlowEventIntervalTree(); 276 }, this); 277 278 // Join refs. 279 lastTask = lastTask.timedAfter('TraceImport', function joinRefs() { 280 progressMeter.update('Joining object refs...'); 281 this.model_.joinRefs(); 282 }, this); 283 284 // Delete any undeleted objects. 285 lastTask = lastTask.timedAfter('TraceImport', 286 function cleanupUndeletedObjects() { 287 progressMeter.update('Cleaning up undeleted objects...'); 288 this.model_.cleanupUndeletedObjects(); 289 }, this); 290 291 // Sort global and process memory dumps. 292 lastTask = lastTask.timedAfter('TraceImport', function sortMemoryDumps() { 293 progressMeter.update('Sorting memory dumps...'); 294 this.model_.sortMemoryDumps(); 295 }, this); 296 297 // Finalize memory dump graphs. 298 lastTask = lastTask.timedAfter('TraceImport', 299 function finalizeMemoryGraphs() { 300 progressMeter.update('Finalizing memory dump graphs...'); 301 this.model_.finalizeMemoryGraphs(); 302 }, this); 303 304 // Run initializers. 305 lastTask = lastTask.timedAfter('TraceImport', 306 function initializeObjects() { 307 progressMeter.update('Initializing objects (step 2/2)...'); 308 this.model_.initializeObjects(); 309 }, this); 310 311 // Build event indices mapping from an event id to all flow events. 312 lastTask = lastTask.timedAfter('TraceImport', 313 function buildEventIndices() { 314 progressMeter.update('Building event indices...'); 315 this.model_.buildEventIndices(); 316 }, this); 317 318 // Build the UserModel. 319 lastTask = lastTask.timedAfter('TraceImport', function buildUserModel() { 320 progressMeter.update('Building UserModel...'); 321 var userModelBuilder = new tr.importer.UserModelBuilder(this.model_); 322 userModelBuilder.buildUserModel(); 323 }, this); 324 325 // Sort Expectations. 326 lastTask = lastTask.timedAfter('TraceImport', 327 function sortExpectations() { 328 progressMeter.update('Sorting user expectations...'); 329 this.model_.userModel.sortExpectations(); 330 }, this); 331 332 // Run audits. 333 lastTask = lastTask.timedAfter('TraceImport', function runAudits() { 334 progressMeter.update('Running auditors...'); 335 auditors.forEach(function(auditor) { 336 auditor.runAudit(); 337 }); 338 }, this); 339 340 lastTask = lastTask.timedAfter('TraceImport', function sortAlerts() { 341 progressMeter.update('Updating alerts...'); 342 this.model_.sortAlerts(); 343 }, this); 344 345 lastTask = lastTask.timedAfter('TraceImport', 346 function lastUpdateBounds() { 347 progressMeter.update('Update bounds...'); 348 this.model_.updateBounds(); 349 }, this); 350 351 lastTask = lastTask.timedAfter('TraceImport', 352 function addModelWarnings() { 353 progressMeter.update('Looking for warnings...'); 354 // Log an import warning if the clock is low resolution. 355 if (!this.model_.isTimeHighResolution) { 356 this.model_.importWarning({ 357 type: 'low_resolution_timer', 358 message: 'Trace time is low resolution, trace may be unusable.', 359 showToUser: true 360 }); 361 } 362 }, this); 363 364 // Cleanup. 365 lastTask.after(function() { 366 this.importing_ = false; 367 }, this); 368 return importTask; 369 }, 370 371 createImporter_: function(eventData) { 372 var importerConstructor = tr.importer.Importer.findImporterFor(eventData); 373 if (!importerConstructor) { 374 throw new Error('Couldn\'t create an importer for the provided ' + 375 'eventData.'); 376 } 377 return new importerConstructor(this.model_, eventData); 378 }, 379 380 hasEventDataDecoder_: function(importers) { 381 for (var i = 0; i < importers.length; ++i) { 382 if (!importers[i].isTraceDataContainer()) 383 return true; 384 } 385 386 return false; 387 } 388 }; 389 390 return { 391 ImportOptions: ImportOptions, 392 Import: Import 393 }; 394}); 395</script> 396