Home | History | Annotate | Download | only in src
      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  * timeline model. 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 base.require('timeline_model');
     20 base.require('timeline_color_scheme');
     21 base.require('linux_perf_bus_parser');
     22 base.require('linux_perf_clock_parser');
     23 base.require('linux_perf_cpufreq_parser');
     24 base.require('linux_perf_drm_parser');
     25 base.require('linux_perf_exynos_parser');
     26 base.require('linux_perf_gesture_parser');
     27 base.require('linux_perf_i915_parser');
     28 base.require('linux_perf_mali_parser');
     29 base.require('linux_perf_power_parser');
     30 base.require('linux_perf_sched_parser');
     31 base.require('linux_perf_workqueue_parser');
     32 base.require('linux_perf_android_parser');
     33 
     34 base.exportTo('tracing', function() {
     35   /**
     36    * Represents the scheduling state for a single thread.
     37    * @constructor
     38    */
     39   function CpuState(cpu) {
     40     this.cpu = cpu;
     41   }
     42 
     43   CpuState.prototype = {
     44     __proto__: Object.prototype,
     45 
     46     /**
     47      * Switches the active pid on this Cpu. If necessary, add a TimelineSlice
     48      * to the cpu representing the time spent on that Cpu since the last call to
     49      * switchRunningLinuxPid.
     50      */
     51     switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) {
     52       // Generate a slice if the last active pid was not the idle task
     53       if (this.lastActivePid !== undefined && this.lastActivePid != 0) {
     54         var duration = ts - this.lastActiveTs;
     55         var thread = importer.threadsByLinuxPid[this.lastActivePid];
     56         if (thread)
     57           name = thread.userFriendlyName;
     58         else
     59           name = this.lastActiveComm;
     60 
     61         var slice = new tracing.TimelineSlice('', name,
     62                                               tracing.getStringColorId(name),
     63                                               this.lastActiveTs,
     64                                               {
     65                                                 comm: this.lastActiveComm,
     66                                                 tid: this.lastActivePid,
     67                                                 prio: this.lastActivePrio,
     68                                                 stateWhenDescheduled: prevState
     69                                               },
     70                                               duration);
     71         this.cpu.slices.push(slice);
     72       }
     73 
     74       this.lastActiveTs = ts;
     75       this.lastActivePid = pid;
     76       this.lastActiveComm = comm;
     77       this.lastActivePrio = prio;
     78     }
     79   };
     80 
     81   /**
     82    * Imports linux perf events into a specified model.
     83    * @constructor
     84    */
     85   function LinuxPerfImporter(model, events) {
     86     this.importPriority = 2;
     87     this.model_ = model;
     88     this.events_ = events;
     89     this.clockSyncRecords_ = [];
     90     this.cpuStates_ = {};
     91     this.kernelThreadStates_ = {};
     92     this.buildMapFromLinuxPidsToTimelineThreads();
     93     this.lineNumber = -1;
     94     this.pseudoThreadCounter = 1;
     95     this.parsers_ = [];
     96     this.eventHandlers_ = {};
     97   }
     98 
     99   TestExports = {};
    100 
    101   // Matches the default trace record in 3.2 and later (includes irq-info):
    102   //          <idle>-0     [001] d...  1.23: sched_switch
    103   var lineREWithIRQInfo = new RegExp(
    104       '^\\s*(.+?)\\s+\\[(\\d+)\\]' + '\\s+[dX.][N.][Hhs.][0-9a-f.]' +
    105       '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$');
    106   TestExports.lineREWithIRQInfo = lineREWithIRQInfo;
    107 
    108   // Matches the default trace record pre-3.2:
    109   //          <idle>-0     [001]  1.23: sched_switch
    110   var lineRE = /^\s*(.+?)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/;
    111   TestExports.lineRE = lineRE;
    112 
    113   // Matches the trace_event_clock_sync record
    114   //  0: trace_event_clock_sync: parent_ts=19581477508
    115   var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/;
    116   TestExports.traceEventClockSyncRE = traceEventClockSyncRE;
    117 
    118   // Some kernel trace events are manually classified in slices and
    119   // hand-assigned a pseudo PID.
    120   var pseudoKernelPID = 0;
    121 
    122   /**
    123    * Deduce the format of trace data. Linix kernels prior to 3.3 used
    124    * one format (by default); 3.4 and later used another.
    125    *
    126    * @return {string} the regular expression for parsing data when
    127    * the format is recognized; otherwise null.
    128    */
    129   function autoDetectLineRE(line) {
    130     if (lineREWithIRQInfo.test(line))
    131       return lineREWithIRQInfo;
    132     if (lineRE.test(line))
    133       return lineRE;
    134     return null;
    135   };
    136   TestExports.autoDetectLineRE = autoDetectLineRE;
    137 
    138   /**
    139    * Guesses whether the provided events is a Linux perf string.
    140    * Looks for the magic string "# tracer" at the start of the file,
    141    * or the typical task-pid-cpu-timestamp-function sequence of a typical
    142    * trace's body.
    143    *
    144    * @return {boolean} True when events is a linux perf array.
    145    */
    146   LinuxPerfImporter.canImport = function(events) {
    147     if (!(typeof(events) === 'string' || events instanceof String))
    148       return false;
    149 
    150     if (/^# tracer:/.test(events))
    151       return true;
    152 
    153     var m = /^(.+)\n/.exec(events);
    154     if (m)
    155       events = m[1];
    156     if (autoDetectLineRE(events))
    157       return true;
    158 
    159     return false;
    160   };
    161 
    162   LinuxPerfImporter.prototype = {
    163     __proto__: Object.prototype,
    164 
    165     /**
    166      * Precomputes a lookup table from linux pids back to existing
    167      * TimelineThreads. This is used during importing to add information to each
    168      * timeline thread about whether it was running, descheduled, sleeping, et
    169      * cetera.
    170      */
    171     buildMapFromLinuxPidsToTimelineThreads: function() {
    172       this.threadsByLinuxPid = {};
    173       this.model_.getAllThreads().forEach(
    174           function(thread) {
    175             this.threadsByLinuxPid[thread.tid] = thread;
    176           }.bind(this));
    177     },
    178 
    179     /**
    180      * @return {CpuState} A CpuState corresponding to the given cpuNumber.
    181      */
    182     getOrCreateCpuState: function(cpuNumber) {
    183       if (!this.cpuStates_[cpuNumber]) {
    184         var cpu = this.model_.getOrCreateCpu(cpuNumber);
    185         this.cpuStates_[cpuNumber] = new CpuState(cpu);
    186       }
    187       return this.cpuStates_[cpuNumber];
    188     },
    189 
    190     /**
    191      * @return {TimelinThread} A thread corresponding to the kernelThreadName.
    192      */
    193     getOrCreateKernelThread: function(kernelThreadName, opt_pid, opt_tid) {
    194       if (!this.kernelThreadStates_[kernelThreadName]) {
    195         var pid = opt_pid;
    196         if (pid == undefined) {
    197           pid = /.+-(\d+)/.exec(kernelThreadName)[1];
    198           pid = parseInt(pid, 10);
    199         }
    200         var tid = opt_tid;
    201         if (tid == undefined)
    202           tid = pid;
    203 
    204         var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid);
    205         thread.name = kernelThreadName;
    206         this.kernelThreadStates_[kernelThreadName] = {
    207           pid: pid,
    208           thread: thread,
    209           openSlice: undefined,
    210           openSliceTS: undefined
    211         };
    212         this.threadsByLinuxPid[pid] = thread;
    213       }
    214       return this.kernelThreadStates_[kernelThreadName];
    215     },
    216 
    217     /**
    218      * @return {TimelinThread} A pseudo thread corresponding to the
    219      * threadName.  Pseudo threads are for events that we want to break
    220      * out to a separate timeline but would not otherwise happen.
    221      * These threads are assigned to pseudoKernelPID and given a
    222      * unique (incrementing) TID.
    223      */
    224     getOrCreatePseudoThread: function(threadName) {
    225       var thread = this.kernelThreadStates_[threadName];
    226       if (!thread) {
    227         thread = this.getOrCreateKernelThread(threadName, pseudoKernelPID,
    228             this.pseudoThreadCounter);
    229         this.pseudoThreadCounter++;
    230       }
    231       return thread;
    232     },
    233 
    234     /**
    235      * Imports the data in this.events_ into model_.
    236      */
    237     importEvents: function(isSecondaryImport) {
    238       this.createParsers();
    239       this.importCpuData();
    240       if (!this.alignClocks(isSecondaryImport))
    241         return;
    242       this.buildMapFromLinuxPidsToTimelineThreads();
    243       this.buildPerThreadCpuSlicesFromCpuState();
    244     },
    245 
    246     /**
    247      * Called by the TimelineModel after all other importers have imported their
    248      * events.
    249      */
    250     finalizeImport: function() {
    251     },
    252 
    253     /**
    254      * Builds the cpuSlices array on each thread based on our knowledge of what
    255      * each Cpu is doing.  This is done only for TimelineThreads that are
    256      * already in the model, on the assumption that not having any traced data
    257      * on a thread means that it is not of interest to the user.
    258      */
    259     buildPerThreadCpuSlicesFromCpuState: function() {
    260       // Push the cpu slices to the threads that they run on.
    261       for (var cpuNumber in this.cpuStates_) {
    262         var cpuState = this.cpuStates_[cpuNumber];
    263         var cpu = cpuState.cpu;
    264 
    265         for (var i = 0; i < cpu.slices.length; i++) {
    266           var slice = cpu.slices[i];
    267 
    268           var thread = this.threadsByLinuxPid[slice.args.tid];
    269           if (!thread)
    270             continue;
    271           if (!thread.tempCpuSlices)
    272             thread.tempCpuSlices = [];
    273           thread.tempCpuSlices.push(slice);
    274         }
    275       }
    276 
    277       // Create slices for when the thread is not running.
    278       var runningId = tracing.getColorIdByName('running');
    279       var runnableId = tracing.getColorIdByName('runnable');
    280       var sleepingId = tracing.getColorIdByName('sleeping');
    281       var ioWaitId = tracing.getColorIdByName('iowait');
    282       this.model_.getAllThreads().forEach(function(thread) {
    283         if (!thread.tempCpuSlices)
    284           return;
    285         var origSlices = thread.tempCpuSlices;
    286         delete thread.tempCpuSlices;
    287 
    288         origSlices.sort(function(x, y) {
    289           return x.start - y.start;
    290         });
    291 
    292         // Walk the slice list and put slices between each original slice
    293         // to show when the thread isn't running
    294         var slices = [];
    295         if (origSlices.length) {
    296           var slice = origSlices[0];
    297           slices.push(new tracing.TimelineSlice('', 'Running', runningId,
    298               slice.start, {}, slice.duration));
    299         }
    300         for (var i = 1; i < origSlices.length; i++) {
    301           var prevSlice = origSlices[i - 1];
    302           var nextSlice = origSlices[i];
    303           var midDuration = nextSlice.start - prevSlice.end;
    304           if (prevSlice.args.stateWhenDescheduled == 'S') {
    305             slices.push(new tracing.TimelineSlice('', 'Sleeping', sleepingId,
    306                 prevSlice.end, {}, midDuration));
    307           } else if (prevSlice.args.stateWhenDescheduled == 'R' ||
    308                      prevSlice.args.stateWhenDescheduled == 'R+') {
    309             slices.push(new tracing.TimelineSlice('', 'Runnable', runnableId,
    310                 prevSlice.end, {}, midDuration));
    311           } else if (prevSlice.args.stateWhenDescheduled == 'D') {
    312             slices.push(new tracing.TimelineSlice(
    313                 '', 'Uninterruptible Sleep', ioWaitId,
    314                 prevSlice.end, {}, midDuration));
    315           } else if (prevSlice.args.stateWhenDescheduled == 'T') {
    316             slices.push(new tracing.TimelineSlice('', '__TASK_STOPPED',
    317                 ioWaitId, prevSlice.end, {}, midDuration));
    318           } else if (prevSlice.args.stateWhenDescheduled == 't') {
    319             slices.push(new tracing.TimelineSlice('', 'debug', ioWaitId,
    320                 prevSlice.end, {}, midDuration));
    321           } else if (prevSlice.args.stateWhenDescheduled == 'Z') {
    322             slices.push(new tracing.TimelineSlice('', 'Zombie', ioWaitId,
    323                 prevSlice.end, {}, midDuration));
    324           } else if (prevSlice.args.stateWhenDescheduled == 'X') {
    325             slices.push(new tracing.TimelineSlice('', 'Exit Dead', ioWaitId,
    326                 prevSlice.end, {}, midDuration));
    327           } else if (prevSlice.args.stateWhenDescheduled == 'x') {
    328             slices.push(new tracing.TimelineSlice('', 'Task Dead', ioWaitId,
    329                 prevSlice.end, {}, midDuration));
    330           } else if (prevSlice.args.stateWhenDescheduled == 'W') {
    331             slices.push(new tracing.TimelineSlice('', 'WakeKill', ioWaitId,
    332                 prevSlice.end, {}, midDuration));
    333           } else if (prevSlice.args.stateWhenDescheduled == 'D|W') {
    334             slices.push(new tracing.TimelineSlice(
    335                 '', 'Uninterruptable Sleep | WakeKill', ioWaitId,
    336                 prevSlice.end, {}, midDuration));
    337           } else {
    338             throw new Error('Unrecognized state: ') +
    339                 prevSlice.args.stateWhenDescheduled;
    340           }
    341 
    342           slices.push(new tracing.TimelineSlice('', 'Running', runningId,
    343               nextSlice.start, {}, nextSlice.duration));
    344         }
    345         thread.cpuSlices = slices;
    346       });
    347     },
    348 
    349     /**
    350      * Walks the slices stored on this.cpuStates_ and adjusts their timestamps
    351      * based on any alignment metadata we discovered.
    352      */
    353     alignClocks: function(isSecondaryImport) {
    354       if (this.clockSyncRecords_.length == 0) {
    355         // If this is a secondary import, and no clock syncing records were
    356         // found, then abort the import. Otherwise, just skip clock alignment.
    357         if (!isSecondaryImport)
    358           return true;
    359 
    360         // Remove the newly imported CPU slices from the model.
    361         this.abortImport();
    362         return false;
    363       }
    364 
    365       // Shift all the slice times based on the sync record.
    366       var sync = this.clockSyncRecords_[0];
    367       // NB: parentTS of zero denotes no times-shift; this is
    368       // used when user and kernel event clocks are identical.
    369       if (sync.parentTS == 0 || sync.parentTS == sync.perfTS)
    370         return true;
    371       var timeShift = sync.parentTS - sync.perfTS;
    372       for (var cpuNumber in this.cpuStates_) {
    373         var cpuState = this.cpuStates_[cpuNumber];
    374         var cpu = cpuState.cpu;
    375 
    376         for (var i = 0; i < cpu.slices.length; i++) {
    377           var slice = cpu.slices[i];
    378           slice.start = slice.start + timeShift;
    379           slice.duration = slice.duration;
    380         }
    381 
    382         for (var counterName in cpu.counters) {
    383           var counter = cpu.counters[counterName];
    384           for (var sI = 0; sI < counter.timestamps.length; sI++)
    385             counter.timestamps[sI] = (counter.timestamps[sI] + timeShift);
    386         }
    387       }
    388       for (var kernelThreadName in this.kernelThreadStates_) {
    389         var kthread = this.kernelThreadStates_[kernelThreadName];
    390         var thread = kthread.thread;
    391         thread.shiftTimestampsForward(timeShift);
    392       }
    393       return true;
    394     },
    395 
    396     /**
    397      * Removes any data that has been added to the model because of an error
    398      * detected during the import.
    399      */
    400     abortImport: function() {
    401       if (this.pushedEventsToThreads)
    402         throw new Error('Cannot abort, have alrady pushedCpuDataToThreads.');
    403 
    404       for (var cpuNumber in this.cpuStates_)
    405         delete this.model_.cpus[cpuNumber];
    406       for (var kernelThreadName in this.kernelThreadStates_) {
    407         var kthread = this.kernelThreadStates_[kernelThreadName];
    408         var thread = kthread.thread;
    409         var process = thread.parent;
    410         delete process.threads[thread.tid];
    411         delete this.model_.processes[process.pid];
    412       }
    413       this.model_.importErrors.push(
    414           'Cannot import kernel trace without a clock sync.');
    415     },
    416 
    417     /**
    418      * Creates an instance of each registered linux perf event parser.
    419      * This allows the parsers to register handlers for the events they
    420      * understand.  We also register our own special handlers (for the
    421      * timestamp synchronization markers).
    422      */
    423     createParsers: function() {
    424       // Instantiate the parsers; this will register handlers for known events
    425       var parserConstructors = tracing.LinuxPerfParser.getSubtypeConstructors();
    426       for (var i = 0; i < parserConstructors.length; ++i) {
    427         var parserConstructor = parserConstructors[i];
    428         this.parsers_.push(new parserConstructor(this));
    429       }
    430 
    431       this.registerEventHandler('tracing_mark_write:trace_event_clock_sync',
    432           LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
    433       this.registerEventHandler('tracing_mark_write',
    434           LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
    435       // NB: old-style trace markers; deprecated
    436       this.registerEventHandler('0:trace_event_clock_sync',
    437           LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this));
    438       this.registerEventHandler('0',
    439           LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this));
    440     },
    441 
    442     /**
    443      * Registers a linux perf event parser used by importCpuData.
    444      */
    445     registerEventHandler: function(eventName, handler) {
    446       // TODO(sleffler) how to handle conflicts?
    447       this.eventHandlers_[eventName] = handler;
    448     },
    449 
    450     /**
    451      * Records the fact that a pid has become runnable. This data will
    452      * eventually get used to derive each thread's cpuSlices array.
    453      */
    454     markPidRunnable: function(ts, pid, comm, prio) {
    455       // TODO(nduca): implement this functionality.
    456     },
    457 
    458     importError: function(message) {
    459       this.model_.importErrors.push('Line ' + (this.lineNumber + 1) +
    460           ': ' + message);
    461     },
    462 
    463     /**
    464      * Processes a trace_event_clock_sync event.
    465      */
    466     traceClockSyncEvent: function(eventName, cpuNumber, pid, ts, eventBase) {
    467       var event = /parent_ts=(\d+\.?\d*)/.exec(eventBase[2]);
    468       if (!event)
    469         return false;
    470 
    471       this.clockSyncRecords_.push({
    472         perfTS: ts,
    473         parentTS: event[1] * 1000
    474       });
    475       return true;
    476     },
    477 
    478     /**
    479      * Processes a trace_marking_write event.
    480      */
    481     traceMarkingWriteEvent: function(eventName, cpuNumber, pid, ts, eventBase,
    482                                      threadName) {
    483       var event = /^\s*(\w+):\s*(.*)$/.exec(eventBase[5]);
    484       if (!event) {
    485         // Check if the event matches events traced by the Android framework
    486         if (eventBase[5].lastIndexOf('B|', 0) === 0 ||
    487             eventBase[5] === 'E' ||
    488             eventBase[5].lastIndexOf('C|', 0) === 0)
    489           event = [eventBase[5], 'android', eventBase[5]];
    490         else
    491           return false;
    492       }
    493 
    494       var writeEventName = eventName + ':' + event[1];
    495       var threadName = (/(.+)-\d+/.exec(eventBase[1]))[1];
    496       var handler = this.eventHandlers_[writeEventName];
    497       if (!handler) {
    498         this.importError('Unknown trace_marking_write event ' + writeEventName);
    499         return true;
    500       }
    501       return handler(writeEventName, cpuNumber, pid, ts, event, threadName);
    502     },
    503 
    504     /**
    505      * Walks the this.events_ structure and creates TimelineCpu objects.
    506      */
    507     importCpuData: function() {
    508       this.lines_ = this.events_.split('\n');
    509 
    510       var lineRE = null;
    511       for (this.lineNumber = 0; this.lineNumber < this.lines_.length;
    512           ++this.lineNumber) {
    513         var line = this.lines_[this.lineNumber];
    514         if (line.length == 0 || /^#/.test(line))
    515           continue;
    516         if (lineRE == null) {
    517           lineRE = autoDetectLineRE(line);
    518           if (lineRE == null) {
    519             this.importError('Cannot parse line: ' + line);
    520             continue;
    521           }
    522         }
    523         var eventBase = lineRE.exec(line);
    524         if (!eventBase) {
    525           this.importError('Unrecognized line: ' + line);
    526           continue;
    527         }
    528 
    529         var pid = parseInt((/.+-(\d+)/.exec(eventBase[1]))[1]);
    530         var cpuNumber = parseInt(eventBase[2]);
    531         var ts = parseFloat(eventBase[3]) * 1000;
    532         var eventName = eventBase[4];
    533 
    534         var handler = this.eventHandlers_[eventName];
    535         if (!handler) {
    536           this.importError('Unknown event ' + eventName + ' (' + line + ')');
    537           continue;
    538         }
    539         if (!handler(eventName, cpuNumber, pid, ts, eventBase))
    540           this.importError('Malformed ' + eventName + ' event (' + line + ')');
    541       }
    542     }
    543   };
    544 
    545   tracing.TimelineModel.registerImporter(LinuxPerfImporter);
    546 
    547   return {
    548     LinuxPerfImporter: LinuxPerfImporter,
    549     _LinuxPerfImporterTestExports: TestExports
    550   };
    551 
    552 });
    553