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