| // 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: |
| // <idle>-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); |
| } |
| }, |
| |
| 'cpufreq_interactive_already': { |
| format: /cpu=(\d+) load=(\d+) cur=(\d+) targ=(\d+)/, |
| handler: function(importer, event) { |
| var targetCpuNumber = parseInt(event[1]); |
| var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); |
| var loadCounter = targetCpu.cpu.getOrCreateCounter('', 'Load'); |
| var curCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Current Frequency'); |
| var targCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Target Frequency'); |
| if (loadCounter.numSeries == 0) { |
| loadCounter.seriesNames.push('state'); |
| loadCounter.seriesColors.push( |
| tracing.getStringColorId(loadCounter.name + '.' + 'state')); |
| } |
| if (curCounter.numSeries == 0) { |
| curCounter.seriesNames.push('state'); |
| curCounter.seriesColors.push( |
| tracing.getStringColorId(curCounter.name + '.' + 'state')); |
| } |
| if (targCounter.numSeries == 0) { |
| targCounter.seriesNames.push('state'); |
| targCounter.seriesColors.push( |
| tracing.getStringColorId(targCounter.name + '.' + 'state')); |
| } |
| var loadState = parseInt(event[2]); |
| var curState = parseInt(event[3]); |
| var targState = parseInt(event[4]); |
| loadCounter.timestamps.push(event.timestamp); |
| loadCounter.samples.push(loadState); |
| curCounter.timestamps.push(event.timestamp); |
| curCounter.samples.push(curState); |
| targCounter.timestamps.push(event.timestamp); |
| targCounter.samples.push(targState); |
| } |
| }, |
| |
| 'cpufreq_interactive_notyet': { |
| format: /cpu=(\d+) load=(\d+) cur=(\d+) targ=(\d+)/, |
| handler: function(importer, event) { |
| var targetCpuNumber = parseInt(event[1]); |
| var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); |
| var loadCounter = targetCpu.cpu.getOrCreateCounter('', 'Load'); |
| var curCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Current Frequency'); |
| var targCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Target Frequency'); |
| if (loadCounter.numSeries == 0) { |
| loadCounter.seriesNames.push('state'); |
| loadCounter.seriesColors.push( |
| tracing.getStringColorId(loadCounter.name + '.' + 'state')); |
| } |
| if (curCounter.numSeries == 0) { |
| curCounter.seriesNames.push('state'); |
| curCounter.seriesColors.push( |
| tracing.getStringColorId(curCounter.name + '.' + 'state')); |
| } |
| if (targCounter.numSeries == 0) { |
| targCounter.seriesNames.push('state'); |
| targCounter.seriesColors.push( |
| tracing.getStringColorId(targCounter.name + '.' + 'state')); |
| } |
| var loadState = parseInt(event[2]); |
| var curState = parseInt(event[3]); |
| var targState = parseInt(event[4]); |
| loadCounter.timestamps.push(event.timestamp); |
| loadCounter.samples.push(loadState); |
| curCounter.timestamps.push(event.timestamp); |
| curCounter.samples.push(curState); |
| targCounter.timestamps.push(event.timestamp); |
| targCounter.samples.push(targState); |
| } |
| }, |
| |
| 'cpufreq_interactive_target': { |
| format: /cpu=(\d+) load=(\d+) cur=(\d+) targ=(\d+)/, |
| handler: function(importer, event) { |
| var targetCpuNumber = parseInt(event[1]); |
| var targetCpu = importer.getOrCreateCpuState(targetCpuNumber); |
| var loadCounter = targetCpu.cpu.getOrCreateCounter('', 'Load'); |
| var curCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Current Frequency'); |
| var targCounter = targetCpu.cpu.getOrCreateCounter('', 'Interactive Target Frequency'); |
| if (loadCounter.numSeries == 0) { |
| loadCounter.seriesNames.push('state'); |
| loadCounter.seriesColors.push( |
| tracing.getStringColorId(loadCounter.name + '.' + 'state')); |
| } |
| if (curCounter.numSeries == 0) { |
| curCounter.seriesNames.push('state'); |
| curCounter.seriesColors.push( |
| tracing.getStringColorId(curCounter.name + '.' + 'state')); |
| } |
| if (targCounter.numSeries == 0) { |
| targCounter.seriesNames.push('state'); |
| targCounter.seriesColors.push( |
| tracing.getStringColorId(targCounter.name + '.' + 'state')); |
| } |
| var loadState = parseInt(event[2]); |
| var curState = parseInt(event[3]); |
| var targState = parseInt(event[4]); |
| loadCounter.timestamps.push(event.timestamp); |
| loadCounter.samples.push(loadState); |
| curCounter.timestamps.push(event.timestamp); |
| curCounter.samples.push(curState); |
| targCounter.timestamps.push(event.timestamp); |
| targCounter.samples.push(targState); |
| } |
| }, |
| |
| '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 |
| }; |
| |
| }); |