// 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 * timeline 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. */ cr.define('tracing', 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 TimelineSlice * 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]; if (thread) name = thread.userFriendlyName; else name = this.lastActiveComm; var slice = new tracing.TimelineSlice(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; } }; function ThreadState(tid) { this.openSlices = []; } /** * Imports linux perf events into a specified model. * @constructor */ function LinuxPerfImporter(model, events, isAdditionalImport) { this.isAdditionalImport_ = isAdditionalImport; this.model_ = model; this.events_ = events; this.clockSyncRecords_ = []; this.cpuStates_ = {}; this.kernelThreadStates_ = {}; this.buildMapFromLinuxPidsToTimelineThreads(); this.lineNumber = -1; // To allow simple indexing of threads, we store all the threads by their // kernel KPID. The KPID is a unique key for a thread in the trace. this.threadStateByKPID_ = {}; } TestExports = {}; // Matches the generic trace record: // -0 [001] .... 1.23: sched_switch var lineRE = /^\s*(.+?)\s+\[(\d+)\]\s*([d.][N.][sh.][\d.])?\s*(\d+\.\d+):\s+(\S+):\s(.*)$/; TestExports.lineRE = lineRE; // 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; /** * 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 (/^# tracer:/.exec(events)) return true; var m = /^(.+)\n/.exec(events); if (m) events = m[1]; if (lineRE.exec(events)) return true; return false; }; LinuxPerfImporter.prototype = { __proto__: Object.prototype, /** * Precomputes a lookup table from linux pids back to existing * TimelineThreads. This is used during importing to add information to each * timeline thread about whether it was running, descheduled, sleeping, et * cetera. */ buildMapFromLinuxPidsToTimelineThreads: 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 {number} The pid extracted from the kernel thread name. */ parsePid: function(kernelThreadName) { var pid = /.+-(\d+)/.exec(kernelThreadName)[1]; pid = parseInt(pid); return pid; }, /** * @return {number} The string portion of the thread extracted from the * kernel thread name. */ parseThreadName: function(kernelThreadName) { return /(.+)-\d+/.exec(kernelThreadName)[1]; }, /** * @return {TimelinThread} A thread corresponding to the kernelThreadName. */ getOrCreateKernelThread: function(kernelThreadName, opt_pid, opt_tid) { if (!this.kernelThreadStates_[kernelThreadName]) { var pid = opt_pid; if (pid == undefined) { pid = this.parsePid(kernelThreadName); } var tid = opt_tid; if (tid == undefined) tid = pid; var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid); thread.name = kernelThreadName; this.kernelThreadStates_[kernelThreadName] = { pid: pid, thread: thread, openSlice: undefined, openSliceTS: undefined, asyncSlices: {} }; this.threadsByLinuxPid[pid] = thread; } return this.kernelThreadStates_[kernelThreadName]; }, /** * Imports the data in this.events_ into model_. */ importEvents: function() { this.importCpuData(); if (!this.alignClocks()) return; this.buildPerThreadCpuSlicesFromCpuState(); }, /** * Called by the TimelineModel 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 TimelineThreads 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 = []; // Because Chrome's Array.sort is not a stable sort, we need to keep // the slice index around to keep slices with identical start times in // the proper order when sorting them. slice.index = i; thread.tempCpuSlices.push(slice); } } // 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) return; var origSlices = thread.tempCpuSlices; delete thread.tempCpuSlices; origSlices.sort(function(x, y) { var delta = x.start - y.start; if (delta == 0) { // Break ties using the original slice ordering. return x.index - y.index; } else { return delta; } }); // 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]; slices.push(new tracing.TimelineSlice('Running', runningId, slice.start, {}, slice.duration)); } for (var i = 1; i < origSlices.length; i++) { var prevSlice = origSlices[i - 1]; var nextSlice = origSlices[i]; var midDuration = nextSlice.start - prevSlice.end; if (prevSlice.args.stateWhenDescheduled == 'S') { slices.push(new tracing.TimelineSlice('Sleeping', sleepingId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'R' || prevSlice.args.stateWhenDescheduled == 'R+') { slices.push(new tracing.TimelineSlice('Runnable', runnableId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'D') { slices.push(new tracing.TimelineSlice( 'Uninterruptible Sleep', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'T') { slices.push(new tracing.TimelineSlice('__TASK_STOPPED', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 't') { slices.push(new tracing.TimelineSlice('debug', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'Z') { slices.push(new tracing.TimelineSlice('Zombie', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'X') { slices.push(new tracing.TimelineSlice('Exit Dead', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'x') { slices.push(new tracing.TimelineSlice('Task Dead', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'W') { slices.push(new tracing.TimelineSlice('WakeKill', ioWaitId, prevSlice.end, {}, midDuration)); } else if (prevSlice.args.stateWhenDescheduled == 'D|W') { slices.push(new tracing.TimelineSlice( 'Uninterruptible Sleep | WakeKill', ioWaitId, prevSlice.end, {}, midDuration)); } else { throw 'Unrecognized state: ' + prevSlice.args.stateWhenDescheduled; } slices.push(new tracing.TimelineSlice('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() { if (this.clockSyncRecords_.length == 0) { // If this is an additional import, and no clock syncing records were // found, then abort the import. Otherwise, just skip clock alignment. if (!this.isAdditionalImport_) return; // 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; for (var i = 0; i < thread.subRows[0].length; i++) { thread.subRows[0][i].start += 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 '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.'); }, /** * 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) { // TODO(nduca): implement this functionality. }, importError: function(message) { this.model_.importErrors.push('Line ' + (this.lineNumber + 1) + ': ' + message); }, malformedEvent: function(eventName) { this.importError('Malformed ' + eventName + ' event'); }, /** * Helper to open a kernel thread slice. */ openSlice: function(kthread, name, ts) { kthread.openSliceTS = ts; kthread.openSlice = name; }, /** * Helper to close a kernel thread slice. */ closeSlice: function(kthread, ts, data) { if (kthread.openSlice) { var slice = new tracing.TimelineSlice(kthread.openSlice, tracing.getStringColorId(kthread.openSlice), kthread.openSliceTS, data, ts - kthread.openSliceTS); kthread.thread.subRows[0].push(slice); kthread.openSlice = undefined; } }, /** * Helper to open an async slice. */ openAsyncSlice: function(kthread, key, ts, name) { var slice = new tracing.TimelineAsyncSlice(name, tracing.getStringColorId(name), ts); slice.startThread = kthread.thread; kthread.asyncSlices[key] = slice; }, /** * Helper to close an async slice. */ closeAsyncSlice: function(kthread, key, ts, data) { var slice = kthread.asyncSlices[key]; if (slice) { slice.duration = ts - slice.start; slice.args = data; slice.endThread = kthread.thread; slice.subSlices = [ new tracing.TimelineSlice(slice.title, slice.colorId, slice.start, slice.args, slice.duration) ]; kthread.thread.asyncSlices.push(slice); delete kthread.asyncSlices[key]; } }, /** * Helper to get a ThreadState for a given taskId. */ getThreadState: function(taskId) { var kpid = this.parsePid(taskId); return this.threadStateByKPID_[kpid]; }, /** * Helper to get or create a ThreadState for a given taskId. */ getOrCreateThreadState: function(taskId, pid) { var kpid = this.parsePid(taskId); var state = this.threadStateByKPID_[kpid]; if (!state) { state = new ThreadState(); state.threadName = this.parseThreadName(taskId); state.tid = kpid; state.pid = pid; state.thread = this.model_.getOrCreateProcess(pid). getOrCreateThread(kpid); this.threadsByLinuxPid[kpid] = state.thread; if (!state.thread.name) { state.thread.name = state.threadName; } this.threadStateByKPID_[kpid] = state; } return state; }, /** * Helper to process a 'begin' event (e.g. initiate a slice). * @param {string} name The trace event name. * @param {number} ts The trace event begin timestamp. */ processBegin: function(taskId, name, ts, pid) { var state = this.getOrCreateThreadState(taskId, pid); var colorId = tracing.getStringColorId(name); var slice = new tracing.TimelineThreadSlice(name, colorId, ts, null); state.openSlices.push(slice); }, /** * Helper to process an 'end' event (e.g. close a slice). * @param {number} ts The trace event begin timestamp. */ processEnd: function(taskId, ts) { var state = this.getThreadState(taskId); if (!state || state.openSlices.length == 0) { // Ignore E events that are unmatched. return; } var slice = state.openSlices.pop(); slice.duration = ts - slice.start; // Store the slice on the correct subrow. var subRowIndex = state.openSlices.length; state.thread.getSubrow(subRowIndex).push(slice); // Add the slice to the subSlices array of its parent. if (state.openSlices.length) { var parentSlice = state.openSlices[state.openSlices.length - 1]; parentSlice.subSlices.push(slice); } }, /** * Helper function that closes any open slices. This happens when a trace * ends before an 'E' phase event can get posted. When that happens, this * closes the slice at the highest timestamp we recorded and sets the * didNotFinish flag to true. */ autoCloseOpenSlices: function() { // We need to know the model bounds in order to assign an end-time to // the open slices. this.model_.updateBounds(); // The model's max value in the trace is wrong at this point if there are // un-closed events. To close those events, we need the true global max // value. To compute this, build a list of timestamps that weren't // included in the max calculation, then compute the real maximum based // on that. var openTimestamps = []; for (var kpid in this.threadStateByKPID_) { var state = this.threadStateByKPID_[kpid]; for (var i = 0; i < state.openSlices.length; i++) { var slice = state.openSlices[i]; openTimestamps.push(slice.start); for (var s = 0; s < slice.subSlices.length; s++) { var subSlice = slice.subSlices[s]; openTimestamps.push(subSlice.start); if (subSlice.duration) openTimestamps.push(subSlice.end); } } } // Figure out the maximum value of model.maxTimestamp and // Math.max(openTimestamps). Made complicated by the fact that the model // timestamps might be undefined. var realMaxTimestamp; if (this.model_.maxTimestamp) { realMaxTimestamp = Math.max(this.model_.maxTimestamp, Math.max.apply(Math, openTimestamps)); } else { realMaxTimestamp = Math.max.apply(Math, openTimestamps); } // Automatically close any slices are still open. These occur in a number // of reasonable situations, e.g. deadlock. This pass ensures the open // slices make it into the final model. for (var kpid in this.threadStateByKPID_) { var state = this.threadStateByKPID_[kpid]; while (state.openSlices.length > 0) { var slice = state.openSlices.pop(); slice.duration = realMaxTimestamp - slice.start; slice.didNotFinish = true; // Store the slice on the correct subrow. var subRowIndex = state.openSlices.length; state.thread.getSubrow(subRowIndex).push(slice); // Add the slice to the subSlices array of its parent. if (state.openSlices.length) { var parentSlice = state.openSlices[state.openSlices.length - 1]; parentSlice.subSlices.push(slice); } } } }, /** * Helper that creates and adds samples to a TimelineCounter object based on * 'C' phase events. */ processCounter: function(name, ts, value, pid) { var ctr = this.model_.getOrCreateProcess(pid) .getOrCreateCounter('', name); // Initialize the counter's series fields if needed. // if (ctr.numSeries == 0) { ctr.seriesNames.push('state'); ctr.seriesColors.push( tracing.getStringColorId(ctr.name + '.' + 'state')); } // Add the sample values. ctr.timestamps.push(ts); ctr.samples.push(value); }, /** * Walks the this.events_ structure and creates TimelineCpu objects. */ importCpuData: function() { this.lines_ = this.events_.split('\n'); for (this.lineNumber = 0; this.lineNumber < this.lines_.length; ++this.lineNumber) { var line = this.lines_[this.lineNumber]; if (/^#/.exec(line) || line.length == 0) continue; var eventBase = lineRE.exec(line); if (!eventBase) { this.importError('Unrecognized line: ' + line); continue; } var taskId = eventBase[1]; var cpuNum = eventBase[2]; var taskInfo = eventBase[3]; var timestamp = eventBase[4]; var eventName = eventBase[5]; var eventInfo = eventBase[6]; var eventDefinition = this.eventDefinitions[eventName]; if (eventDefinition) { // Parse the event info. var event; if (eventDefinition.format) { event = eventDefinition.format.exec(eventInfo); if (!event) { this.malformedEvent(eventName); continue; } } else { event = {}; } // Add the basic event properties. event.timestamp = parseFloat(timestamp) * 1000; event.name = eventName; event.info = eventInfo; event.taskId = taskId; event.cpuState = this.getOrCreateCpuState(parseInt(cpuNum)); // Invoke the handler. if (eventDefinition.handler) { eventDefinition.handler(this, event); } } else { console.log('unknown event ' + eventName); } } }, /** * Table of supported events represented as an associative array indexed by event name. * Each event definition has the following properties: * * format: A regular expression to parse the event info. * If omitted, the event information is not parsed but the other basic * properties are still provided to the event handler. * handler: A handler function to invoke to handle the event. * If omitted, the event is parsed but not handled. * * The event object passed as a parameter to the handler has the matched groups from * from the regular expression and in addition has the following properties: * * timestamp: The uptime in milliseconds. * name: The event name. * info: The unparsed event info. * taskId: The task ID. * cpuState: The CPU state object for the CPU associated with the event. */ eventDefinitions: { 'sched_switch': { format: new RegExp( 'prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) prev_state=(\\S+) ==> ' + 'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)'), handler: function(importer, event) { var prevState = event[4]; var nextComm = event[5]; var nextPid = parseInt(event[6]); var nextPrio = parseInt(event[7]); event.cpuState.switchRunningLinuxPid( importer, prevState, event.timestamp, nextPid, nextComm, nextPrio); } }, 'sched_wakeup': { format: /comm=(.+) pid=(\d+) prio=(\d+) success=(\d+) target_cpu=(\d+)/, handler: function(importer, event) { var comm = event[1]; var pid = parseInt(event[2]); var prio = parseInt(event[3]); importer.markPidRunnable(event.timestamp, pid, comm, prio); } }, 'power_start': { // NB: old-style power event, deprecated format: /type=(\d+) state=(\d) cpu_id=(\d)+/, handler: function(importer, event) { var targetCpuNumber = parseInt(event[3]); var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); var powerCounter; if (event[1] == '1') { powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State'); } else { importer.importError('Don\'t understand power_start events of ' + 'type ' + event[1]); return; } if (powerCounter.numSeries == 0) { powerCounter.seriesNames.push('state'); powerCounter.seriesColors.push( tracing.getStringColorId(powerCounter.name + '.' + 'state')); } var powerState = parseInt(event[2]); powerCounter.timestamps.push(event.timestamp); powerCounter.samples.push(powerState); } }, 'power_frequency': { // NB: old-style power event, deprecated format: /type=(\d+) state=(\d+) cpu_id=(\d)+/, handler: function(importer, event) { var targetCpuNumber = parseInt(event[3]); var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); var powerCounter = targetCpu.cpu.getOrCreateCounter('', 'Power Frequency'); if (powerCounter.numSeries == 0) { powerCounter.seriesNames.push('state'); powerCounter.seriesColors.push( tracing.getStringColorId(powerCounter.name + '.' + 'state')); } var powerState = parseInt(event[2]); powerCounter.timestamps.push(event.timestamp); powerCounter.samples.push(powerState); } }, 'cpu_frequency': { format: /state=(\d+) cpu_id=(\d)+/, handler: function(importer, event) { var targetCpuNumber = parseInt(event[2]); var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); var powerCounter = targetCpu.cpu.getOrCreateCounter('', 'Clock Frequency'); if (powerCounter.numSeries == 0) { powerCounter.seriesNames.push('state'); powerCounter.seriesColors.push( tracing.getStringColorId(powerCounter.name + '.' + 'state')); } var powerState = parseInt(event[1]); powerCounter.timestamps.push(event.timestamp); powerCounter.samples.push(powerState); } }, 'cpu_idle': { format: /state=(\d+) cpu_id=(\d)+/, handler: function(importer, event) { var targetCpuNumber = parseInt(event[2]); var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); var powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State'); if (powerCounter.numSeries == 0) { powerCounter.seriesNames.push('state'); powerCounter.seriesColors.push( tracing.getStringColorId(powerCounter.name)); } var powerState = parseInt(event[1]); // NB: 4294967295/-1 means an exit from the current state if (powerState != 4294967295) powerCounter.samples.push(powerState); else powerCounter.samples.push(0); powerCounter.timestamps.push(event.timestamp); } }, 'workqueue_execute_start': { // workqueue_execute_start: work struct c7a8a89c: function MISRWrapper format: /work struct (.+): function (\S+)/, handler: function(importer, event) { var kthread = importer.getOrCreateKernelThread(event.taskId); importer.openSlice(kthread, event[2], event.timestamp); } }, 'workqueue_execute_end': { // workqueue_execute_end: work struct c7a8a89c format: /work struct (.+)/, handler: function(importer, event) { var kthread = importer.getOrCreateKernelThread(event.taskId); importer.closeSlice(kthread, event.timestamp, {}); } }, 'workqueue_queue_work': { // ignored for now }, 'workqueue_activate_work': { // ignored for now }, 'ext4_sync_file_enter': { // ext4_sync_file_enter: dev 179,9 ino 114914 parent 114912 datasync 1 format: /dev (\d+,\d+) ino (\d+) parent (\d+) datasync (\d+)/, handler: function(importer, event) { var kthread = importer.getOrCreateKernelThread('ext4:' + event.taskId); var device = event[1]; var inode = event[2]; var datasync = event[4] == 1; var key = device + '-' + inode; importer.openAsyncSlice(kthread, key, event.timestamp, datasync ? 'fdatasync' : 'fsync'); } }, 'ext4_sync_file_exit': { // ext4_sync_file_exit: dev 179,9 ino 114912 ret 0 format: /dev (\d+,\d+) ino (\d+) ret (\d+)/, handler: function(importer, event) { var kthread = importer.getOrCreateKernelThread('ext4:' + event.taskId); var device = event[1]; var inode = event[2]; var error = parseInt(event[3]); var key = device + '-' + inode; importer.closeAsyncSlice(kthread, key, event.timestamp, { device: device, inode: inode, error: error }); } }, 'block_rq_issue': { // block_rq_issue: 179,0 WS 0 () 9182248 + 8 [mmcqd/0] format: /(\d+,\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? \d+ \(.*\) (\d+) \+ (\d+) \[.*\]/, handler: function(importer, event) { var action; switch (event[3]) { case 'D': action = 'discard'; break; case 'W': action = 'write'; break; case 'R': action = 'read'; break; case 'N': action = 'none'; break; default: action = 'unknown'; break; } if (event[2]) { action += ' flush'; } if (event[4] == 'F') { action += ' fua'; } if (event[5] == 'A') { action += ' ahead'; } if (event[6] == 'S') { action += ' sync'; } if (event[7] == 'M') { action += ' meta'; } var device = event[1] var sector = parseInt(event[8]) var numSectors = parseInt(event[9]) var kthread = importer.getOrCreateKernelThread('block:' + event.taskId); var key = device + '-' + sector + '-' + numSectors; importer.openAsyncSlice(kthread, key, event.timestamp, action); } }, 'block_rq_complete': { // block_rq_complete: 179,0 WS () 9182248 + 8 [0] format: /(\d+,\d+) (F)?([DWRN])(F)?(A)?(S)?(M)? \(.*\) (\d+) \+ (\d+) \[(.*)\]/, handler: function(importer, event) { var device = event[1] var sector = parseInt(event[8]) var numSectors = parseInt(event[9]) var error = parseInt(event[10]) var kthread = importer.getOrCreateKernelThread('block:' + event.taskId); var key = device + '-' + sector + '-' + numSectors; importer.closeAsyncSlice(kthread, key, event.timestamp, { device: device, sector: sector, numSectors: numSectors, error: error }); } }, 'i915_gem_object_pwrite': { format: /obj=(.+), offset=(\d+), len=(\d+)/, handler: function(importer, event) { var obj = event[1]; var offset = parseInt(event[2]); var len = parseInt(event[3]); var kthread = importer.getOrCreateKernelThread('i915_gem', 0, 1); importer.openSlice(kthread, 'pwrite:' + obj, event.timestamp); importer.closeSlice(kthread, event.timestamp, { obj: obj, offset: offset, len: len }); } }, 'i915_flip_request': { format: /plane=(\d+), obj=(.+)/, handler: function(importer, event) { var plane = parseInt(event[1]); var obj = event[2]; // use i915_obj_plane? var kthread = importer.getOrCreateKernelThread('i915_flip', 0, 2); importer.openSlice(kthread, 'flip:' + obj + '/' + plane, event.timestamp); } }, 'i915_flip_complete': { format: /plane=(\d+), obj=(.+)/, handler: function(importer, event) { var plane = parseInt(event[1]); var obj = event[2]; // use i915_obj_plane? var kthread = importer.getOrCreateKernelThread('i915_flip', 0, 2); importer.closeSlice(kthread, event.timestamp, { obj: obj, plane: plane }); } }, 'tracing_mark_write': { handler: function(importer, event) { var eventData = traceEventClockSyncRE.exec(event.info); if (eventData) { importer.clockSyncRecords_.push({ perfTS: event.timestamp, parentTS: eventData[1] * 1000 }); } else { var eventData = event.info.split('|') switch (eventData[0]) { case 'B': var pid = parseInt(eventData[1]); var name = eventData[2]; importer.processBegin(event.taskId, name, event.timestamp, pid); break; case 'E': importer.processEnd(event.taskId, event.timestamp); break; case 'C': var pid = parseInt(eventData[1]); var name = eventData[2]; var value = parseInt(eventData[3]); importer.processCounter(name, event.timestamp, value, pid); break; default: importer.malformedEvent(event.name); break; } } } }, } }; // NB: old-style trace markers; deprecated LinuxPerfImporter.prototype.eventDefinitions['0'] = LinuxPerfImporter.prototype.eventDefinitions['tracing_mark_write']; TestExports.eventDefinitions = LinuxPerfImporter.prototype.eventDefinitions; tracing.TimelineModel.registerImporter(LinuxPerfImporter); return { LinuxPerfImporter: LinuxPerfImporter, _LinuxPerfImporterTestExports: TestExports }; });