blob: 8fe21637c8cfcc50d8476fadc8653bc6f0517c0a [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel='import' href='/tracing/base/base.html'>
<link rel='import' href='/tracing/base/timing.html'>
<link rel="import" href="/tracing/importer/empty_importer.html">
<link rel="import" href="/tracing/importer/importer.html">
<link rel="import" href="/tracing/importer/user_model_builder.html">
<script>
'use strict';
tr.exportTo('tr.importer', function() {
var Timing = tr.b.Timing;
function ImportOptions() {
this.shiftWorldToZero = true;
this.pruneEmptyContainers = true;
this.showImportWarnings = true;
this.trackDetailedModelStats = false;
// Callback called after
// importers run in which more data can be added to the model, before it is
// finalized.
this.customizeModelCallback = undefined;
var auditorTypes = tr.c.Auditor.getAllRegisteredTypeInfos();
this.auditorConstructors = auditorTypes.map(function(typeInfo) {
return typeInfo.constructor;
});
}
function Import(model, opt_options) {
if (model === undefined)
throw new Error('Must provide model to import into.');
// TODO(dsinclair): Check the model is empty.
this.importing_ = false;
this.importOptions_ = opt_options || new ImportOptions();
this.model_ = model;
this.model_.importOptions = this.importOptions_;
}
Import.prototype = {
__proto__: Object.prototype,
/**
* Imports the provided traces into the model. The eventData type
* is undefined and will be passed to all the importers registered
* via Importer.register. The first importer that returns true
* for canImport(events) will be used to import the events.
*
* The primary trace is provided via the eventData variable. If multiple
* traces are to be imported, specify the first one as events, and the
* remainder in the opt_additionalEventData array.
*
* @param {Array} traces An array of eventData to be imported. Each
* eventData should correspond to a single trace file and will be handled by
* a separate importer.
*/
importTraces: function(traces) {
var progressMeter = {
update: function(msg) {}
};
tr.b.Task.RunSynchronously(
this.createImportTracesTask(progressMeter, traces));
},
/**
* Imports a trace with the usual options from importTraces, but
* does so using idle callbacks, putting up an import dialog
* during the import process.
*/
importTracesWithProgressDialog: function(traces) {
if (tr.isHeadless)
throw new Error('Cannot use this method in headless mode.');
var overlay = tr.ui.b.Overlay();
overlay.title = 'Importing...';
overlay.userCanClose = false;
overlay.msgEl = document.createElement('div');
overlay.appendChild(overlay.msgEl);
overlay.msgEl.style.margin = '20px';
overlay.update = function(msg) {
this.msgEl.textContent = msg;
};
overlay.visible = true;
var promise =
tr.b.Task.RunWhenIdle(this.createImportTracesTask(overlay, traces));
promise.then(
function() { overlay.visible = false; },
function(err) { overlay.visible = false; }
);
return promise;
},
/**
* Creates a task that will import the provided traces into the model,
* updating the progressMeter as it goes. Parameters are as defined in
* importTraces.
*/
createImportTracesTask: function(progressMeter, traces) {
if (this.importing_)
throw new Error('Already importing.');
this.importing_ = true;
// Just some simple setup. It is useful to have a no-op first
// task so that we can set up the lastTask = lastTask.after()
// pattern that follows.
var importTask = new tr.b.Task(function prepareImport() {
progressMeter.update('I will now import your traces for you...');
}, this);
var lastTask = importTask;
var importers = [];
lastTask = lastTask.timedAfter('TraceImport', function createImports() {
// Copy the traces array, we may mutate it.
traces = traces.slice(0);
progressMeter.update('Creating importers...');
// Figure out which importers to use.
for (var i = 0; i < traces.length; ++i)
importers.push(this.createImporter_(traces[i]));
// Some traces have other traces inside them. Before doing the full
// import, ask the importer if it has any subtraces, and if so, create
// importers for them, also.
for (var i = 0; i < importers.length; i++) {
var subtraces = importers[i].extractSubtraces();
for (var j = 0; j < subtraces.length; j++) {
try {
traces.push(subtraces[j]);
importers.push(this.createImporter_(subtraces[j]));
} catch (error) {
// TODO(kphanee): Log the subtrace file which has failed.
console.warn(error.name + ': ' + error.message);
continue;
}
}
}
if (traces.length && !this.hasEventDataDecoder_(importers)) {
throw new Error(
'Could not find an importer for the provided eventData.');
}
// Sort them on priority. This ensures importing happens in a
// predictable order, e.g. ftrace_importer before
// trace_event_importer.
importers.sort(function(x, y) {
return x.importPriority - y.importPriority;
});
}, this);
// We import clock sync markers before all other events. This is necessary
// because we need the clock sync markers in order to know by how much we
// need to shift the timestamps of other events.
lastTask = lastTask.timedAfter('TraceImport',
function importClockSyncMarkers(task) {
importers.forEach(function(importer, index) {
task.subTask(Timing.wrapNamedFunction(
'TraceImport', importer.importerName,
function runImportClockSyncMarkersOnOneImporter() {
progressMeter.update(
'Importing clock sync markers ' + (index + 1) + ' of ' +
importers.length);
importer.importClockSyncMarkers();
}), this);
}, this);
}, this);
// Run the import.
lastTask = lastTask.timedAfter('TraceImport', function runImport(task) {
importers.forEach(function(importer, index) {
task.subTask(Timing.wrapNamedFunction(
'TraceImport', importer.importerName,
function runImportEventsOnOneImporter() {
progressMeter.update(
'Importing ' + (index + 1) + ' of ' + importers.length);
importer.importEvents();
}), this);
}, this);
}, this);
// Run the cusomizeModelCallback if needed.
if (this.importOptions_.customizeModelCallback) {
lastTask = lastTask.timedAfter('TraceImport',
function runCustomizeCallbacks(task) {
this.importOptions_.customizeModelCallback(this.model_);
}, this);
}
// Import sample data.
lastTask = lastTask.timedAfter('TraceImport',
function importSampleData(task) {
importers.forEach(function(importer, index) {
progressMeter.update(
'Importing sample data ' + (index + 1) + '/' + importers.length);
importer.importSampleData();
}, this);
}, this);
// Autoclose open slices and create subSlices.
lastTask = lastTask.timedAfter('TraceImport', function runAutoclosers() {
progressMeter.update('Autoclosing open slices...');
this.model_.autoCloseOpenSlices();
this.model_.createSubSlices();
}, this);
// Finalize import.
lastTask = lastTask.timedAfter('TraceImport',
function finalizeImport(task) {
importers.forEach(function(importer, index) {
progressMeter.update(
'Finalizing import ' + (index + 1) + '/' + importers.length);
importer.finalizeImport();
}, this);
}, this);
// Run preinit.
lastTask = lastTask.timedAfter('TraceImport', function runPreinits() {
progressMeter.update('Initializing objects (step 1/2)...');
this.model_.preInitializeObjects();
}, this);
// Prune empty containers.
if (this.importOptions_.pruneEmptyContainers) {
lastTask = lastTask.timedAfter('TraceImport',
function runPruneEmptyContainers() {
progressMeter.update('Pruning empty containers...');
this.model_.pruneEmptyContainers();
}, this);
}
// Merge kernel and userland slices on each thread.
lastTask = lastTask.timedAfter('TraceImport',
function runMergeKernelWithuserland() {
progressMeter.update('Merging kernel with userland...');
this.model_.mergeKernelWithUserland();
}, this);
// Create auditors
var auditors = [];
lastTask = lastTask.timedAfter('TraceImport',
function createAuditorsAndRunAnnotate() {
progressMeter.update('Adding arbitrary data to model...');
auditors = this.importOptions_.auditorConstructors.map(
function(auditorConstructor) {
return new auditorConstructor(this.model_);
}, this);
auditors.forEach(function(auditor) {
auditor.runAnnotate();
auditor.installUserFriendlyCategoryDriverIfNeeded();
});
}, this);
lastTask = lastTask.timedAfter('TraceImport',
function computeWorldBounds() {
progressMeter.update('Computing final world bounds...');
this.model_.computeWorldBounds(this.importOptions_.shiftWorldToZero);
}, this);
// Build the flow event interval tree.
lastTask = lastTask.timedAfter('TraceImport',
function buildFlowEventIntervalTree() {
progressMeter.update('Building flow event map...');
this.model_.buildFlowEventIntervalTree();
}, this);
// Join refs.
lastTask = lastTask.timedAfter('TraceImport', function joinRefs() {
progressMeter.update('Joining object refs...');
this.model_.joinRefs();
}, this);
// Delete any undeleted objects.
lastTask = lastTask.timedAfter('TraceImport',
function cleanupUndeletedObjects() {
progressMeter.update('Cleaning up undeleted objects...');
this.model_.cleanupUndeletedObjects();
}, this);
// Sort global and process memory dumps.
lastTask = lastTask.timedAfter('TraceImport', function sortMemoryDumps() {
progressMeter.update('Sorting memory dumps...');
this.model_.sortMemoryDumps();
}, this);
// Finalize memory dump graphs.
lastTask = lastTask.timedAfter('TraceImport',
function finalizeMemoryGraphs() {
progressMeter.update('Finalizing memory dump graphs...');
this.model_.finalizeMemoryGraphs();
}, this);
// Run initializers.
lastTask = lastTask.timedAfter('TraceImport',
function initializeObjects() {
progressMeter.update('Initializing objects (step 2/2)...');
this.model_.initializeObjects();
}, this);
// Build event indices mapping from an event id to all flow events.
lastTask = lastTask.timedAfter('TraceImport',
function buildEventIndices() {
progressMeter.update('Building event indices...');
this.model_.buildEventIndices();
}, this);
// Build the UserModel.
lastTask = lastTask.timedAfter('TraceImport', function buildUserModel() {
progressMeter.update('Building UserModel...');
var userModelBuilder = new tr.importer.UserModelBuilder(this.model_);
userModelBuilder.buildUserModel();
}, this);
// Sort Expectations.
lastTask = lastTask.timedAfter('TraceImport',
function sortExpectations() {
progressMeter.update('Sorting user expectations...');
this.model_.userModel.sortExpectations();
}, this);
// Run audits.
lastTask = lastTask.timedAfter('TraceImport', function runAudits() {
progressMeter.update('Running auditors...');
auditors.forEach(function(auditor) {
auditor.runAudit();
});
}, this);
lastTask = lastTask.timedAfter('TraceImport', function sortAlerts() {
progressMeter.update('Updating alerts...');
this.model_.sortAlerts();
}, this);
lastTask = lastTask.timedAfter('TraceImport',
function lastUpdateBounds() {
progressMeter.update('Update bounds...');
this.model_.updateBounds();
}, this);
lastTask = lastTask.timedAfter('TraceImport',
function addModelWarnings() {
progressMeter.update('Looking for warnings...');
// Log an import warning if the clock is low resolution.
if (!this.model_.isTimeHighResolution) {
this.model_.importWarning({
type: 'low_resolution_timer',
message: 'Trace time is low resolution, trace may be unusable.',
showToUser: true
});
}
}, this);
// Cleanup.
lastTask.after(function() {
this.importing_ = false;
}, this);
return importTask;
},
createImporter_: function(eventData) {
var importerConstructor = tr.importer.Importer.findImporterFor(eventData);
if (!importerConstructor) {
throw new Error('Couldn\'t create an importer for the provided ' +
'eventData.');
}
return new importerConstructor(this.model_, eventData);
},
hasEventDataDecoder_: function(importers) {
for (var i = 0; i < importers.length; ++i) {
if (!importers[i].isTraceDataContainer())
return true;
}
return false;
}
};
return {
ImportOptions: ImportOptions,
Import: Import
};
});
</script>