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