// Copyright (c) 2012 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. /** * @fileoverview Imports text files in the Linux event trace format into the * model. This format is output both by sched_trace and by Linux's perf * tool. * * This importer assumes the events arrive as a string. The unit tests provide * examples of the trace format. * * Linux scheduler traces use a definition for 'pid' that is different than * tracing uses. Whereas tracing uses pid to identify a specific process, a pid * in a linux trace refers to a specific thread within a process. Within this * file, we the definition used in Linux traces, as it improves the importing * code's readability. */ 'use strict'; base.require('model'); base.require('color_scheme'); base.require('importer.linux_perf.bus_parser'); base.require('importer.linux_perf.clock_parser'); base.require('importer.linux_perf.cpufreq_parser'); base.require('importer.linux_perf.disk_parser'); base.require('importer.linux_perf.drm_parser'); base.require('importer.linux_perf.exynos_parser'); base.require('importer.linux_perf.gesture_parser'); base.require('importer.linux_perf.i915_parser'); base.require('importer.linux_perf.mali_parser'); base.require('importer.linux_perf.power_parser'); base.require('importer.linux_perf.sched_parser'); base.require('importer.linux_perf.workqueue_parser'); base.require('importer.linux_perf.android_parser'); base.require('importer.linux_perf.kfunc_parser'); base.exportTo('tracing.importer', function() { /** * Represents the scheduling state for a single thread. * @constructor */ function CpuState(cpu) { this.cpu = cpu; } CpuState.prototype = { __proto__: Object.prototype, /** * Switches the active pid on this Cpu. If necessary, add a Slice * to the cpu representing the time spent on that Cpu since the last call to * switchRunningLinuxPid. */ switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) { // Generate a slice if the last active pid was not the idle task if (this.lastActivePid !== undefined && this.lastActivePid != 0) { var duration = ts - this.lastActiveTs; var thread = importer.threadsByLinuxPid[this.lastActivePid]; var name; if (thread) name = thread.userFriendlyName; else name = this.lastActiveComm; var slice = new tracing.model.Slice('', name, tracing.getStringColorId(name), this.lastActiveTs, { comm: this.lastActiveComm, tid: this.lastActivePid, prio: this.lastActivePrio, stateWhenDescheduled: prevState }, duration); this.cpu.slices.push(slice); } this.lastActiveTs = ts; this.lastActivePid = pid; this.lastActiveComm = comm; this.lastActivePrio = prio; } }; /** * Imports linux perf events into a specified model. * @constructor */ function LinuxPerfImporter(model, events) { this.importPriority = 2; this.model_ = model; this.events_ = events; this.clockSyncRecords_ = []; this.cpuStates_ = {}; this.wakeups_ = []; this.kernelThreadStates_ = {}; this.buildMapFromLinuxPidsToThreads(); this.lineNumberBase = 0; this.lineNumber = -1; this.pseudoThreadCounter = 1; this.parsers_ = []; this.eventHandlers_ = {}; } TestExports = {}; // Matches the trace record in 3.2 and later with the print-tgid option: // <idle>-0 0 [001] d... 1.23: sched_switch // // A TGID (Thread Group ID) is basically what the Linux kernel calls what // userland refers to as a process ID (as opposed to a Linux pid, which is // what userland calls a thread ID). var lineREWithTGID = new RegExp( '^\\s*(.+)-(\\d+)\\s+\\(\\s*(\\d+|-+)\\)\\s\\[(\\d+)\\]' + '\\s+[dX.][N.][Hhs.][0-9a-f.]' + '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$'); var lineParserWithTGID = function(line) { var groups = lineREWithTGID.exec(line); if (!groups) { return groups; } var tgid = groups[3]; if (tgid[0] === '-') tgid = undefined; return { threadName: groups[1], pid: groups[2], tgid: tgid, cpuNumber: groups[4], timestamp: groups[5], eventName: groups[6], details: groups[7] }; } TestExports.lineParserWithTGID = lineParserWithTGID; // Matches the default trace record in 3.2 and later (includes irq-info): // <idle>-0 [001] d... 1.23: sched_switch var lineREWithIRQInfo = new RegExp( '^\\s*(.+)-(\\d+)\\s+\\[(\\d+)\\]' + '\\s+[dX.][N.][Hhs.][0-9a-f.]' + '\\s+(\\d+\\.\\d+):\\s+(\\S+):\\s(.*)$'); var lineParserWithIRQInfo = function(line) { var groups = lineREWithIRQInfo.exec(line); if (!groups) { return groups; } return { threadName: groups[1], pid: groups[2], cpuNumber: groups[3], timestamp: groups[4], eventName: groups[5], details: groups[6] }; } TestExports.lineParserWithIRQInfo = lineParserWithIRQInfo; // Matches the default trace record pre-3.2: // <idle>-0 [001] 1.23: sched_switch var lineREWithLegacyFmt = /^\s*(.+)-(\d+)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/; var lineParserWithLegacyFmt = function(line) { var groups = lineREWithLegacyFmt.exec(line); if (!groups) { return groups; } return { threadName: groups[1], pid: groups[2], cpuNumber: groups[3], timestamp: groups[4], eventName: groups[5], details: groups[6] }; } TestExports.lineParserWithLegacyFmt = lineParserWithLegacyFmt; // Matches the trace_event_clock_sync record // 0: trace_event_clock_sync: parent_ts=19581477508 var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/; TestExports.traceEventClockSyncRE = traceEventClockSyncRE; // Some kernel trace events are manually classified in slices and // hand-assigned a pseudo PID. var pseudoKernelPID = 0; /** * Deduce the format of trace data. Linix kernels prior to 3.3 used one * format (by default); 3.4 and later used another. Additionally, newer * kernels can optionally trace the TGID. * * @return {function} the function for parsing data when the format is * recognized; otherwise null. */ function autoDetectLineParser(line) { if (lineREWithTGID.test(line)) return lineParserWithTGID; if (lineREWithIRQInfo.test(line)) return lineParserWithIRQInfo; if (lineREWithLegacyFmt.test(line)) return lineParserWithLegacyFmt; return null; }; TestExports.autoDetectLineParser = autoDetectLineParser; /** * Guesses whether the provided events is a Linux perf string. * Looks for the magic string "# tracer" at the start of the file, * or the typical task-pid-cpu-timestamp-function sequence of a typical * trace's body. * * @return {boolean} True when events is a linux perf array. */ LinuxPerfImporter.canImport = function(events) { if (!(typeof(events) === 'string' || events instanceof String)) return false; if (LinuxPerfImporter._extractEventsFromSystraceHTML(events, false).ok) return true; if (/^# tracer:/.test(events)) return true; var m = /^(.+)\n/.exec(events); if (m) events = m[1]; if (autoDetectLineParser(events)) return true; return false; }; LinuxPerfImporter._extractEventsFromSystraceHTML = function( incoming_events, produce_result) { var failure = {ok: false}; if (produce_result === undefined) produce_result = true; if (/^<!DOCTYPE HTML>/.test(incoming_events) == false) return failure; var lines = incoming_events.split('\n'); var cur_line = 1; function advanceToLineMatching(regex) { for (; cur_line < lines.length; cur_line++) { if (regex.test(lines[cur_line])) return true; } return false; } // Try to find the data... if (!advanceToLineMatching(/^ <script>$/)) return failure; if (!advanceToLineMatching(/^ var linuxPerfData = "\\$/)) return failure; var events_begin_at_line = cur_line + 1; if (!advanceToLineMatching(/^ <\/script>$/)) return failure; var events_end_at_line = cur_line; if (!advanceToLineMatching(/^<\/body>$/)) return failure; if (!advanceToLineMatching(/^<\/html>$/)) return failure; var raw_events = lines.slice(events_begin_at_line, events_end_at_line); function endsWith(str, suffix) { return str.indexOf(suffix, str.length - suffix.length) !== -1; } function stripSuffix(str, suffix) { if (!endsWith(str, suffix)) return str; return str.substring(str, str.length - suffix.length); } // Strip off escaping in the file needed to preserve linebreaks. var events = []; if (produce_result) { for (var i = 0; i < raw_events.length; i++) { var event = raw_events[i]; event = stripSuffix(event, '\\n\\'); events.push(event); } } else { events = [raw_events[raw_events.length - 1]]; } // Last event ends differently. Strip that off too, // treating absence of that trailing stirng as a failure. var oldLastEvent = events[events.length - 1]; var newLastEvent = stripSuffix(oldLastEvent, '\\n";'); if (newLastEvent == oldLastEvent) return failure; events[events.length - 1] = newLastEvent; return {ok: true, lines: produce_result ? events : undefined, events_begin_at_line: events_begin_at_line}; } LinuxPerfImporter.prototype = { __proto__: Object.prototype, get model() { return this.model_; }, /** * Precomputes a lookup table from linux pids back to existing * Threads. This is used during importing to add information to each * thread about whether it was running, descheduled, sleeping, et * cetera. */ buildMapFromLinuxPidsToThreads: function() { this.threadsByLinuxPid = {}; this.model_.getAllThreads().forEach( function(thread) { this.threadsByLinuxPid[thread.tid] = thread; }.bind(this)); }, /** * @return {CpuState} A CpuState corresponding to the given cpuNumber. */ getOrCreateCpuState: function(cpuNumber) { if (!this.cpuStates_[cpuNumber]) { var cpu = this.model_.getOrCreateCpu(cpuNumber); this.cpuStates_[cpuNumber] = new CpuState(cpu); } return this.cpuStates_[cpuNumber]; }, /** * @return {TimelinThread} A thread corresponding to the kernelThreadName. */ getOrCreateKernelThread: function(kernelThreadName, pid, tid) { if (!this.kernelThreadStates_[kernelThreadName]) { var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid); thread.name = kernelThreadName; this.kernelThreadStates_[kernelThreadName] = { pid: pid, thread: thread, openSlice: undefined, openSliceTS: undefined }; this.threadsByLinuxPid[pid] = thread; } return this.kernelThreadStates_[kernelThreadName]; }, /** * @return {TimelinThread} A pseudo thread corresponding to the * threadName. Pseudo threads are for events that we want to break * out to a separate timeline but would not otherwise happen. * These threads are assigned to pseudoKernelPID and given a * unique (incrementing) TID. */ getOrCreatePseudoThread: function(threadName) { var thread = this.kernelThreadStates_[threadName]; if (!thread) { thread = this.getOrCreateKernelThread(threadName, pseudoKernelPID, this.pseudoThreadCounter); this.pseudoThreadCounter++; } return thread; }, /** * Imports the data in this.events_ into model_. */ importEvents: function(isSecondaryImport) { this.createParsers(); this.importCpuData(); if (!this.alignClocks(isSecondaryImport)) return; this.buildMapFromLinuxPidsToThreads(); this.buildPerThreadCpuSlicesFromCpuState(); }, /** * Called by the Model after all other importers have imported their * events. */ finalizeImport: function() { }, /** * Builds the cpuSlices array on each thread based on our knowledge of what * each Cpu is doing. This is done only for Threads that are * already in the model, on the assumption that not having any traced data * on a thread means that it is not of interest to the user. */ buildPerThreadCpuSlicesFromCpuState: function() { // Push the cpu slices to the threads that they run on. for (var cpuNumber in this.cpuStates_) { var cpuState = this.cpuStates_[cpuNumber]; var cpu = cpuState.cpu; for (var i = 0; i < cpu.slices.length; i++) { var slice = cpu.slices[i]; var thread = this.threadsByLinuxPid[slice.args.tid]; if (!thread) continue; if (!thread.tempCpuSlices) thread.tempCpuSlices = []; thread.tempCpuSlices.push(slice); } } for (var i in this.wakeups_) { var wakeup = this.wakeups_[i]; var thread = this.threadsByLinuxPid[wakeup.tid]; if (!thread) continue; thread.tempWakeups = thread.tempWakeups || []; thread.tempWakeups.push(wakeup); } // Create slices for when the thread is not running. var runningId = tracing.getColorIdByName('running'); var runnableId = tracing.getColorIdByName('runnable'); var sleepingId = tracing.getColorIdByName('sleeping'); var ioWaitId = tracing.getColorIdByName('iowait'); this.model_.getAllThreads().forEach(function(thread) { if (thread.tempCpuSlices === undefined) return; var origSlices = thread.tempCpuSlices; delete thread.tempCpuSlices; origSlices.sort(function(x, y) { return x.start - y.start; }); var wakeups = thread.tempWakeups || []; delete thread.tempWakeups; wakeups.sort(function(x, y) { return x.ts - y.ts; }); // Walk the slice list and put slices between each original slice // to show when the thread isn't running var slices = []; if (origSlices.length) { var slice = origSlices[0]; if (wakeups.length && wakeups[0].ts < slice.start) { var wakeup = wakeups.shift(); var wakeupDuration = slice.start - wakeup.ts; var args = {'wakeup from tid': wakeup.fromTid}; slices.push(new tracing.model.Slice('', 'Runnable', runnableId, wakeup.ts, args, wakeupDuration)); } slices.push(new tracing.model.Slice('', 'Running', runningId, slice.start, {}, slice.duration)); } var wakeup = undefined; for (var i = 1; i < origSlices.length; i++) { var prevSlice = origSlices[i - 1]; var nextSlice = origSlices[i]; var midDuration = nextSlice.start - prevSlice.end; while (wakeups.length && wakeups[0].ts < nextSlice.start) { var w = wakeups.shift(); if (wakeup === undefined && w.ts > prevSlice.end) { wakeup = w; } } // Push a sleep slice onto the slices list, interrupting it with a // wakeup if appropriate. var pushSleep = function(title, id) { if (wakeup !== undefined) { midDuration = wakeup.ts - prevSlice.end; } slices.push(new tracing.model.Slice('', title, id, prevSlice.end, {}, midDuration)); if (wakeup !== undefined) { var wakeupDuration = nextSlice.start - wakeup.ts; var args = {'wakeup from tid': wakeup.fromTid}; slices.push(new tracing.model.Slice('', 'Runnable', runnableId, wakeup.ts, args, wakeupDuration)); wakeup = undefined; } } if (prevSlice.args.stateWhenDescheduled == 'S') { pushSleep('Sleeping', sleepingId); } else if (prevSlice.args.stateWhenDescheduled == 'R' || prevSlice.args.stateWhenDescheduled == 'R+') { slices.push(new tracing.model.Slice('', 'Runnable', runnableId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'D') { pushSleep('Uninterruptible Sleep', ioWaitId); } else if (prevSlice.args.stateWhenDescheduled == 'T') { slices.push(new tracing.model.Slice('', '__TASK_STOPPED', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 't') { slices.push(new tracing.model.Slice('', 'debug', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'Z') { slices.push(new tracing.model.Slice('', 'Zombie', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'X') { slices.push(new tracing.model.Slice('', 'Exit Dead', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'x') { slices.push(new tracing.model.Slice('', 'Task Dead', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'W') { slices.push(new tracing.model.Slice('', 'WakeKill', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'D|W') { pushSleep('Uninterruptable Sleep | WakeKill', ioWaitId); } else { throw new Error('Unrecognized state: ') + prevSlice.args.stateWhenDescheduled; } slices.push(new tracing.model.Slice('', 'Running', runningId, nextSlice.start, {}, nextSlice.duration)); } thread.cpuSlices = slices; }); }, /** * Walks the slices stored on this.cpuStates_ and adjusts their timestamps * based on any alignment metadata we discovered. */ alignClocks: function(isSecondaryImport) { if (this.clockSyncRecords_.length == 0) { // If this is a secondary import, and no clock syncing records were // found, then abort the import. Otherwise, just skip clock alignment. if (!isSecondaryImport) return true; // Remove the newly imported CPU slices from the model. this.abortImport(); return false; } // Shift all the slice times based on the sync record. var sync = this.clockSyncRecords_[0]; // NB: parentTS of zero denotes no times-shift; this is // used when user and kernel event clocks are identical. if (sync.parentTS == 0 || sync.parentTS == sync.perfTS) return true; var timeShift = sync.parentTS - sync.perfTS; for (var cpuNumber in this.cpuStates_) { var cpuState = this.cpuStates_[cpuNumber]; var cpu = cpuState.cpu; for (var i = 0; i < cpu.slices.length; i++) { var slice = cpu.slices[i]; slice.start = slice.start + timeShift; slice.duration = slice.duration; } for (var counterName in cpu.counters) { var counter = cpu.counters[counterName]; for (var sI = 0; sI < counter.timestamps.length; sI++) counter.timestamps[sI] = (counter.timestamps[sI] + timeShift); } } for (var kernelThreadName in this.kernelThreadStates_) { var kthread = this.kernelThreadStates_[kernelThreadName]; var thread = kthread.thread; thread.shiftTimestampsForward(timeShift); } return true; }, /** * Removes any data that has been added to the model because of an error * detected during the import. */ abortImport: function() { if (this.pushedEventsToThreads) throw new Error('Cannot abort, have alrady pushedCpuDataToThreads.'); for (var cpuNumber in this.cpuStates_) delete this.model_.cpus[cpuNumber]; for (var kernelThreadName in this.kernelThreadStates_) { var kthread = this.kernelThreadStates_[kernelThreadName]; var thread = kthread.thread; var process = thread.parent; delete process.threads[thread.tid]; delete this.model_.processes[process.pid]; } this.model_.importErrors.push( 'Cannot import kernel trace without a clock sync.'); }, /** * Creates an instance of each registered linux perf event parser. * This allows the parsers to register handlers for the events they * understand. We also register our own special handlers (for the * timestamp synchronization markers). */ createParsers: function() { // Instantiate the parsers; this will register handlers for known events var parserConstructors = tracing.importer.linux_perf.Parser.getSubtypeConstructors(); for (var i = 0; i < parserConstructors.length; ++i) { var parserConstructor = parserConstructors[i]; this.parsers_.push(new parserConstructor(this)); } this.registerEventHandler('tracing_mark_write:trace_event_clock_sync', LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this)); this.registerEventHandler('tracing_mark_write', LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this)); // NB: old-style trace markers; deprecated this.registerEventHandler('0:trace_event_clock_sync', LinuxPerfImporter.prototype.traceClockSyncEvent.bind(this)); this.registerEventHandler('0', LinuxPerfImporter.prototype.traceMarkingWriteEvent.bind(this)); }, /** * Registers a linux perf event parser used by importCpuData. */ registerEventHandler: function(eventName, handler) { // TODO(sleffler) how to handle conflicts? this.eventHandlers_[eventName] = handler; }, /** * Records the fact that a pid has become runnable. This data will * eventually get used to derive each thread's cpuSlices array. */ markPidRunnable: function(ts, pid, comm, prio, fromPid) { // The the pids that get passed in to this function are Linux kernel // pids, which identify threads. The rest of trace-viewer refers to // these as tids, so the change of nomenclature happens in the following // construction of the wakeup object. this.wakeups_.push({ts: ts, tid: pid, fromTid: fromPid}); }, importError: function(message) { this.model_.importErrors.push( 'Line ' + (this.lineNumberBase + this.lineNumber + 1) + ': ' + message); }, /** * Processes a trace_event_clock_sync event. */ traceClockSyncEvent: function(eventName, cpuNumber, pid, ts, eventBase) { var event = /parent_ts=(\d+\.?\d*)/.exec(eventBase.details); if (!event) return false; this.clockSyncRecords_.push({ perfTS: ts, parentTS: event[1] * 1000 }); return true; }, /** * Processes a trace_marking_write event. */ traceMarkingWriteEvent: function(eventName, cpuNumber, pid, ts, eventBase, threadName) { var event = /^\s*(\w+):\s*(.*)$/.exec(eventBase.details); if (!event) { // Check if the event matches events traced by the Android framework var tag = eventBase.details.substring(0, 2); if (tag == 'B|' || tag == 'E' || tag == 'E|' || tag == 'C|') { eventBase.subEventName = 'android'; } else { return false; } } else { eventBase.subEventName = event[1]; eventBase.details = event[2]; } var writeEventName = eventName + ':' + eventBase.subEventName; var handler = this.eventHandlers_[writeEventName]; if (!handler) { this.importError('Unknown trace_marking_write event ' + writeEventName); return true; } return handler(writeEventName, cpuNumber, pid, ts, eventBase, threadName); }, /** * Walks the this.events_ structure and creates Cpu objects. */ importCpuData: function() { var extractResult = LinuxPerfImporter._extractEventsFromSystraceHTML( this.events_, true); if (extractResult.ok) { this.lineNumberBase = extractResult.events_begin_at_line; this.lines_ = extractResult.lines; } else { this.lineNumberBase = 0; this.lines_ = this.events_.split('\n'); } var lineParser = null; for (this.lineNumber = 0; this.lineNumber < this.lines_.length; ++this.lineNumber) { var line = this.lines_[this.lineNumber]; if (line.length == 0 || /^#/.test(line)) continue; if (lineParser == null) { lineParser = autoDetectLineParser(line); if (lineParser == null) { this.importError('Cannot parse line: ' + line); continue; } } var eventBase = lineParser(line); if (!eventBase) { this.importError('Unrecognized line: ' + line); continue; } var pid = parseInt(eventBase.pid); var cpuNumber = parseInt(eventBase.cpuNumber); var ts = parseFloat(eventBase.timestamp) * 1000; var eventName = eventBase.eventName; var handler = this.eventHandlers_[eventName]; if (!handler) { this.importError('Unknown event ' + eventName + ' (' + line + ')'); continue; } if (!handler(eventName, cpuNumber, pid, ts, eventBase)) this.importError('Malformed ' + eventName + ' event (' + line + ')'); } } }; tracing.Model.registerImporter(LinuxPerfImporter); return { LinuxPerfImporter: LinuxPerfImporter, _LinuxPerfImporterTestExports: TestExports }; });