apply Android tracing importer patch
Change-Id: I76052d3ed8ed495870b29320d7b2d247d8152216
diff --git a/src/tracing/linux_perf_importer.js b/src/tracing/linux_perf_importer.js
index ffeb5a7..5505796 100644
--- a/src/tracing/linux_perf_importer.js
+++ b/src/tracing/linux_perf_importer.js
@@ -63,6 +63,10 @@
}
};
+ function ThreadState(tid) {
+ this.openSlices = [];
+ }
+
/**
* Imports linux perf events into a specified model.
* @constructor
@@ -76,6 +80,10 @@
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 = {};
@@ -162,14 +170,30 @@
},
/**
+ * @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 = /.+-(\d+)/.exec(kernelThreadName)[1];
- pid = parseInt(pid, 10);
+ pid = this.parsePid(kernelThreadName);
}
var tid = opt_tid;
if (tid == undefined)
@@ -225,6 +249,12 @@
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);
}
}
@@ -241,7 +271,13 @@
delete thread.tempCpuSlices;
origSlices.sort(function(x, y) {
- return x.start - y.start;
+ 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
@@ -382,6 +418,141 @@
},
/**
+ * Helper to process a 'begin' event (e.g. initiate a slice).
+ * @param {ThreadState} state Thread state (holds slices).
+ * @param {string} name The trace event name.
+ * @param {number} ts The trace event begin timestamp.
+ */
+ processBegin: function(state, tname, name, ts, pid, tid) {
+ var colorId = tracing.getStringColorId(name);
+ var slice = new tracing.TimelineThreadSlice(name, colorId, ts, null);
+ // XXX: Should these be removed from the slice before putting it into the
+ // model?
+ slice.pid = pid;
+ slice.tid = tid;
+ slice.threadName = tname;
+ state.openSlices.push(slice);
+ },
+
+ /**
+ * Helper to process an 'end' event (e.g. close a slice).
+ * @param {ThreadState} state Thread state (holds slices).
+ * @param {number} ts The trace event begin timestamp.
+ */
+ processEnd: function(state, ts) {
+ if (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 thread = this.model_.getOrCreateProcess(slice.pid).
+ getOrCreateThread(slice.tid);
+ if (!thread.name)
+ thread.name = slice.threadName;
+ this.threadsByLinuxPid[slice.tid] = thread;
+ var subRowIndex = state.openSlices.length;
+ 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 thread = this.model_.getOrCreateProcess(slice.pid)
+ .getOrCreateThread(slice.tid);
+ var subRowIndex = state.openSlices.length;
+ 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() {
@@ -615,15 +786,43 @@
case '0': // NB: old-style trace markers; deprecated
case 'tracing_mark_write':
var event = traceEventClockSyncRE.exec(eventBase[5]);
- if (!event) {
- this.malformedEvent(eventName);
- continue;
+ if (event)
+ this.clockSyncRecords_.push({
+ perfTS: ts,
+ parentTS: event[1] * 1000
+ });
+ else {
+ var tid = this.parsePid(eventBase[1]);
+ var tname = this.parseThreadName(eventBase[1]);
+ var kpid = tid;
+
+ if (!(kpid in this.threadStateByKPID_))
+ this.threadStateByKPID_[kpid] = new ThreadState();
+ var state = this.threadStateByKPID_[kpid];
+
+ var event = eventBase[5].split('|')
+ switch (event[0]) {
+ case 'B':
+ var pid = parseInt(event[1]);
+ var name = event[2];
+ this.processBegin(state, tname, name, ts, pid, tid);
+ break;
+ case 'E':
+ this.processEnd(state, ts);
+ break;
+ case 'C':
+ var pid = parseInt(event[1]);
+ var name = event[2];
+ var value = parseInt(event[3]);
+ this.processCounter(name, ts, value, pid);
+ break;
+ default:
+ this.malformedEvent(eventName);
+ break;
+ }
}
- this.clockSyncRecords_.push({
- perfTS: ts,
- parentTS: event[1] * 1000
- });
break;
+
default:
console.log('unknown event ' + eventName);
break;