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