forked from mirrors/gecko-dev
		
	 b8157dfaaf
			
		
	
	
		b8157dfaaf
		
	
	
	
	
		
			
			# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D35894 --HG-- extra : source : 4722b924e08478f5337ab509718bd66906bf472f extra : amend_source : a5baa1aab21639fdba44537e3a10b179b0073cb4
		
			
				
	
	
		
			624 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			624 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| "use strict";
 | |
| 
 | |
| /**
 | |
|  * Utility functions for managing recording models and their internal data,
 | |
|  * such as filtering profile samples or offsetting timestamps.
 | |
|  */
 | |
| 
 | |
| function mapRecordingOptions(type, options) {
 | |
|   if (type === "profiler") {
 | |
|     return {
 | |
|       entries: options.bufferSize,
 | |
|       interval: options.sampleFrequency
 | |
|         ? 1000 / options.sampleFrequency
 | |
|         : void 0,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   if (type === "memory") {
 | |
|     return {
 | |
|       probability: options.allocationsSampleProbability,
 | |
|       maxLogLength: options.allocationsMaxLogLength,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   if (type === "timeline") {
 | |
|     return {
 | |
|       withMarkers: true,
 | |
|       withTicks: options.withTicks,
 | |
|       withMemory: options.withMemory,
 | |
|       withFrames: true,
 | |
|       withGCEvents: true,
 | |
|       withDocLoadingEvents: false,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   return options;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Takes an options object for `startRecording`, and normalizes
 | |
|  * it based off of server support. For example, if the user
 | |
|  * requests to record memory `withMemory = true`, but the server does
 | |
|  * not support that feature, then the `false` will overwrite user preference
 | |
|  * in order to define the recording with what is actually available, not
 | |
|  * what the user initially requested.
 | |
|  *
 | |
|  * @param {object} options
 | |
|  * @param {boolean}
 | |
|  */
 | |
| function normalizePerformanceFeatures(options, supportedFeatures) {
 | |
|   return Object.keys(options).reduce((modifiedOptions, feature) => {
 | |
|     if (supportedFeatures[feature] !== false) {
 | |
|       modifiedOptions[feature] = options[feature];
 | |
|     }
 | |
|     return modifiedOptions;
 | |
|   }, Object.create(null));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Filters all the samples in the provided profiler data to be more recent
 | |
|  * than the specified start time.
 | |
|  *
 | |
|  * @param object profile
 | |
|  *        The profiler data received from the backend.
 | |
|  * @param number profilerStartTime
 | |
|  *        The earliest acceptable sample time (in milliseconds).
 | |
|  */
 | |
| function filterSamples(profile, profilerStartTime) {
 | |
|   const firstThread = profile.threads[0];
 | |
|   const TIME_SLOT = firstThread.samples.schema.time;
 | |
|   firstThread.samples.data = firstThread.samples.data.filter(e => {
 | |
|     return e[TIME_SLOT] >= profilerStartTime;
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Offsets all the samples in the provided profiler data by the specified time.
 | |
|  *
 | |
|  * @param object profile
 | |
|  *        The profiler data received from the backend.
 | |
|  * @param number timeOffset
 | |
|  *        The amount of time to offset by (in milliseconds).
 | |
|  */
 | |
| function offsetSampleTimes(profile, timeOffset) {
 | |
|   const firstThread = profile.threads[0];
 | |
|   const TIME_SLOT = firstThread.samples.schema.time;
 | |
|   const samplesData = firstThread.samples.data;
 | |
|   for (let i = 0; i < samplesData.length; i++) {
 | |
|     samplesData[i][TIME_SLOT] -= timeOffset;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Offsets all the markers in the provided timeline data by the specified time.
 | |
|  *
 | |
|  * @param array markers
 | |
|  *        The markers array received from the backend.
 | |
|  * @param number timeOffset
 | |
|  *        The amount of time to offset by (in milliseconds).
 | |
|  */
 | |
| function offsetMarkerTimes(markers, timeOffset) {
 | |
|   for (const marker of markers) {
 | |
|     marker.start -= timeOffset;
 | |
|     marker.end -= timeOffset;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Offsets and scales all the timestamps in the provided array by the
 | |
|  * specified time and scale factor.
 | |
|  *
 | |
|  * @param array array
 | |
|  *        A list of timestamps received from the backend.
 | |
|  * @param number timeOffset
 | |
|  *        The amount of time to offset by (in milliseconds).
 | |
|  * @param number timeScale
 | |
|  *        The factor to scale by, after offsetting.
 | |
|  */
 | |
| function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
 | |
|   for (let i = 0, len = timestamps.length; i < len; i++) {
 | |
|     timestamps[i] -= timeOffset;
 | |
|     if (timeScale) {
 | |
|       timestamps[i] /= timeScale;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Push all elements of src array into dest array. Marker data will come in small chunks
 | |
|  * and add up over time, whereas allocation arrays can be > 500000 elements (and
 | |
|  * Function.prototype.apply throws if applying more than 500000 elements, which
 | |
|  * is what spawned this separate function), so iterate one element at a time.
 | |
|  * @see bug 1166823
 | |
|  * @see http://jsperf.com/concat-large-arrays
 | |
|  * @see http://jsperf.com/concat-large-arrays/2
 | |
|  *
 | |
|  * @param {Array} dest
 | |
|  * @param {Array} src
 | |
|  */
 | |
| function pushAll(dest, src) {
 | |
|   const length = src.length;
 | |
|   for (let i = 0; i < length; i++) {
 | |
|     dest.push(src[i]);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Cache used in `RecordingUtils.getProfileThreadFromAllocations`.
 | |
|  */
 | |
| var gProfileThreadFromAllocationCache = new WeakMap();
 | |
| 
 | |
| /**
 | |
|  * Converts allocation data from the memory actor to something that follows
 | |
|  * the same structure as the samples data received from the profiler.
 | |
|  *
 | |
|  * @see MemoryActor.prototype.getAllocations for more information.
 | |
|  *
 | |
|  * @param object allocations
 | |
|  *        A list of { sites, timestamps, frames, sizes } arrays.
 | |
|  * @return object
 | |
|  *         The "profile" describing the allocations log.
 | |
|  */
 | |
| function getProfileThreadFromAllocations(allocations) {
 | |
|   const cached = gProfileThreadFromAllocationCache.get(allocations);
 | |
|   if (cached) {
 | |
|     return cached;
 | |
|   }
 | |
| 
 | |
|   const { sites, timestamps, frames, sizes } = allocations;
 | |
|   const uniqueStrings = new UniqueStrings();
 | |
| 
 | |
|   // Convert allocation frames to the the stack and frame tables expected by
 | |
|   // the profiler format.
 | |
|   //
 | |
|   // Since the allocations log is already presented as a tree, we would be
 | |
|   // wasting time if we jumped through the same hoops as deflateProfile below
 | |
|   // and instead use the existing structure of the allocations log to build up
 | |
|   // the profile JSON.
 | |
|   //
 | |
|   // The allocations.frames array corresponds roughly to the profile stack
 | |
|   // table: a trie of all stacks. We could work harder to further deduplicate
 | |
|   // each individual frame as the profiler does, but it is not necessary for
 | |
|   // correctness.
 | |
|   const stackTable = new Array(frames.length);
 | |
|   const frameTable = new Array(frames.length);
 | |
| 
 | |
|   // Array used to concat the location.
 | |
|   const locationConcatArray = new Array(5);
 | |
| 
 | |
|   for (let i = 0; i < frames.length; i++) {
 | |
|     const frame = frames[i];
 | |
|     if (!frame) {
 | |
|       stackTable[i] = frameTable[i] = null;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     const prefix = frame.parent;
 | |
| 
 | |
|     // Schema:
 | |
|     //   [prefix, frame]
 | |
|     stackTable[i] = [frames[prefix] ? prefix : null, i];
 | |
| 
 | |
|     // Schema:
 | |
|     //   [location]
 | |
|     //
 | |
|     // The only field a frame will have in an allocations profile is location.
 | |
|     //
 | |
|     // If frame.functionDisplayName is present, the format is
 | |
|     //   "functionDisplayName (source:line:column)"
 | |
|     // Otherwise, it is
 | |
|     //   "source:line:column"
 | |
|     //
 | |
|     // A static array is used to join to save memory on intermediate strings.
 | |
|     locationConcatArray[0] = frame.source;
 | |
|     locationConcatArray[1] = ":";
 | |
|     locationConcatArray[2] = String(frame.line);
 | |
|     locationConcatArray[3] = ":";
 | |
|     locationConcatArray[4] = String(frame.column);
 | |
|     locationConcatArray[5] = "";
 | |
| 
 | |
|     let location = locationConcatArray.join("");
 | |
|     const funcName = frame.functionDisplayName;
 | |
| 
 | |
|     if (funcName) {
 | |
|       locationConcatArray[0] = funcName;
 | |
|       locationConcatArray[1] = " (";
 | |
|       locationConcatArray[2] = location;
 | |
|       locationConcatArray[3] = ")";
 | |
|       locationConcatArray[4] = "";
 | |
|       locationConcatArray[5] = "";
 | |
|       location = locationConcatArray.join("");
 | |
|     }
 | |
| 
 | |
|     frameTable[i] = [uniqueStrings.getOrAddStringIndex(location)];
 | |
|   }
 | |
| 
 | |
|   const samples = new Array(sites.length);
 | |
|   let writePos = 0;
 | |
|   for (let i = 0; i < sites.length; i++) {
 | |
|     // Schema:
 | |
|     //   [stack, time, size]
 | |
|     //
 | |
|     // Originally, sites[i] indexes into the frames array. Note that in the
 | |
|     // loop above, stackTable[sites[i]] and frames[sites[i]] index the same
 | |
|     // information.
 | |
|     const stackIndex = sites[i];
 | |
|     if (frames[stackIndex]) {
 | |
|       samples[writePos++] = [stackIndex, timestamps[i], sizes[i]];
 | |
|     }
 | |
|   }
 | |
|   samples.length = writePos;
 | |
| 
 | |
|   const thread = {
 | |
|     name: "allocations",
 | |
|     samples: allocationsWithSchema(samples),
 | |
|     stackTable: stackTableWithSchema(stackTable),
 | |
|     frameTable: frameTableWithSchema(frameTable),
 | |
|     stringTable: uniqueStrings.stringTable,
 | |
|   };
 | |
| 
 | |
|   gProfileThreadFromAllocationCache.set(allocations, thread);
 | |
|   return thread;
 | |
| }
 | |
| 
 | |
| function allocationsWithSchema(data) {
 | |
|   let slot = 0;
 | |
|   return {
 | |
|     schema: {
 | |
|       stack: slot++,
 | |
|       time: slot++,
 | |
|       size: slot++,
 | |
|     },
 | |
|     data: data,
 | |
|   };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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.
 | |
|  */
 | |
| function deflateProfile(profile) {
 | |
|   profile.threads = profile.threads.map(thread => {
 | |
|     const uniqueStacks = new UniqueStacks();
 | |
|     return deflateThread(thread, uniqueStacks);
 | |
|   });
 | |
| 
 | |
|   profile.meta.version = 3;
 | |
|   return profile;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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++) {
 | |
|     const 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]
 | |
| 
 | |
|   const deflatedSamples = new Array(samples.length);
 | |
|   for (let i = 0; i < samples.length; i++) {
 | |
|     const sample = samples[i];
 | |
|     deflatedSamples[i] = [
 | |
|       deflateStack(sample.frames, uniqueStacks),
 | |
|       sample.time,
 | |
|       sample.responsiveness,
 | |
|       sample.rss,
 | |
|       sample.uss,
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   return samplesWithSchema(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]
 | |
| 
 | |
|   const deflatedMarkers = new Array(markers.length);
 | |
|   for (let i = 0; i < markers.length; i++) {
 | |
|     const 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) {
 | |
|   // Some extra threads in a profile come stringified as a full profile (so
 | |
|   // it has nested threads itself) so the top level "thread" does not have markers
 | |
|   // or samples. We don't use this anyway so just make this safe to deflate.
 | |
|   // can be a string rather than an object on import. Bug 1173695
 | |
|   if (typeof thread === "string") {
 | |
|     thread = JSON.parse(thread);
 | |
|   }
 | |
|   if (!thread.samples) {
 | |
|     thread.samples = [];
 | |
|   }
 | |
|   if (!thread.markers) {
 | |
|     thread.markers = [];
 | |
|   }
 | |
| 
 | |
|   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.getStringTable(),
 | |
|   };
 | |
| }
 | |
| 
 | |
| function stackTableWithSchema(data) {
 | |
|   let slot = 0;
 | |
|   return {
 | |
|     schema: {
 | |
|       prefix: slot++,
 | |
|       frame: slot++,
 | |
|     },
 | |
|     data: data,
 | |
|   };
 | |
| }
 | |
| 
 | |
| function frameTableWithSchema(data) {
 | |
|   let slot = 0;
 | |
|   return {
 | |
|     schema: {
 | |
|       location: slot++,
 | |
|       implementation: slot++,
 | |
|       optimizations: slot++,
 | |
|       line: slot++,
 | |
|       category: slot++,
 | |
|     },
 | |
|     data: data,
 | |
|   };
 | |
| }
 | |
| 
 | |
| function samplesWithSchema(data) {
 | |
|   let slot = 0;
 | |
|   return {
 | |
|     schema: {
 | |
|       stack: slot++,
 | |
|       time: slot++,
 | |
|       responsiveness: slot++,
 | |
|       rss: slot++,
 | |
|       uss: slot++,
 | |
|     },
 | |
|     data: data,
 | |
|   };
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A helper class to deduplicate strings.
 | |
|  */
 | |
| function UniqueStrings() {
 | |
|   this.stringTable = [];
 | |
|   this._stringHash = Object.create(null);
 | |
| }
 | |
| 
 | |
| UniqueStrings.prototype.getOrAddStringIndex = function(s) {
 | |
|   if (!s) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   const stringHash = this._stringHash;
 | |
|   const stringTable = this.stringTable;
 | |
|   let index = stringHash[s];
 | |
|   if (index !== undefined) {
 | |
|     return index;
 | |
|   }
 | |
| 
 | |
|   index = stringTable.length;
 | |
|   stringHash[s] = index;
 | |
|   stringTable.push(s);
 | |
|   return index;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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._frameHash = Object.create(null);
 | |
|   this._stackHash = Object.create(null);
 | |
|   this._uniqueStrings = new UniqueStrings();
 | |
| }
 | |
| 
 | |
| UniqueStacks.prototype.getStackTableWithSchema = function() {
 | |
|   return stackTableWithSchema(this._stackTable);
 | |
| };
 | |
| 
 | |
| UniqueStacks.prototype.getFrameTableWithSchema = function() {
 | |
|   return frameTableWithSchema(this._frameTable);
 | |
| };
 | |
| 
 | |
| UniqueStacks.prototype.getStringTable = function() {
 | |
|   return this._uniqueStrings.stringTable;
 | |
| };
 | |
| 
 | |
| UniqueStacks.prototype.getOrAddFrameIndex = function(frame) {
 | |
|   // Schema:
 | |
|   //   [location, implementation, optimizations, line, category]
 | |
| 
 | |
|   const frameHash = this._frameHash;
 | |
|   const frameTable = this._frameTable;
 | |
| 
 | |
|   const locationIndex = this.getOrAddStringIndex(frame.location);
 | |
|   const implementationIndex = this.getOrAddStringIndex(frame.implementation);
 | |
| 
 | |
|   // Super dumb.
 | |
|   const 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]
 | |
| 
 | |
|   const stackHash = this._stackHash;
 | |
|   const stackTable = this._stackTable;
 | |
| 
 | |
|   // Also super dumb.
 | |
|   const 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) {
 | |
|   return this._uniqueStrings.getOrAddStringIndex(s);
 | |
| };
 | |
| 
 | |
| exports.pushAll = pushAll;
 | |
| exports.mapRecordingOptions = mapRecordingOptions;
 | |
| exports.normalizePerformanceFeatures = normalizePerformanceFeatures;
 | |
| exports.filterSamples = filterSamples;
 | |
| exports.offsetSampleTimes = offsetSampleTimes;
 | |
| exports.offsetMarkerTimes = offsetMarkerTimes;
 | |
| exports.offsetAndScaleTimestamps = offsetAndScaleTimestamps;
 | |
| exports.getProfileThreadFromAllocations = getProfileThreadFromAllocations;
 | |
| exports.deflateProfile = deflateProfile;
 | |
| exports.deflateThread = deflateThread;
 | |
| exports.UniqueStrings = UniqueStrings;
 | |
| exports.UniqueStacks = UniqueStacks;
 |