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