• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/**
6 * @fileoverview Imports text files in the Linux event trace format into the
7 * Tracemodel. This format is output both by sched_trace and by Linux's perf
8 * tool.
9 *
10 * This importer assumes the events arrive as a string. The unit tests provide
11 * examples of the trace format.
12 *
13 * Linux scheduler traces use a definition for 'pid' that is different than
14 * tracing uses. Whereas tracing uses pid to identify a specific process, a pid
15 * in a linux trace refers to a specific thread within a process. Within this
16 * file, we the definition used in Linux traces, as it improves the importing
17 * code's readability.
18 */
19'use strict';
20
21base.require('tracing.trace_model');
22base.require('tracing.color_scheme');
23base.require('tracing.importer.linux_perf.bus_parser');
24base.require('tracing.importer.linux_perf.clock_parser');
25base.require('tracing.importer.linux_perf.cpufreq_parser');
26base.require('tracing.importer.linux_perf.disk_parser');
27base.require('tracing.importer.linux_perf.drm_parser');
28base.require('tracing.importer.linux_perf.exynos_parser');
29base.require('tracing.importer.linux_perf.gesture_parser');
30base.require('tracing.importer.linux_perf.i915_parser');
31base.require('tracing.importer.linux_perf.mali_parser');
32base.require('tracing.importer.linux_perf.power_parser');
33base.require('tracing.importer.linux_perf.sched_parser');
34base.require('tracing.importer.linux_perf.sync_parser');
35base.require('tracing.importer.linux_perf.workqueue_parser');
36base.require('tracing.importer.linux_perf.android_parser');
37base.require('tracing.importer.linux_perf.kfunc_parser');
38
39base.exportTo('tracing.importer', function() {
40  /**
41   * Represents the scheduling state for a single thread.
42   * @constructor
43   */
44  function CpuState(cpu) {
45    this.cpu = cpu;
46  }
47
48  CpuState.prototype = {
49    __proto__: Object.prototype,
50
51    /**
52     * Switches the active pid on this Cpu. If necessary, add a Slice
53     * to the cpu representing the time spent on that Cpu since the last call to
54     * switchRunningLinuxPid.
55     */
56    switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) {
57      // Generate a slice if the last active pid was not the idle task
58      if (this.lastActivePid !== undefined && this.lastActivePid != 0) {
59        var duration = ts - this.lastActiveTs;
60        var thread = importer.threadsByLinuxPid[this.lastActivePid];
61        var name;
62        if (thread)
63          name = thread.userFriendlyName;
64        else
65          name = this.lastActiveComm;
66
67        var slice = new tracing.trace_model.Slice(
68            '', name,
69            tracing.getStringColorId(name),
70            this.lastActiveTs,
71            {
72              comm: this.lastActiveComm,
73              tid: this.lastActivePid,
74              prio: this.lastActivePrio,
75              stateWhenDescheduled: prevState
76            },
77            duration);
78        this.cpu.slices.push(slice);
79      }
80
81      this.lastActiveTs = ts;
82      this.lastActivePid = pid;
83      this.lastActiveComm = comm;
84      this.lastActivePrio = prio;
85    }
86  };
87
88  /**
89   * Imports linux perf events into a specified model.
90   * @constructor
91   */
92  function LinuxPerfImporter(model, events) {
93    this.importPriority = 2;
94    this.model_ = model;
95    this.events_ = events;
96    this.clockSyncRecords_ = [];
97    this.cpuStates_ = {};
98    this.wakeups_ = [];
99    this.kernelThreadStates_ = {};
100    this.buildMapFromLinuxPidsToThreads();
101    this.lineNumberBase = 0;
102    this.lineNumber = -1;
103    this.pseudoThreadCounter = 1;
104    this.parsers_ = [];
105    this.eventHandlers_ = {};
106  }
107
108  var TestExports = {};
109
110  // Matches the trace record in 3.2 and later with the print-tgid option:
111  //          <idle>-0    0 [001] d...  1.23: sched_switch
112  //
113  // A TGID (Thread Group ID) is basically what the Linux kernel calls what
114  // userland refers to as a process ID (as opposed to a Linux pid, which is
115  // what userland calls a thread ID).
116  var lineREWithTGID = new RegExp(
117      '^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]' +
118      '\\s+[dX.][N.][Hhs.][0-9a-f.]' +
119      '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
120  var lineParserWithTGID = function(line) {
121    var groups = lineREWithTGID.exec(line);
122    if (!groups) {
123      return groups;
124    }
125
126    var tgid = groups[3];
127    if (tgid[0] === '-')
128      tgid = undefined;
129
130    return {
131      threadName: groups[1],
132      pid: groups[2],
133      tgid: tgid,
134      cpuNumber: groups[4],
135      timestamp: groups[5],
136      eventName: groups[6],
137      details: groups[7]
138    };
139  };
140  TestExports.lineParserWithTGID = lineParserWithTGID;
141
142  // Matches the default trace record in 3.2 and later (includes irq-info):
143  //          <idle>-0     [001] d...  1.23: sched_switch
144  var lineREWithIRQInfo = new RegExp(
145      '^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]' +
146      '\\s+[dX.][N.][Hhs.][0-9a-f.]' +
147      '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
148  var lineParserWithIRQInfo = function(line) {
149    var groups = lineREWithIRQInfo.exec(line);
150    if (!groups) {
151      return groups;
152    }
153    return {
154      threadName: groups[1],
155      pid: groups[2],
156      cpuNumber: groups[3],
157      timestamp: groups[4],
158      eventName: groups[5],
159      details: groups[6]
160    };
161  };
162  TestExports.lineParserWithIRQInfo = lineParserWithIRQInfo;
163
164  // Matches the default trace record pre-3.2:
165  //          <idle>-0     [001]  1.23: sched_switch
166  var lineREWithLegacyFmt =
167      /^\s*(.+)-(\d+)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/;
168  var lineParserWithLegacyFmt = function(line) {
169    var groups = lineREWithLegacyFmt.exec(line);
170    if (!groups) {
171      return groups;
172    }
173    return {
174      threadName: groups[1],
175      pid: groups[2],
176      cpuNumber: groups[3],
177      timestamp: groups[4],
178      eventName: groups[5],
179      details: groups[6]
180    };
181  };
182  TestExports.lineParserWithLegacyFmt = lineParserWithLegacyFmt;
183
184  // Matches the trace_event_clock_sync record
185  //  0: trace_event_clock_sync: parent_ts=19581477508
186  var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/;
187  TestExports.traceEventClockSyncRE = traceEventClockSyncRE;
188
189  // Some kernel trace events are manually classified in slices and
190  // hand-assigned a pseudo PID.
191  var pseudoKernelPID = 0;
192
193  /**
194   * Deduce the format of trace data. Linix kernels prior to 3.3 used one
195   * format (by default); 3.4 and later used another.  Additionally, newer
196   * kernels can optionally trace the TGID.
197   *
198   * @return {function} the function for parsing data when the format is
199   * recognized; otherwise null.
200   */
201  function autoDetectLineParser(line) {
202    if (line[0] == '{')
203      return false;
204    if (lineREWithTGID.test(line))
205      return lineParserWithTGID;
206    if (lineREWithIRQInfo.test(line))
207      return lineParserWithIRQInfo;
208    if (lineREWithLegacyFmt.test(line))
209      return lineParserWithLegacyFmt;
210    return null;
211  };
212  TestExports.autoDetectLineParser = autoDetectLineParser;
213
214  /**
215   * Guesses whether the provided events is a Linux perf string.
216   * Looks for the magic string "# tracer" at the start of the file,
217   * or the typical task-pid-cpu-timestamp-function sequence of a typical
218   * trace's body.
219   *
220   * @return {boolean} True when events is a linux perf array.
221   */
222  LinuxPerfImporter.canImport = function(events) {
223    if (!(typeof(events) === 'string' || events instanceof String))
224      return false;
225
226    if (LinuxPerfImporter._extractEventsFromSystraceHTML(events, false).ok)
227      return true;
228
229    if (/^# tracer:/.test(events))
230      return true;
231
232    var m = /^(.+)\n/.exec(events);
233    if (m)
234      events = m[1];
235    if (autoDetectLineParser(events))
236      return true;
237
238    return false;
239  };
240
241  LinuxPerfImporter._extractEventsFromSystraceHTML = function(
242      incoming_events, produce_result) {
243    var failure = {ok: false};
244    if (produce_result === undefined)
245      produce_result = true;
246
247    if (/^<!DOCTYPE HTML>/.test(incoming_events) == false)
248      return failure;
249    var lines = incoming_events.split('\n');
250    var cur_line = 1;
251    function advanceToLineMatching(regex) {
252      for (; cur_line < lines.length; cur_line++) {
253        if (regex.test(lines[cur_line]))
254          return true;
255      }
256      return false;
257    }
258
259    // Try to find the data...
260    if (!advanceToLineMatching(/^  <script>$/))
261      return failure;
262    if (!advanceToLineMatching(/^  var linuxPerfData = "\\$/))
263      return failure;
264    var events_begin_at_line = cur_line + 1;
265
266    if (!advanceToLineMatching(/^  <\/script>$/))
267      return failure;
268    var events_end_at_line = cur_line;
269
270    if (!advanceToLineMatching(/^<\/body>$/))
271      return failure;
272    if (!advanceToLineMatching(/^<\/html>$/))
273      return failure;
274
275    var raw_events = lines.slice(events_begin_at_line,
276                                 events_end_at_line);
277    function endsWith(str, suffix) {
278      return str.indexOf(suffix, str.length - suffix.length) !== -1;
279    }
280    function stripSuffix(str, suffix) {
281      if (!endsWith(str, suffix))
282        return str;
283      return str.substring(str, str.length - suffix.length);
284    }
285
286    // Strip off escaping in the file needed to preserve linebreaks.
287    var events = [];
288    if (produce_result) {
289      for (var i = 0; i < raw_events.length; i++) {
290        var event = raw_events[i];
291        event = stripSuffix(event, '\\n\\');
292        events.push(event);
293      }
294    } else {
295      events = [raw_events[raw_events.length - 1]];
296    }
297
298    // Last event ends differently. Strip that off too,
299    // treating absence of that trailing stirng as a failure.
300    var oldLastEvent = events[events.length - 1];
301    var newLastEvent = stripSuffix(oldLastEvent, '\\n";');
302    if (newLastEvent == oldLastEvent)
303      return failure;
304    events[events.length - 1] = newLastEvent;
305
306    return {ok: true,
307      lines: produce_result ? events : undefined,
308      events_begin_at_line: events_begin_at_line};
309  };
310
311  LinuxPerfImporter.prototype = {
312    __proto__: Object.prototype,
313
314    extractSubtrace: function() {
315      return undefined;
316    },
317
318    get model() {
319      return this.model_;
320    },
321
322    /**
323     * Precomputes a lookup table from linux pids back to existing
324     * Threads. This is used during importing to add information to each
325     * thread about whether it was running, descheduled, sleeping, et
326     * cetera.
327     */
328    buildMapFromLinuxPidsToThreads: function() {
329      this.threadsByLinuxPid = {};
330      this.model_.getAllThreads().forEach(
331          function(thread) {
332            this.threadsByLinuxPid[thread.tid] = thread;
333          }.bind(this));
334    },
335
336    /**
337     * @return {CpuState} A CpuState corresponding to the given cpuNumber.
338     */
339    getOrCreateCpuState: function(cpuNumber) {
340      if (!this.cpuStates_[cpuNumber]) {
341        var cpu = this.model_.kernel.getOrCreateCpu(cpuNumber);
342        this.cpuStates_[cpuNumber] = new CpuState(cpu);
343      }
344      return this.cpuStates_[cpuNumber];
345    },
346
347    /**
348     * @return {TimelinThread} A thread corresponding to the kernelThreadName.
349     */
350    getOrCreateKernelThread: function(kernelThreadName, pid, tid) {
351      if (!this.kernelThreadStates_[kernelThreadName]) {
352        var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);
353        thread.name = kernelThreadName;
354        this.kernelThreadStates_[kernelThreadName] = {
355          pid: pid,
356          thread: thread,
357          openSlice: undefined,
358          openSliceTS: undefined
359        };
360        this.threadsByLinuxPid[pid] = thread;
361      }
362      return this.kernelThreadStates_[kernelThreadName];
363    },
364
365    /**
366     * @return {TimelinThread} A pseudo thread corresponding to the
367     * threadName.  Pseudo threads are for events that we want to break
368     * out to a separate timeline but would not otherwise happen.
369     * These threads are assigned to pseudoKernelPID and given a
370     * unique (incrementing) TID.
371     */
372    getOrCreatePseudoThread: function(threadName) {
373      var thread = this.kernelThreadStates_[threadName];
374      if (!thread) {
375        thread = this.getOrCreateKernelThread(threadName, pseudoKernelPID,
376            this.pseudoThreadCounter);
377        this.pseudoThreadCounter++;
378      }
379      return thread;
380    },
381
382    /**
383     * Imports the data in this.events_ into model_.
384     */
385    importEvents: function(isSecondaryImport) {
386      this.createParsers();
387      this.importCpuData();
388      if (!this.alignClocks(isSecondaryImport))
389        return;
390      this.buildMapFromLinuxPidsToThreads();
391      this.buildPerThreadCpuSlicesFromCpuState();
392    },
393
394    /**
395     * Called by the Model after all other importers have imported their
396     * events.
397     */
398    finalizeImport: function() {
399    },
400
401    /**
402     * Called by the model to join references between objects, after final model
403     * bounds have been computed.
404     */
405    joinRefs: function() {
406    },
407
408    /**
409     * Builds the cpuSlices array on each thread based on our knowledge of what
410     * each Cpu is doing.  This is done only for Threads that are
411     * already in the model, on the assumption that not having any traced data
412     * on a thread means that it is not of interest to the user.
413     */
414    buildPerThreadCpuSlicesFromCpuState: function() {
415      // Push the cpu slices to the threads that they run on.
416      for (var cpuNumber in this.cpuStates_) {
417        var cpuState = this.cpuStates_[cpuNumber];
418        var cpu = cpuState.cpu;
419
420        for (var i = 0; i < cpu.slices.length; i++) {
421          var slice = cpu.slices[i];
422
423          var thread = this.threadsByLinuxPid[slice.args.tid];
424          if (!thread)
425            continue;
426          if (!thread.tempCpuSlices)
427            thread.tempCpuSlices = [];
428          thread.tempCpuSlices.push(slice);
429        }
430      }
431
432      for (var i in this.wakeups_) {
433        var wakeup = this.wakeups_[i];
434        var thread = this.threadsByLinuxPid[wakeup.tid];
435        if (!thread)
436          continue;
437        thread.tempWakeups = thread.tempWakeups || [];
438        thread.tempWakeups.push(wakeup);
439      }
440
441      // Create slices for when the thread is not running.
442      var runningId = tracing.getColorIdByName('running');
443      var runnableId = tracing.getColorIdByName('runnable');
444      var sleepingId = tracing.getColorIdByName('sleeping');
445      var ioWaitId = tracing.getColorIdByName('iowait');
446      this.model_.getAllThreads().forEach(function(thread) {
447        if (thread.tempCpuSlices === undefined)
448          return;
449        var origSlices = thread.tempCpuSlices;
450        delete thread.tempCpuSlices;
451
452        origSlices.sort(function(x, y) {
453          return x.start - y.start;
454        });
455
456        var wakeups = thread.tempWakeups || [];
457        delete thread.tempWakeups;
458        wakeups.sort(function(x, y) {
459          return x.ts - y.ts;
460        });
461
462        // Walk the slice list and put slices between each original slice to
463        // show when the thread isn't running.
464        var slices = [];
465
466        if (origSlices.length) {
467          var slice = origSlices[0];
468
469          if (wakeups.length && wakeups[0].ts < slice.start) {
470            var wakeup = wakeups.shift();
471            var wakeupDuration = slice.start - wakeup.ts;
472            var args = {'wakeup from tid': wakeup.fromTid};
473            slices.push(new tracing.trace_model.Slice(
474                '', 'Runnable', runnableId, wakeup.ts, args, wakeupDuration));
475          }
476
477          slices.push(new tracing.trace_model.Slice('', 'Running', runningId,
478              slice.start, {}, slice.duration));
479        }
480
481        var wakeup = undefined;
482        for (var i = 1; i < origSlices.length; i++) {
483          var prevSlice = origSlices[i - 1];
484          var nextSlice = origSlices[i];
485          var midDuration = nextSlice.start - prevSlice.end;
486          while (wakeups.length && wakeups[0].ts < nextSlice.start) {
487            var w = wakeups.shift();
488            if (wakeup === undefined && w.ts > prevSlice.end) {
489              wakeup = w;
490            }
491          }
492
493          // Push a sleep slice onto the slices list, interrupting it with a
494          // wakeup if appropriate.
495          var pushSleep = function(title, id) {
496            if (wakeup !== undefined) {
497              midDuration = wakeup.ts - prevSlice.end;
498            }
499            slices.push(new tracing.trace_model.Slice(
500                '', title, id, prevSlice.end, {}, midDuration));
501            if (wakeup !== undefined) {
502              var wakeupDuration = nextSlice.start - wakeup.ts;
503              var args = {'wakeup from tid': wakeup.fromTid};
504              slices.push(new tracing.trace_model.Slice(
505                  '', 'Runnable', runnableId, wakeup.ts, args, wakeupDuration));
506              wakeup = undefined;
507            }
508          };
509
510          if (prevSlice.args.stateWhenDescheduled == 'S') {
511            pushSleep('Sleeping', sleepingId);
512          } else if (prevSlice.args.stateWhenDescheduled == 'R' ||
513                     prevSlice.args.stateWhenDescheduled == 'R+') {
514            slices.push(new tracing.trace_model.Slice(
515                '', 'Runnable', runnableId, prevSlice.end, {}, midDuration));
516          } else if (prevSlice.args.stateWhenDescheduled == 'D') {
517            pushSleep('Uninterruptible Sleep', ioWaitId);
518          } else if (prevSlice.args.stateWhenDescheduled == 'T') {
519            slices.push(new tracing.trace_model.Slice('', '__TASK_STOPPED',
520                ioWaitId, prevSlice.end, {}, midDuration));
521          } else if (prevSlice.args.stateWhenDescheduled == 't') {
522            slices.push(new tracing.trace_model.Slice('', 'debug', ioWaitId,
523                prevSlice.end, {}, midDuration));
524          } else if (prevSlice.args.stateWhenDescheduled == 'Z') {
525            slices.push(new tracing.trace_model.Slice('', 'Zombie', ioWaitId,
526                prevSlice.end, {}, midDuration));
527          } else if (prevSlice.args.stateWhenDescheduled == 'X') {
528            slices.push(new tracing.trace_model.Slice('', 'Exit Dead', ioWaitId,
529                prevSlice.end, {}, midDuration));
530          } else if (prevSlice.args.stateWhenDescheduled == 'x') {
531            slices.push(new tracing.trace_model.Slice('', 'Task Dead', ioWaitId,
532                prevSlice.end, {}, midDuration));
533          } else if (prevSlice.args.stateWhenDescheduled == 'K') {
534            slices.push(new tracing.trace_model.Slice('', 'Wakekill', ioWaitId,
535                prevSlice.end, {}, midDuration));
536          } else if (prevSlice.args.stateWhenDescheduled == 'W') {
537            slices.push(new tracing.trace_model.Slice('', 'Waking', ioWaitId,
538                prevSlice.end, {}, midDuration));
539          } else if (prevSlice.args.stateWhenDescheduled == 'D|K') {
540            pushSleep('Uninterruptible Sleep | WakeKill', ioWaitId);
541          } else if (prevSlice.args.stateWhenDescheduled == 'D|W') {
542            pushSleep('Uninterruptible Sleep | Waking', ioWaitId);
543          } else {
544            slices.push(new tracing.trace_model.Slice('', 'UNKNOWN', ioWaitId,
545                prevSlice.end, {}, midDuration));
546            this.model_.importErrors.push('Unrecognized sleep state: ' +
547                prevSlice.args.stateWhenDescheduled);
548          }
549
550          slices.push(new tracing.trace_model.Slice('', 'Running', runningId,
551              nextSlice.start, {}, nextSlice.duration));
552        }
553        thread.cpuSlices = slices;
554      }, this);
555    },
556
557    /**
558     * Walks the slices stored on this.cpuStates_ and adjusts their timestamps
559     * based on any alignment metadata we discovered.
560     */
561    alignClocks: function(isSecondaryImport) {
562      if (this.clockSyncRecords_.length == 0) {
563        // If this is a secondary import, and no clock syncing records were
564        // found, then abort the import. Otherwise, just skip clock alignment.
565        if (!isSecondaryImport)
566          return true;
567
568        // Remove the newly imported CPU slices from the model.
569        this.abortImport();
570        return false;
571      }
572
573      // Shift all the slice times based on the sync record.
574      var sync = this.clockSyncRecords_[0];
575      // NB: parentTS of zero denotes no times-shift; this is
576      // used when user and kernel event clocks are identical.
577      if (sync.parentTS == 0 || sync.parentTS == sync.perfTS)
578        return true;
579      var timeShift = sync.parentTS - sync.perfTS;
580      for (var cpuNumber in this.cpuStates_) {
581        var cpuState = this.cpuStates_[cpuNumber];
582        var cpu = cpuState.cpu;
583
584        for (var i = 0; i < cpu.slices.length; i++) {
585          var slice = cpu.slices[i];
586          slice.start = slice.start + timeShift;
587          slice.duration = slice.duration;
588        }
589
590        for (var counterName in cpu.counters) {
591          var counter = cpu.counters[counterName];
592          for (var sI = 0; sI < counter.timestamps.length; sI++)
593            counter.timestamps[sI] = (counter.timestamps[sI] + timeShift);
594        }
595      }
596      for (var kernelThreadName in this.kernelThreadStates_) {
597        var kthread = this.kernelThreadStates_[kernelThreadName];
598        var thread = kthread.thread;
599        thread.shiftTimestampsForward(timeShift);
600      }
601      return true;
602    },
603
604    /**
605     * Removes any data that has been added to the model because of an error
606     * detected during the import.
607     */
608    abortImport: function() {
609      if (this.pushedEventsToThreads)
610        throw new Error('Cannot abort, have alrady pushedCpuDataToThreads.');
611
612      for (var cpuNumber in this.cpuStates_)
613        delete this.model_.kernel.cpus[cpuNumber];
614      for (var kernelThreadName in this.kernelThreadStates_) {
615        var kthread = this.kernelThreadStates_[kernelThreadName];
616        var thread = kthread.thread;
617        var process = thread.parent;
618        delete process.threads[thread.tid];
619        delete this.model_.processes[process.pid];
620      }
621      this.model_.importErrors.push(
622          'Cannot import kernel trace without a clock sync.');
623    },
624
625    /**
626     * Creates an instance of each registered linux perf event parser.
627     * This allows the parsers to register handlers for the events they
628     * understand.  We also register our own special handlers (for the
629     * timestamp synchronization markers).
630     */
631    createParsers: function() {
632      // Instantiate the parsers; this will register handlers for known events
633      var parserConstructors =
634          tracing.importer.linux_perf.Parser.getSubtypeConstructors();
635      for (var i = 0; i < parserConstructors.length; ++i) {
636        var parserConstructor = parserConstructors[i];
637        this.parsers_.push(new parserConstructor(this));
638      }
639
640      this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
641          LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
642      this.registerEventHandler('tracing_mark_write',
643          LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
644      // NB: old-style trace markers; deprecated
645      this.registerEventHandler('0:trace_event_clock_sync',
646          LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
647      this.registerEventHandler('0',
648          LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
649    },
650
651    /**
652     * Registers a linux perf event parser used by importCpuData.
653     */
654    registerEventHandler: function(eventName, handler) {
655      // TODO(sleffler) how to handle conflicts?
656      this.eventHandlers_[eventName] = handler;
657    },
658
659    /**
660     * Records the fact that a pid has become runnable. This data will
661     * eventually get used to derive each thread's cpuSlices array.
662     */
663    markPidRunnable: function(ts, pid, comm, prio, fromPid) {
664      // The the pids that get passed in to this function are Linux kernel
665      // pids, which identify threads.  The rest of trace-viewer refers to
666      // these as tids, so the change of nomenclature happens in the following
667      // construction of the wakeup object.
668      this.wakeups_.push({ts: ts, tid: pid, fromTid: fromPid});
669    },
670
671    importError: function(message) {
672      this.model_.importErrors.push(
673          'Line ' + (this.lineNumberBase + this.lineNumber + 1) +
674          ': ' + message);
675    },
676
677    /**
678     * Processes a trace_event_clock_sync event.
679     */
680    traceClockSyncEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
681      var event = /parent_ts=(\d+\.?\d*)/.exec(eventBase.details);
682      if (!event)
683        return false;
684
685      this.clockSyncRecords_.push({
686        perfTS: ts,
687        parentTS: event[1] * 1000
688      });
689      return true;
690    },
691
692    /**
693     * Processes a trace_marking_write event.
694     */
695    traceMarkingWriteEvent: function(eventName, cpuNumber, pid, ts, eventBase,
696                                     threadName) {
697      var event = /^\s*(\w+):\s*(.*)$/.exec(eventBase.details);
698      if (!event) {
699        // Check if the event matches events traced by the Android framework
700        var tag = eventBase.details.substring(0, 2);
701        if (tag == 'B|' || tag == 'E' || tag == 'E|' || tag == 'C|' ||
702            tag == 'S|' || tag == 'F|') {
703          eventBase.subEventName = 'android';
704        } else {
705          return false;
706        }
707      } else {
708        eventBase.subEventName = event[1];
709        eventBase.details = event[2];
710      }
711
712      var writeEventName = eventName + ':' + eventBase.subEventName;
713      var handler = this.eventHandlers_[writeEventName];
714      if (!handler) {
715        this.importError('Unknown trace_marking_write event ' + writeEventName);
716        return true;
717      }
718      return handler(writeEventName, cpuNumber, pid, ts, eventBase, threadName);
719    },
720
721    /**
722     * Walks the this.events_ structure and creates Cpu objects.
723     */
724    importCpuData: function() {
725      var extractResult = LinuxPerfImporter._extractEventsFromSystraceHTML(
726          this.events_, true);
727      if (extractResult.ok) {
728        this.lineNumberBase = extractResult.events_begin_at_line;
729        this.lines_ = extractResult.lines;
730      } else {
731        this.lineNumberBase = 0;
732        this.lines_ = this.events_.split('\n');
733      }
734
735      var lineParser = null;
736      for (this.lineNumber = 0;
737           this.lineNumber < this.lines_.length;
738          ++this.lineNumber) {
739        var line = this.lines_[this.lineNumber];
740        if (line.length == 0 || /^#/.test(line))
741          continue;
742        if (lineParser == null) {
743          lineParser = autoDetectLineParser(line);
744          if (lineParser == null) {
745            this.importError('Cannot parse line: ' + line);
746            continue;
747          }
748        }
749        var eventBase = lineParser(line);
750        if (!eventBase) {
751          this.importError('Unrecognized line: ' + line);
752          continue;
753        }
754
755        var pid = parseInt(eventBase.pid);
756        var cpuNumber = parseInt(eventBase.cpuNumber);
757        var ts = parseFloat(eventBase.timestamp) * 1000;
758        var eventName = eventBase.eventName;
759
760        var handler = this.eventHandlers_[eventName];
761        if (!handler) {
762          this.importError('Unknown event ' + eventName + ' (' + line + ')');
763          continue;
764        }
765        if (!handler(eventName, cpuNumber, pid, ts, eventBase))
766          this.importError('Malformed ' + eventName + ' event (' + line + ')');
767      }
768    }
769  };
770
771  tracing.TraceModel.registerImporter(LinuxPerfImporter);
772
773  return {
774    LinuxPerfImporter: LinuxPerfImporter,
775    _LinuxPerfImporterTestExports: TestExports
776  };
777
778});
779