| OLD | NEW |
| (Empty) |
| 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 cr.define('tracing', function() { | |
| 20 /** | |
| 21 * Represents the scheduling state for a single thread. | |
| 22 * @constructor | |
| 23 */ | |
| 24 function CpuState(cpu) { | |
| 25 this.cpu = cpu; | |
| 26 } | |
| 27 | |
| 28 CpuState.prototype = { | |
| 29 __proto__: Object.prototype, | |
| 30 | |
| 31 /** | |
| 32 * Switches the active pid on this Cpu. If necessary, add a TimelineSlice | |
| 33 * to the cpu representing the time spent on that Cpu since the last call to | |
| 34 * switchRunningLinuxPid. | |
| 35 */ | |
| 36 switchRunningLinuxPid: function(importer, prevState, ts, pid, comm, prio) { | |
| 37 // Generate a slice if the last active pid was not the idle task | |
| 38 if (this.lastActivePid !== undefined && this.lastActivePid != 0) { | |
| 39 var duration = ts - this.lastActiveTs; | |
| 40 var thread = importer.threadsByLinuxPid[this.lastActivePid]; | |
| 41 if (thread) | |
| 42 name = thread.userFriendlyName; | |
| 43 else | |
| 44 name = this.lastActiveComm; | |
| 45 | |
| 46 var slice = new tracing.TimelineSlice(name, | |
| 47 tracing.getStringColorId(name), | |
| 48 this.lastActiveTs, | |
| 49 { | |
| 50 comm: this.lastActiveComm, | |
| 51 tid: this.lastActivePid, | |
| 52 prio: this.lastActivePrio, | |
| 53 stateWhenDescheduled: prevState | |
| 54 }, | |
| 55 duration); | |
| 56 this.cpu.slices.push(slice); | |
| 57 } | |
| 58 | |
| 59 this.lastActiveTs = ts; | |
| 60 this.lastActivePid = pid; | |
| 61 this.lastActiveComm = comm; | |
| 62 this.lastActivePrio = prio; | |
| 63 } | |
| 64 }; | |
| 65 | |
| 66 /** | |
| 67 * Imports linux perf events into a specified model. | |
| 68 * @constructor | |
| 69 */ | |
| 70 function LinuxPerfImporter(model, events, isAdditionalImport) { | |
| 71 this.isAdditionalImport_ = isAdditionalImport; | |
| 72 this.model_ = model; | |
| 73 this.events_ = events; | |
| 74 this.clockSyncRecords_ = []; | |
| 75 this.cpuStates_ = {}; | |
| 76 this.kernelThreadStates_ = {}; | |
| 77 this.buildMapFromLinuxPidsToTimelineThreads(); | |
| 78 this.lineNumber = -1; | |
| 79 } | |
| 80 | |
| 81 TestExports = {}; | |
| 82 | |
| 83 // Matches the generic trace record: | |
| 84 // <idle>-0 [001] 1.23: sched_switch | |
| 85 var lineRE = /^\s*(.+?)\s+\[(\d+)\]\s*(\d+\.\d+):\s+(\S+):\s(.*)$/; | |
| 86 TestExports.lineRE = lineRE; | |
| 87 | |
| 88 // Matches the sched_switch record | |
| 89 var schedSwitchRE = new RegExp( | |
| 90 'prev_comm=(.+) prev_pid=(\\d+) prev_prio=(\\d+) prev_state=(\\S) ==> ' + | |
| 91 'next_comm=(.+) next_pid=(\\d+) next_prio=(\\d+)'); | |
| 92 TestExports.schedSwitchRE = schedSwitchRE; | |
| 93 | |
| 94 // Matches the sched_wakeup record | |
| 95 var schedWakeupRE = | |
| 96 /comm=(.+) pid=(\d+) prio=(\d+) success=(\d+) target_cpu=(\d+)/; | |
| 97 TestExports.schedWakeupRE = schedWakeupRE; | |
| 98 | |
| 99 // Matches the trace_event_clock_sync record | |
| 100 // 0: trace_event_clock_sync: parent_ts=19581477508 | |
| 101 var traceEventClockSyncRE = /trace_event_clock_sync: parent_ts=(\d+\.?\d*)/; | |
| 102 TestExports.traceEventClockSyncRE = traceEventClockSyncRE; | |
| 103 | |
| 104 // Matches the workqueue_execute_start record | |
| 105 // workqueue_execute_start: work struct c7a8a89c: function MISRWrapper | |
| 106 var workqueueExecuteStartRE = /work struct (.+): function (\S+)/; | |
| 107 | |
| 108 // Matches the workqueue_execute_start record | |
| 109 // workqueue_execute_end: work struct c7a8a89c | |
| 110 var workqueueExecuteEndRE = /work struct (.+)/; | |
| 111 | |
| 112 // Some kernel trace events are manually classified in slices and | |
| 113 // hand-assigned a pseudo PID+TID. | |
| 114 var pseudoKernelPID = 0; | |
| 115 TestExports.pseudoKernelPID = pseudoKernelPID; | |
| 116 var pseudoI915GemObjectTID = 1; | |
| 117 TestExports.pseudoI915GemObjectTID = pseudoI915GemObjectTID; | |
| 118 var pseudoI915GemRingTID = 2; | |
| 119 TestExports.pseudoI915GemRingTID = pseudoI915GemRingTID; | |
| 120 var pseudoI915FlipTID = 3; | |
| 121 TestExports.pseudoI915FlipTID = pseudoI915FlipTID; | |
| 122 var pseudoI915RegTID = 4; | |
| 123 TestExports.pseudoI915RegTID = pseudoI915RegTID; | |
| 124 var pseudoVblankTID = 5; | |
| 125 TestExports.pseudoVblankTID = pseudoVblankTID; | |
| 126 | |
| 127 /** | |
| 128 * Guesses whether the provided events is a Linux perf string. | |
| 129 * Looks for the magic string "# tracer" at the start of the file, | |
| 130 * or the typical task-pid-cpu-timestamp-function sequence of a typical | |
| 131 * trace's body. | |
| 132 * | |
| 133 * @return {boolean} True when events is a linux perf array. | |
| 134 */ | |
| 135 LinuxPerfImporter.canImport = function(events) { | |
| 136 if (!(typeof(events) === 'string' || events instanceof String)) | |
| 137 return false; | |
| 138 | |
| 139 if (/^# tracer:/.exec(events)) | |
| 140 return true; | |
| 141 | |
| 142 var m = /^(.+)\n/.exec(events); | |
| 143 if (m) | |
| 144 events = m[1]; | |
| 145 if (lineRE.exec(events)) | |
| 146 return true; | |
| 147 | |
| 148 return false; | |
| 149 }; | |
| 150 | |
| 151 LinuxPerfImporter.prototype = { | |
| 152 __proto__: Object.prototype, | |
| 153 | |
| 154 /** | |
| 155 * Precomputes a lookup table from linux pids back to existing | |
| 156 * TimelineThreads. This is used during importing to add information to each | |
| 157 * timeline thread about whether it was running, descheduled, sleeping, et | |
| 158 * cetera. | |
| 159 */ | |
| 160 buildMapFromLinuxPidsToTimelineThreads: function() { | |
| 161 this.threadsByLinuxPid = {}; | |
| 162 this.model_.getAllThreads().forEach( | |
| 163 function(thread) { | |
| 164 this.threadsByLinuxPid[thread.tid] = thread; | |
| 165 }.bind(this)); | |
| 166 }, | |
| 167 | |
| 168 /** | |
| 169 * @return {CpuState} A CpuState corresponding to the given cpuNumber. | |
| 170 */ | |
| 171 getOrCreateCpuState: function(cpuNumber) { | |
| 172 if (!this.cpuStates_[cpuNumber]) { | |
| 173 var cpu = this.model_.getOrCreateCpu(cpuNumber); | |
| 174 this.cpuStates_[cpuNumber] = new CpuState(cpu); | |
| 175 } | |
| 176 return this.cpuStates_[cpuNumber]; | |
| 177 }, | |
| 178 | |
| 179 /** | |
| 180 * @return {TimelinThread} A thread corresponding to the kernelThreadName. | |
| 181 */ | |
| 182 getOrCreateKernelThread: function(kernelThreadName, opt_pid, opt_tid) { | |
| 183 if (!this.kernelThreadStates_[kernelThreadName]) { | |
| 184 var pid = opt_pid; | |
| 185 if (pid == undefined) { | |
| 186 pid = /.+-(\d+)/.exec(kernelThreadName)[1]; | |
| 187 pid = parseInt(pid, 10); | |
| 188 } | |
| 189 var tid = opt_tid; | |
| 190 if (tid == undefined) | |
| 191 tid = pid; | |
| 192 | |
| 193 var thread = this.model_.getOrCreateProcess(pid).getOrCreateThread(tid); | |
| 194 thread.name = kernelThreadName; | |
| 195 this.kernelThreadStates_[kernelThreadName] = { | |
| 196 pid: pid, | |
| 197 thread: thread, | |
| 198 openSlice: undefined, | |
| 199 openSliceTS: undefined | |
| 200 }; | |
| 201 this.threadsByLinuxPid[pid] = thread; | |
| 202 } | |
| 203 return this.kernelThreadStates_[kernelThreadName]; | |
| 204 }, | |
| 205 | |
| 206 /** | |
| 207 * Imports the data in this.events_ into model_. | |
| 208 */ | |
| 209 importEvents: function() { | |
| 210 this.importCpuData(); | |
| 211 if (!this.alignClocks()) | |
| 212 return; | |
| 213 this.buildPerThreadCpuSlicesFromCpuState(); | |
| 214 }, | |
| 215 | |
| 216 /** | |
| 217 * Called by the TimelineModel after all other importers have imported their | |
| 218 * events. | |
| 219 */ | |
| 220 finalizeImport: function() { | |
| 221 }, | |
| 222 | |
| 223 /** | |
| 224 * Builds the cpuSlices array on each thread based on our knowledge of what | |
| 225 * each Cpu is doing. This is done only for TimelineThreads that are | |
| 226 * already in the model, on the assumption that not having any traced data | |
| 227 * on a thread means that it is not of interest to the user. | |
| 228 */ | |
| 229 buildPerThreadCpuSlicesFromCpuState: function() { | |
| 230 // Push the cpu slices to the threads that they run on. | |
| 231 for (var cpuNumber in this.cpuStates_) { | |
| 232 var cpuState = this.cpuStates_[cpuNumber]; | |
| 233 var cpu = cpuState.cpu; | |
| 234 | |
| 235 for (var i = 0; i < cpu.slices.length; i++) { | |
| 236 var slice = cpu.slices[i]; | |
| 237 | |
| 238 var thread = this.threadsByLinuxPid[slice.args.tid]; | |
| 239 if (!thread) | |
| 240 continue; | |
| 241 if (!thread.tempCpuSlices) | |
| 242 thread.tempCpuSlices = []; | |
| 243 thread.tempCpuSlices.push(slice); | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 // Create slices for when the thread is not running. | |
| 248 var runningId = tracing.getColorIdByName('running'); | |
| 249 var runnableId = tracing.getColorIdByName('runnable'); | |
| 250 var sleepingId = tracing.getColorIdByName('sleeping'); | |
| 251 var ioWaitId = tracing.getColorIdByName('iowait'); | |
| 252 this.model_.getAllThreads().forEach(function(thread) { | |
| 253 if (!thread.tempCpuSlices) | |
| 254 return; | |
| 255 var origSlices = thread.tempCpuSlices; | |
| 256 delete thread.tempCpuSlices; | |
| 257 | |
| 258 origSlices.sort(function(x, y) { | |
| 259 return x.start - y.start; | |
| 260 }); | |
| 261 | |
| 262 // Walk the slice list and put slices between each original slice | |
| 263 // to show when the thread isn't running | |
| 264 var slices = []; | |
| 265 if (origSlices.length) { | |
| 266 var slice = origSlices[0]; | |
| 267 slices.push(new tracing.TimelineSlice('Running', runningId, | |
| 268 slice.start, {}, slice.duration)); | |
| 269 } | |
| 270 for (var i = 1; i < origSlices.length; i++) { | |
| 271 var prevSlice = origSlices[i - 1]; | |
| 272 var nextSlice = origSlices[i]; | |
| 273 var midDuration = nextSlice.start - prevSlice.end; | |
| 274 if (prevSlice.args.stateWhenDescheduled == 'S') { | |
| 275 slices.push(new tracing.TimelineSlice('Sleeping', sleepingId, | |
| 276 prevSlice.end, {}, midDuration)); | |
| 277 } else if (prevSlice.args.stateWhenDescheduled == 'R') { | |
| 278 slices.push(new tracing.TimelineSlice('Runnable', runnableId, | |
| 279 prevSlice.end, {}, midDuration)); | |
| 280 } else if (prevSlice.args.stateWhenDescheduled == 'D') { | |
| 281 slices.push(new tracing.TimelineSlice( | |
| 282 'Uninterruptible Sleep', ioWaitId, | |
| 283 prevSlice.end, {}, midDuration)); | |
| 284 } else if (prevSlice.args.stateWhenDescheduled == 'T') { | |
| 285 slices.push(new tracing.TimelineSlice('__TASK_STOPPED', ioWaitId, | |
| 286 prevSlice.end, {}, midDuration)); | |
| 287 } else if (prevSlice.args.stateWhenDescheduled == 't') { | |
| 288 slices.push(new tracing.TimelineSlice('debug', ioWaitId, | |
| 289 prevSlice.end, {}, midDuration)); | |
| 290 } else if (prevSlice.args.stateWhenDescheduled == 'Z') { | |
| 291 slices.push(new tracing.TimelineSlice('Zombie', ioWaitId, | |
| 292 prevSlice.end, {}, midDuration)); | |
| 293 } else if (prevSlice.args.stateWhenDescheduled == 'X') { | |
| 294 slices.push(new tracing.TimelineSlice('Exit Dead', ioWaitId, | |
| 295 prevSlice.end, {}, midDuration)); | |
| 296 } else if (prevSlice.args.stateWhenDescheduled == 'x') { | |
| 297 slices.push(new tracing.TimelineSlice('Task Dead', ioWaitId, | |
| 298 prevSlice.end, {}, midDuration)); | |
| 299 } else if (prevSlice.args.stateWhenDescheduled == 'W') { | |
| 300 slices.push(new tracing.TimelineSlice('WakeKill', ioWaitId, | |
| 301 prevSlice.end, {}, midDuration)); | |
| 302 } else if (prevSlice.args.stateWhenDescheduled == 'D|W') { | |
| 303 slices.push(new tracing.TimelineSlice( | |
| 304 'Uninterruptable Sleep | WakeKill', ioWaitId, | |
| 305 prevSlice.end, {}, midDuration)); | |
| 306 } else { | |
| 307 throw 'Unrecognized state: ' + prevSlice.args.stateWhenDescheduled; | |
| 308 } | |
| 309 | |
| 310 slices.push(new tracing.TimelineSlice('Running', runningId, | |
| 311 nextSlice.start, {}, nextSlice.duration)); | |
| 312 } | |
| 313 thread.cpuSlices = slices; | |
| 314 }); | |
| 315 }, | |
| 316 | |
| 317 /** | |
| 318 * Walks the slices stored on this.cpuStates_ and adjusts their timestamps | |
| 319 * based on any alignment metadata we discovered. | |
| 320 */ | |
| 321 alignClocks: function() { | |
| 322 if (this.clockSyncRecords_.length == 0) { | |
| 323 // If this is an additional import, and no clock syncing records were | |
| 324 // found, then abort the import. Otherwise, just skip clock alignment. | |
| 325 if (!this.isAdditionalImport_) | |
| 326 return; | |
| 327 | |
| 328 // Remove the newly imported CPU slices from the model. | |
| 329 this.abortImport(); | |
| 330 return false; | |
| 331 } | |
| 332 | |
| 333 // Shift all the slice times based on the sync record. | |
| 334 var sync = this.clockSyncRecords_[0]; | |
| 335 // NB: parentTS of zero denotes no times-shift; this is | |
| 336 // used when user and kernel event clocks are identical. | |
| 337 if (sync.parentTS == 0 || sync.parentTS == sync.perfTS) | |
| 338 return true; | |
| 339 var timeShift = sync.parentTS - sync.perfTS; | |
| 340 for (var cpuNumber in this.cpuStates_) { | |
| 341 var cpuState = this.cpuStates_[cpuNumber]; | |
| 342 var cpu = cpuState.cpu; | |
| 343 | |
| 344 for (var i = 0; i < cpu.slices.length; i++) { | |
| 345 var slice = cpu.slices[i]; | |
| 346 slice.start = slice.start + timeShift; | |
| 347 slice.duration = slice.duration; | |
| 348 } | |
| 349 | |
| 350 for (var counterName in cpu.counters) { | |
| 351 var counter = cpu.counters[counterName]; | |
| 352 for (var sI = 0; sI < counter.timestamps.length; sI++) | |
| 353 counter.timestamps[sI] = (counter.timestamps[sI] + timeShift); | |
| 354 } | |
| 355 } | |
| 356 for (var kernelThreadName in this.kernelThreadStates_) { | |
| 357 var kthread = this.kernelThreadStates_[kernelThreadName]; | |
| 358 var thread = kthread.thread; | |
| 359 for (var i = 0; i < thread.subRows[0].length; i++) { | |
| 360 thread.subRows[0][i].start += timeShift; | |
| 361 } | |
| 362 } | |
| 363 return true; | |
| 364 }, | |
| 365 | |
| 366 /** | |
| 367 * Removes any data that has been added to the model because of an error | |
| 368 * detected during the import. | |
| 369 */ | |
| 370 abortImport: function() { | |
| 371 if (this.pushedEventsToThreads) | |
| 372 throw 'Cannot abort, have alrady pushedCpuDataToThreads.'; | |
| 373 | |
| 374 for (var cpuNumber in this.cpuStates_) | |
| 375 delete this.model_.cpus[cpuNumber]; | |
| 376 for (var kernelThreadName in this.kernelThreadStates_) { | |
| 377 var kthread = this.kernelThreadStates_[kernelThreadName]; | |
| 378 var thread = kthread.thread; | |
| 379 var process = thread.parent; | |
| 380 delete process.threads[thread.tid]; | |
| 381 delete this.model_.processes[process.pid]; | |
| 382 } | |
| 383 this.model_.importErrors.push( | |
| 384 'Cannot import kernel trace without a clock sync.'); | |
| 385 }, | |
| 386 | |
| 387 /** | |
| 388 * Records the fact that a pid has become runnable. This data will | |
| 389 * eventually get used to derive each thread's cpuSlices array. | |
| 390 */ | |
| 391 markPidRunnable: function(ts, pid, comm, prio) { | |
| 392 // TODO(nduca): implement this functionality. | |
| 393 }, | |
| 394 | |
| 395 importError: function(message) { | |
| 396 this.model_.importErrors.push('Line ' + (this.lineNumber + 1) + | |
| 397 ': ' + message); | |
| 398 }, | |
| 399 | |
| 400 malformedEvent: function(eventName) { | |
| 401 this.importError('Malformed ' + eventName + ' event'); | |
| 402 }, | |
| 403 | |
| 404 cpuStateSlice: function(ts, targetCpuNumber, eventType, cpuState) { | |
| 405 var targetCpu = this.getOrCreateCpuState(targetCpuNumber); | |
| 406 var powerCounter; | |
| 407 if (eventType != '1') { | |
| 408 this.importError('Don\'t understand power_start events of type ' + | |
| 409 eventType); | |
| 410 return; | |
| 411 } | |
| 412 powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State'); | |
| 413 if (powerCounter.numSeries == 0) { | |
| 414 powerCounter.seriesNames.push('state'); | |
| 415 powerCounter.seriesColors.push( | |
| 416 tracing.getStringColorId(powerCounter.name + '.' + 'state')); | |
| 417 } | |
| 418 powerCounter.timestamps.push(ts); | |
| 419 powerCounter.samples.push(cpuState); | |
| 420 }, | |
| 421 | |
| 422 cpuIdleSlice: function(ts, targetCpuNumber, cpuState) { | |
| 423 var targetCpu = this.getOrCreateCpuState(targetCpuNumber); | |
| 424 var powerCounter = targetCpu.cpu.getOrCreateCounter('', 'C-State'); | |
| 425 if (powerCounter.numSeries == 0) { | |
| 426 powerCounter.seriesNames.push('state'); | |
| 427 powerCounter.seriesColors.push( | |
| 428 tracing.getStringColorId(powerCounter.name)); | |
| 429 } | |
| 430 // NB: 4294967295/-1 means an exit from the current state | |
| 431 if (cpuState != 4294967295) | |
| 432 powerCounter.samples.push(cpuState); | |
| 433 else | |
| 434 powerCounter.samples.push(0); | |
| 435 powerCounter.timestamps.push(ts); | |
| 436 }, | |
| 437 | |
| 438 cpuFrequencySlice: function(ts, targetCpuNumber, powerState) { | |
| 439 var targetCpu = this.getOrCreateCpuState(targetCpuNumber); | |
| 440 var powerCounter = | |
| 441 targetCpu.cpu.getOrCreateCounter('', 'Clock Frequency'); | |
| 442 if (powerCounter.numSeries == 0) { | |
| 443 powerCounter.seriesNames.push('state'); | |
| 444 powerCounter.seriesColors.push( | |
| 445 tracing.getStringColorId(powerCounter.name + '.' + 'state')); | |
| 446 } | |
| 447 powerCounter.timestamps.push(ts); | |
| 448 powerCounter.samples.push(powerState); | |
| 449 }, | |
| 450 | |
| 451 i915GemObjectSlice: function(ts, eventName, obj, args) { | |
| 452 var kthread = this.getOrCreateKernelThread('i915_gem', pseudoKernelPID, | |
| 453 pseudoI915GemObjectTID); | |
| 454 kthread.openSlice = eventName + ':' + obj; | |
| 455 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 456 tracing.getStringColorId(kthread.openSlice), ts, args, 0); | |
| 457 | |
| 458 kthread.thread.subRows[0].push(slice); | |
| 459 }, | |
| 460 | |
| 461 i915GemRingSlice: function(ts, eventName, dev, ring, args) { | |
| 462 var kthread = this.getOrCreateKernelThread('i915_gem_ring', | |
| 463 pseudoKernelPID, pseudoI915GemRingTID); | |
| 464 kthread.openSlice = eventName + ':' + dev + '.' + ring; | |
| 465 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 466 tracing.getStringColorId(kthread.openSlice), ts, args, 0); | |
| 467 | |
| 468 kthread.thread.subRows[0].push(slice); | |
| 469 }, | |
| 470 | |
| 471 i915RegSlice: function(ts, eventName, reg, args) { | |
| 472 var kthread = this.getOrCreateKernelThread('i915_reg', | |
| 473 pseudoKernelPID, pseudoI915RegTID); | |
| 474 kthread.openSlice = eventName + ':' + reg; | |
| 475 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 476 tracing.getStringColorId(kthread.openSlice), ts, args, 0); | |
| 477 | |
| 478 kthread.thread.subRows[0].push(slice); | |
| 479 }, | |
| 480 | |
| 481 i915OpenFlipSlice: function(ts, obj, plane) { | |
| 482 // use i915_obj_plane? | |
| 483 var kthread = this.getOrCreateKernelThread('i915_flip', | |
| 484 pseudoKernelPID, pseudoI915FlipTID); | |
| 485 kthread.openSliceTS = ts; | |
| 486 kthread.openSlice = 'flip:' + obj + '/' + plane; | |
| 487 }, | |
| 488 | |
| 489 i915CloseFlipSlice: function(ts, args) { | |
| 490 // use i915_obj_plane? | |
| 491 var kthread = this.getOrCreateKernelThread('i915_flip', | |
| 492 pseudoKernelPID, pseudoI915FlipTID); | |
| 493 if (kthread.openSlice) { | |
| 494 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 495 tracing.getStringColorId(kthread.openSlice), | |
| 496 kthread.openSliceTS, | |
| 497 args, | |
| 498 ts - kthread.openSliceTS); | |
| 499 | |
| 500 kthread.thread.subRows[0].push(slice); | |
| 501 } | |
| 502 kthread.openSlice = undefined; | |
| 503 }, | |
| 504 | |
| 505 drmVblankSlice: function(ts, eventName, args) { | |
| 506 var kthread = this.getOrCreateKernelThread('drm_vblank', | |
| 507 pseudoKernelPID, pseudoVblankTID); | |
| 508 kthread.openSlice = eventName; | |
| 509 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 510 tracing.getStringColorId(kthread.openSlice), ts, args, 0); | |
| 511 | |
| 512 kthread.thread.subRows[0].push(slice); | |
| 513 }, | |
| 514 | |
| 515 /** | |
| 516 * Walks the this.events_ structure and creates TimelineCpu objects. | |
| 517 */ | |
| 518 importCpuData: function() { | |
| 519 this.lines_ = this.events_.split('\n'); | |
| 520 | |
| 521 for (this.lineNumber = 0; this.lineNumber < this.lines_.length; | |
| 522 ++this.lineNumber) { | |
| 523 var line = this.lines_[this.lineNumber]; | |
| 524 if (/^#/.exec(line) || line.length == 0) | |
| 525 continue; | |
| 526 var eventBase = lineRE.exec(line); | |
| 527 if (!eventBase) { | |
| 528 this.importError('Unrecognized line: ' + line); | |
| 529 continue; | |
| 530 } | |
| 531 | |
| 532 var cpuState = this.getOrCreateCpuState(parseInt(eventBase[2])); | |
| 533 var ts = parseFloat(eventBase[3]) * 1000; | |
| 534 | |
| 535 var eventName = eventBase[4]; | |
| 536 | |
| 537 switch (eventName) { | |
| 538 case 'sched_switch': | |
| 539 var event = schedSwitchRE.exec(eventBase[5]); | |
| 540 if (!event) { | |
| 541 this.malformedEvent(eventName); | |
| 542 continue; | |
| 543 } | |
| 544 | |
| 545 var prevState = event[4]; | |
| 546 var nextComm = event[5]; | |
| 547 var nextPid = parseInt(event[6]); | |
| 548 var nextPrio = parseInt(event[7]); | |
| 549 cpuState.switchRunningLinuxPid( | |
| 550 this, prevState, ts, nextPid, nextComm, nextPrio); | |
| 551 break; | |
| 552 case 'sched_wakeup': | |
| 553 var event = schedWakeupRE.exec(eventBase[5]); | |
| 554 if (!event) { | |
| 555 this.malformedEvent(eventName); | |
| 556 continue; | |
| 557 } | |
| 558 | |
| 559 var comm = event[1]; | |
| 560 var pid = parseInt(event[2]); | |
| 561 var prio = parseInt(event[3]); | |
| 562 this.markPidRunnable(ts, pid, comm, prio); | |
| 563 break; | |
| 564 case 'power_start': // NB: old-style power event, deprecated | |
| 565 var event = /type=(\d+) state=(\d) cpu_id=(\d)+/.exec(eventBase[5]); | |
| 566 if (!event) { | |
| 567 this.malformedEvent(eventName); | |
| 568 continue; | |
| 569 } | |
| 570 | |
| 571 var targetCpuNumber = parseInt(event[3]); | |
| 572 var cpuState = parseInt(event[2]); | |
| 573 this.cpuStateSlice(ts, targetCpuNumber, event[1], cpuState); | |
| 574 break; | |
| 575 case 'power_frequency': // NB: old-style power event, deprecated | |
| 576 var event = /type=(\d+) state=(\d+) cpu_id=(\d)+/.exec( | |
| 577 eventBase[5]); | |
| 578 if (!event) { | |
| 579 this.malformedEvent(eventName); | |
| 580 continue; | |
| 581 } | |
| 582 | |
| 583 var targetCpuNumber = parseInt(event[3]); | |
| 584 var powerState = parseInt(event[2]); | |
| 585 this.cpuFrequencySlice(ts, targetCpuNumber, powerState); | |
| 586 break; | |
| 587 case 'cpu_frequency': | |
| 588 var event = /state=(\d+) cpu_id=(\d)+/.exec(eventBase[5]); | |
| 589 if (!event) { | |
| 590 this.malformedEvent(eventName); | |
| 591 continue; | |
| 592 } | |
| 593 | |
| 594 var targetCpuNumber = parseInt(event[2]); | |
| 595 var powerState = parseInt(event[1]); | |
| 596 this.cpuFrequencySlice(ts, targetCpuNumber, powerState); | |
| 597 break; | |
| 598 case 'cpu_idle': | |
| 599 var event = /state=(\d+) cpu_id=(\d)+/.exec(eventBase[5]); | |
| 600 if (!event) { | |
| 601 this.malformedEvent(eventName); | |
| 602 continue; | |
| 603 } | |
| 604 | |
| 605 var targetCpuNumber = parseInt(event[2]); | |
| 606 var cpuState = parseInt(event[1]); | |
| 607 this.cpuIdleSlice(ts, targetCpuNumber, cpuState); | |
| 608 break; | |
| 609 case 'workqueue_execute_start': | |
| 610 var event = workqueueExecuteStartRE.exec(eventBase[5]); | |
| 611 if (!event) { | |
| 612 this.malformedEvent(eventName); | |
| 613 continue; | |
| 614 } | |
| 615 | |
| 616 var kthread = this.getOrCreateKernelThread(eventBase[1]); | |
| 617 kthread.openSliceTS = ts; | |
| 618 kthread.openSlice = event[2]; | |
| 619 break; | |
| 620 case 'workqueue_execute_end': | |
| 621 var event = workqueueExecuteEndRE.exec(eventBase[5]); | |
| 622 if (!event) { | |
| 623 this.malformedEvent(eventName); | |
| 624 continue; | |
| 625 } | |
| 626 | |
| 627 var kthread = this.getOrCreateKernelThread(eventBase[1]); | |
| 628 if (kthread.openSlice) { | |
| 629 var slice = new tracing.TimelineSlice(kthread.openSlice, | |
| 630 tracing.getStringColorId(kthread.openSlice), | |
| 631 kthread.openSliceTS, | |
| 632 {}, | |
| 633 ts - kthread.openSliceTS); | |
| 634 | |
| 635 kthread.thread.subRows[0].push(slice); | |
| 636 } | |
| 637 kthread.openSlice = undefined; | |
| 638 break; | |
| 639 case 'i915_gem_object_create': | |
| 640 var event = /obj=(.+), size=(\d+)/.exec(eventBase[5]); | |
| 641 if (!event) { | |
| 642 this.malformedEvent(eventName); | |
| 643 continue; | |
| 644 } | |
| 645 | |
| 646 var obj = event[1]; | |
| 647 var size = parseInt(event[2]); | |
| 648 this.i915GemObjectSlice(ts, eventName, obj, | |
| 649 { | |
| 650 obj: obj, | |
| 651 size: size | |
| 652 }); | |
| 653 break; | |
| 654 case 'i915_gem_object_bind': | |
| 655 case 'i915_gem_object_unbind': | |
| 656 // TODO(sleffler) mappable | |
| 657 var event = /obj=(.+), offset=(.+), size=(\d+)/.exec(eventBase[5]); | |
| 658 if (!event) { | |
| 659 this.malformedEvent(eventName); | |
| 660 continue; | |
| 661 } | |
| 662 | |
| 663 var obj = event[1]; | |
| 664 var offset = event[2]; | |
| 665 var size = parseInt(event[3]); | |
| 666 this.i915ObjectGemSlice(ts, eventName + ':' + obj, | |
| 667 { | |
| 668 obj: obj, | |
| 669 offset: offset, | |
| 670 size: size | |
| 671 }); | |
| 672 break; | |
| 673 case 'i915_gem_object_change_domain': | |
| 674 var event = /obj=(.+), read=(.+), write=(.+)/.exec(eventBase[5]); | |
| 675 if (!event) { | |
| 676 this.malformedEvent(eventName); | |
| 677 continue; | |
| 678 } | |
| 679 | |
| 680 var obj = event[1]; | |
| 681 var read = event[2]; | |
| 682 var write = event[3]; | |
| 683 this.i915GemObjectSlice(ts, eventName, obj, | |
| 684 { | |
| 685 obj: obj, | |
| 686 read: read, | |
| 687 write: write | |
| 688 }); | |
| 689 break; | |
| 690 case 'i915_gem_object_pread': | |
| 691 case 'i915_gem_object_pwrite': | |
| 692 var event = /obj=(.+), offset=(\d+), len=(\d+)/.exec(eventBase[5]); | |
| 693 if (!event) { | |
| 694 this.malformedEvent(eventName); | |
| 695 continue; | |
| 696 } | |
| 697 | |
| 698 var obj = event[1]; | |
| 699 var offset = parseInt(event[2]); | |
| 700 var len = parseInt(event[3]); | |
| 701 this.i915GemObjectSlice(ts, eventName, obj, | |
| 702 { | |
| 703 obj: obj, | |
| 704 offset: offset, | |
| 705 len: len | |
| 706 }); | |
| 707 break; | |
| 708 case 'i915_gem_object_fault': | |
| 709 // TODO(sleffler) writable | |
| 710 var event = /obj=(.+), (.+) index=(\d+)/.exec(eventBase[5]); | |
| 711 if (!event) { | |
| 712 this.malformedEvent(eventName); | |
| 713 continue; | |
| 714 } | |
| 715 | |
| 716 var obj = event[1]; | |
| 717 var type = event[2]; | |
| 718 var index = parseInt(event[3]); | |
| 719 this.i915GemObjectSlice(ts, eventName, obj, | |
| 720 { | |
| 721 obj: obj, | |
| 722 type: type, | |
| 723 index: index | |
| 724 }); | |
| 725 break; | |
| 726 case 'i915_gem_object_clflush': | |
| 727 case 'i915_gem_object_destroy': | |
| 728 var event = /obj=(.+)/.exec(eventBase[5]); | |
| 729 if (!event) { | |
| 730 this.malformedEvent(eventName); | |
| 731 continue; | |
| 732 } | |
| 733 | |
| 734 var obj = event[1]; | |
| 735 this.i915GemObjectSlice(ts, eventName, obj, | |
| 736 { | |
| 737 obj: obj, | |
| 738 }); | |
| 739 break; | |
| 740 case 'i915_gem_ring_dispatch': | |
| 741 var event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(eventBase[5]); | |
| 742 if (!event) { | |
| 743 this.malformedEvent(eventName); | |
| 744 continue; | |
| 745 } | |
| 746 | |
| 747 var dev = parseInt(event[1]); | |
| 748 var ring = parseInt(event[2]); | |
| 749 var seqno = parseInt(event[3]); | |
| 750 this.i915GemRingSlice(ts, eventName, dev, ring, | |
| 751 { | |
| 752 dev: dev, | |
| 753 ring: ring, | |
| 754 seqno: seqno | |
| 755 }); | |
| 756 break; | |
| 757 case 'i915_gem_ring_flush': | |
| 758 var event = /dev=(\d+), ring=(\d+), invalidate=(.+), flush=(.+)/. | |
| 759 exec(eventBase[5]); | |
| 760 if (!event) { | |
| 761 this.malformedEvent(eventName); | |
| 762 continue; | |
| 763 } | |
| 764 | |
| 765 var dev = parseInt(event[1]); | |
| 766 var ring = parseInt(event[2]); | |
| 767 var invalidate = event[3]; | |
| 768 var flush = event[4]; | |
| 769 this.i915GemRingSlice(ts, eventName, dev, ring, | |
| 770 { | |
| 771 dev: dev, | |
| 772 ring: ring, | |
| 773 invalidate: invalidate, | |
| 774 flush: flush | |
| 775 }); | |
| 776 break; | |
| 777 case 'i915_gem_request': | |
| 778 case 'i915_gem_request_add': | |
| 779 case 'i915_gem_request_complete': | |
| 780 case 'i915_gem_request_retire': | |
| 781 case 'i915_gem_request_wait_begin': | |
| 782 case 'i915_gem_request_wait_end': | |
| 783 var event = /dev=(\d+), ring=(\d+), seqno=(\d+)/.exec(eventBase[5]); | |
| 784 if (!event) { | |
| 785 this.malformedEvent(eventName); | |
| 786 continue; | |
| 787 } | |
| 788 | |
| 789 var dev = parseInt(event[1]); | |
| 790 var ring = parseInt(event[2]); | |
| 791 var seqno = parseInt(event[3]); | |
| 792 this.i915GemRingSlice(ts, eventName, dev, ring, | |
| 793 { | |
| 794 dev: dev, | |
| 795 ring: ring, | |
| 796 seqno: seqno | |
| 797 }); | |
| 798 break; | |
| 799 case 'i915_ring_wait_begin': | |
| 800 case 'i915_ring_wait_end': | |
| 801 var event = /dev=(\d+), ring=(\d+)/.exec(eventBase[5]); | |
| 802 if (!event) { | |
| 803 this.malformedEvent(eventName); | |
| 804 continue; | |
| 805 } | |
| 806 | |
| 807 var dev = parseInt(event[1]); | |
| 808 var ring = parseInt(event[2]); | |
| 809 this.i915GemRingSlice(ts, eventName, dev, ring, | |
| 810 { | |
| 811 dev: dev, | |
| 812 ring: ring | |
| 813 }); | |
| 814 break; | |
| 815 case 'i915_gem_object_change_domain': | |
| 816 var event = /obj=(.+), read=(.+), write=(.+)/.exec(eventBase[5]); | |
| 817 if (!event) { | |
| 818 this.malformedEvent(eventName); | |
| 819 continue; | |
| 820 } | |
| 821 | |
| 822 var obj = event[1]; | |
| 823 var read = event[2]; | |
| 824 var write = event[3]; | |
| 825 this.i915GemObjectSlice(ts, eventName, obj, | |
| 826 { | |
| 827 obj: obj, | |
| 828 read: read, | |
| 829 write: write | |
| 830 }); | |
| 831 break; | |
| 832 case 'i915_reg_rw': | |
| 833 var event = /(.+) reg=(.+), len=(.+), val=(.+)/.exec(eventBase[5]); | |
| 834 if (!event) { | |
| 835 this.malformedEvent(eventName); | |
| 836 continue; | |
| 837 } | |
| 838 | |
| 839 var rw = event[1]; | |
| 840 var reg = event[2]; | |
| 841 var len = event[3]; | |
| 842 var data = event[3]; | |
| 843 this.i915RegSlice(ts, rw, reg, | |
| 844 { | |
| 845 rw: rw, | |
| 846 reg: reg, | |
| 847 len: len, | |
| 848 data: data | |
| 849 }); | |
| 850 break; | |
| 851 case 'i915_flip_request': | |
| 852 var event = /plane=(\d+), obj=(.+)/.exec(eventBase[5]); | |
| 853 if (!event) { | |
| 854 this.malformedEvent(eventName); | |
| 855 continue; | |
| 856 } | |
| 857 | |
| 858 var plane = parseInt(event[1]); | |
| 859 var obj = event[2]; | |
| 860 this.i915OpenFlipSlice(ts, obj, plane); | |
| 861 break; | |
| 862 case 'i915_flip_complete': | |
| 863 var event = /plane=(\d+), obj=(.+)/.exec(eventBase[5]); | |
| 864 if (!event) { | |
| 865 this.malformedEvent(eventName); | |
| 866 continue; | |
| 867 } | |
| 868 | |
| 869 var plane = parseInt(event[1]); | |
| 870 var obj = event[2]; | |
| 871 this.i915CloseFlipSlice(ts, | |
| 872 { | |
| 873 obj: obj, | |
| 874 plane: plane | |
| 875 }); | |
| 876 break; | |
| 877 case 'drm_vblank_event': | |
| 878 var event = /crtc=(\d+), seq=(\d+)/.exec(eventBase[5]); | |
| 879 if (!event) { | |
| 880 this.malformedEvent(eventName); | |
| 881 continue; | |
| 882 } | |
| 883 | |
| 884 var crtc = parseInt(event[1]); | |
| 885 var seq = parseInt(event[2]); | |
| 886 this.drmVblankSlice(ts, 'vblank:' + crtc, | |
| 887 { | |
| 888 crtc: crtc, | |
| 889 seq: seq | |
| 890 }); | |
| 891 break; | |
| 892 case '0': // NB: old-style trace markers; deprecated | |
| 893 case 'tracing_mark_write': | |
| 894 var event = traceEventClockSyncRE.exec(eventBase[5]); | |
| 895 if (!event) { | |
| 896 this.malformedEvent(eventName); | |
| 897 continue; | |
| 898 } | |
| 899 this.clockSyncRecords_.push({ | |
| 900 perfTS: ts, | |
| 901 parentTS: event[1] * 1000 | |
| 902 }); | |
| 903 break; | |
| 904 default: | |
| 905 console.log('unknown event ' + eventName); | |
| 906 break; | |
| 907 } | |
| 908 } | |
| 909 } | |
| 910 }; | |
| 911 | |
| 912 tracing.TimelineModel.registerImporter(LinuxPerfImporter); | |
| 913 | |
| 914 return { | |
| 915 LinuxPerfImporter: LinuxPerfImporter, | |
| 916 _LinuxPerfImporterTestExports: TestExports | |
| 917 }; | |
| 918 | |
| 919 }); | |
| OLD | NEW |