Bug 1154115 - Add adapter that deduplicates old, undeduplicated profiles in the frontend. (r=jsantell,vporof)
authorShu-yu Guo <shu@rfrn.org>
Mon, 11 May 2015 14:16:44 -0700
changeset 243416 4756cdcc1f2fc28cc1ae14596d9baaf41675353b
parent 243415 f98738059b8e477f9d884b66b02d60334330bf1d
child 243417 dfd0d6a420c5c2ea5c968ac228522d3331cfae4b
push id28738
push usercbook@mozilla.com
push dateTue, 12 May 2015 14:11:31 +0000
treeherdermozilla-central@bedce1b405a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjsantell, vporof
bugs1154115
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1154115 - Add adapter that deduplicates old, undeduplicated profiles in the frontend. (r=jsantell,vporof)
browser/devtools/performance/modules/actors.js
browser/devtools/performance/modules/io.js
browser/devtools/performance/modules/recording-utils.js
--- a/browser/devtools/performance/modules/actors.js
+++ b/browser/devtools/performance/modules/actors.js
@@ -163,16 +163,22 @@ ProfilerFrontFacade.prototype = {
     return data;
   }),
 
   /**
    * Returns profile data from now since `startTime`.
    */
   getProfile: Task.async(function *(options) {
     let profilerData = yield (actorCompatibilityBridge("getProfile").call(this, options));
+    // If the backend is not deduped, dedupe it ourselves, as rest of the code
+    // expects a deduped profile.
+    if (profilerData.profile.meta.version === 2) {
+      RecordingUtils.deflateProfile(profilerData.profile);
+    }
+
     // If the backend does not support filtering by start and endtime on platform (< Fx40),
     // do it on the client (much slower).
     if (!this.traits.filterable) {
       RecordingUtils.filterSamples(profilerData.profile, options.startTime || 0);
     }
 
     return profilerData;
   }),
--- a/browser/devtools/performance/modules/io.js
+++ b/browser/devtools/performance/modules/io.js
@@ -99,16 +99,19 @@ let PerformanceIO = {
       }
       if (!isValidSerializerVersion(recordingData.version)) {
         deferred.reject(new Error("Unsupported recording data file version."));
         return;
       }
       if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
         recordingData = convertLegacyData(recordingData);
       }
+      if (recordingData.profile.meta.version === 2) {
+        RecordingUtils.deflateProfile(recordingData.profile);
+      }
       deferred.resolve(recordingData);
     });
 
     return deferred.promise;
   }
 };
 
 exports.PerformanceIO = PerformanceIO;
--- a/browser/devtools/performance/modules/recording-utils.js
+++ b/browser/devtools/performance/modules/recording-utils.js
@@ -175,8 +175,301 @@ exports.RecordingUtils.getFilteredBluepr
       if (markerDetails.group > removedGroup) {
         markerDetails.group--;
       }
     }
   }
 
   return filteredBlueprint;
 };
+
+/**
+ * Deduplicates a profile by deduplicating stacks, frames, and strings.
+ *
+ * This is used to adapt version 2 profiles from the backend to version 3, for
+ * use with older Geckos (like B2G).
+ *
+ * Note that the schemas used by this must be kept in sync with schemas used
+ * by the C++ UniqueStacks class in tools/profiler/ProfileEntry.cpp.
+ *
+ * @param object profile
+ *               A profile with version 2.
+ */
+exports.RecordingUtils.deflateProfile = function deflateProfile(profile) {
+  profile.threads = profile.threads.map((thread) => {
+    let uniqueStacks = new UniqueStacks();
+    return deflateThread(thread, uniqueStacks);
+  });
+
+  profile.meta.version = 3;
+};
+
+/**
+ * Given an array of frame objects, deduplicates each frame as well as all
+ * prefixes in the stack. Returns the index of the deduplicated stack.
+ *
+ * @param object frames
+ *               Array of frame objects.
+ * @param UniqueStacks uniqueStacks
+ * @return number index
+ */
+function deflateStack(frames, uniqueStacks) {
+  // Deduplicate every prefix in the stack by keeping track of the current
+  // prefix hash.
+  let prefixIndex = null;
+  for (let i = 0; i < frames.length; i++) {
+    let frameIndex = uniqueStacks.getOrAddFrameIndex(frames[i]);
+    prefixIndex = uniqueStacks.getOrAddStackIndex(prefixIndex, frameIndex);
+  }
+  return prefixIndex;
+}
+
+/**
+ * Given an array of sample objects, deduplicate each sample's stack and
+ * convert the samples to a table with a schema. Returns the deflated samples.
+ *
+ * @param object samples
+ *               Array of samples
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateSamples(samples, uniqueStacks) {
+  // Schema:
+  //   [stack, time, responsiveness, rss, uss, frameNumber, power]
+
+  let deflatedSamples = new Array(samples.length);
+  for (let i = 0; i < samples.length; i++) {
+    let sample = samples[i];
+    deflatedSamples[i] = [
+      deflateStack(sample.frames, uniqueStacks),
+      sample.time,
+      sample.responsiveness,
+      sample.rss,
+      sample.uss,
+      sample.frameNumber,
+      sample.power
+    ];
+  }
+
+  let slot = 0;
+  return {
+    schema: {
+      stack: slot++,
+      time: slot++,
+      responsiveness: slot++,
+      rss: slot++,
+      uss: slot++,
+      frameNumber: slot++,
+      power: slot++
+    },
+    data: deflatedSamples
+  };
+}
+
+/**
+ * Given an array of marker objects, convert the markers to a table with a
+ * schema. Returns the deflated markers.
+ *
+ * If a marker contains a backtrace as its payload, the backtrace stack is
+ * deduplicated in the context of the profile it's in.
+ *
+ * @param object markers
+ *               Array of markers
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateMarkers(markers, uniqueStacks) {
+  // Schema:
+  //   [name, time, data]
+
+  let deflatedMarkers = new Array(markers.length);
+  for (let i = 0; i < markers.length; i++) {
+    let marker = markers[i];
+    if (marker.data && marker.data.type === "tracing" && marker.data.stack) {
+      marker.data.stack = deflateThread(marker.data.stack, uniqueStacks);
+    }
+
+    deflatedMarkers[i] = [
+      uniqueStacks.getOrAddStringIndex(marker.name),
+      marker.time,
+      marker.data
+    ];
+  }
+
+  let slot = 0;
+  return {
+    schema: {
+      name: slot++,
+      time: slot++,
+      data: slot++
+    },
+    data: deflatedMarkers
+  };
+}
+
+/**
+ * Deflate a thread.
+ *
+ * @param object thread
+ *               The profile thread.
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateThread(thread, uniqueStacks) {
+  return {
+    name: thread.name,
+    tid: thread.tid,
+    samples: deflateSamples(thread.samples, uniqueStacks),
+    markers: deflateMarkers(thread.markers, uniqueStacks),
+    stackTable: uniqueStacks.getStackTableWithSchema(),
+    frameTable: uniqueStacks.getFrameTableWithSchema(),
+    stringTable: uniqueStacks.stringTable
+  };
+}
+
+/**
+ * A helper class to deduplicate old-version profiles.
+ *
+ * The main functionality provided is deduplicating frames and stacks.
+ *
+ * For example, given 2 stacks
+ *   [A, B, C]
+ * and
+ *   [A, B, D]
+ *
+ * There are 4 unique frames: A, B, C, and D.
+ * There are 4 unique prefixes: [A], [A, B], [A, B, C], [A, B, D]
+ *
+ * For the example, the output of using UniqueStacks is:
+ *
+ * Frame table:
+ *   [A, B, C, D]
+ *
+ * That is, A has id 0, B has id 1, etc.
+ *
+ * Since stack prefixes are themselves deduplicated (shared), stacks are
+ * represented as a tree, or more concretely, a pair of ids, the prefix and
+ * the leaf.
+ *
+ * Stack table:
+ *   [
+ *     [null, 0],
+ *     [0,    1],
+ *     [1,    2],
+ *     [1,    3]
+ *   ]
+ *
+ * That is, [A] has id 0 and value [null, 0]. This means it has no prefix, and
+ * has the leaf frame 0, which resolves to A in the frame table.
+ *
+ * [A, B] has id 1 and value [0, 1]. This means it has prefix 0, which is [A],
+ * and leaf 1, thus [A, B].
+ *
+ * [A, B, C] has id 2 and value [1, 2]. This means it has prefix 1, which in
+ * turn is [A, B], and leaf 2, thus [A, B, C].
+ *
+ * [A, B, D] has id 3 and value [1, 3]. Note how it shares the prefix 1 with
+ * [A, B, C].
+ */
+function UniqueStacks() {
+  this._frameTable = [];
+  this._stackTable = [];
+  this.stringTable = [];
+  this._frameHash = Object.create(null);
+  this._stackHash = Object.create(null);
+  this._stringHash = Object.create(null);
+}
+
+UniqueStacks.prototype.getStackTableWithSchema = function() {
+  let slot = 0;
+  return {
+    schema: {
+      prefix: slot++,
+      frame: slot++
+    },
+    data: this._stackTable
+  };
+};
+
+UniqueStacks.prototype.getFrameTableWithSchema = function() {
+  let slot = 0;
+  return {
+    schema: {
+      location: slot++,
+      implementation: slot++,
+      optimizations: slot++,
+      line: slot++,
+      category: slot++
+    },
+    data: this._frameTable
+  };
+}
+
+UniqueStacks.prototype.getOrAddFrameIndex = function(frame) {
+  // Schema:
+  //   [location, implementation, optimizations, line, category]
+
+  let frameHash = this._frameHash;
+  let frameTable = this._frameTable;
+
+  let locationIndex = this.getOrAddStringIndex(frame.location);
+  let implementationIndex = this.getOrAddStringIndex(frame.implementation);
+
+  // Super dumb.
+  let hash = `${locationIndex} ${implementationIndex || ""} ${frame.line || ""} ${frame.category || ""}`;
+
+  let index = frameHash[hash];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = frameTable.length;
+  frameHash[hash] = index;
+  frameTable.push([
+    this.getOrAddStringIndex(frame.location),
+    this.getOrAddStringIndex(frame.implementation),
+    // Don't bother with JIT optimization info for deflating old profile data
+    // format to the new format.
+    null,
+    frame.line,
+    frame.category
+  ]);
+  return index;
+};
+
+UniqueStacks.prototype.getOrAddStackIndex = function(prefixIndex, frameIndex) {
+  // Schema:
+  //   [prefix, frame]
+
+  let stackHash = this._stackHash;
+  let stackTable = this._stackTable;
+
+  // Also super dumb.
+  let hash = prefixIndex + " " + frameIndex;
+
+  let index = stackHash[hash];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = stackTable.length;
+  stackHash[hash] = index;
+  stackTable.push([prefixIndex, frameIndex]);
+  return index;
+};
+
+UniqueStacks.prototype.getOrAddStringIndex = function(s) {
+  if (!s) {
+    return null;
+  }
+
+  let stringHash = this._stringHash;
+  let stringTable = this.stringTable;
+  let index = stringHash[s];
+  if (index !== undefined) {
+    return index;
+  }
+
+  index = stringTable.length;
+  stringHash[s] = index;
+  stringTable.push(s);
+  return index;
+};