mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 15:28:39 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
12
devtools/shared/performance/moz.build
Normal file
12
devtools/shared/performance/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
|
||||
|
||||
DevToolsModules(
|
||||
'recording-common.js',
|
||||
'recording-utils.js',
|
||||
)
|
||||
97
devtools/shared/performance/recording-common.js
Normal file
97
devtools/shared/performance/recording-common.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* A mixin to be used for PerformanceRecordingActor, PerformanceRecordingFront,
|
||||
* and LegacyPerformanceRecording for helper methods to access data.
|
||||
*/
|
||||
|
||||
const PerformanceRecordingCommon = exports.PerformanceRecordingCommon = {
|
||||
// Private fields, only needed when a recording is started or stopped.
|
||||
_console: false,
|
||||
_imported: false,
|
||||
_recording: false,
|
||||
_completed: false,
|
||||
_configuration: {},
|
||||
_startingBufferStatus: null,
|
||||
_localStartTime: 0,
|
||||
|
||||
// Serializable fields, necessary and sufficient for import and export.
|
||||
_label: "",
|
||||
_duration: 0,
|
||||
_markers: null,
|
||||
_frames: null,
|
||||
_memory: null,
|
||||
_ticks: null,
|
||||
_allocations: null,
|
||||
_profile: null,
|
||||
_systemHost: null,
|
||||
_systemClient: null,
|
||||
|
||||
/**
|
||||
* Helper methods for returning the status of the recording.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
isRecording: function () { return this._recording; },
|
||||
isCompleted: function () { return this._completed || this.isImported(); },
|
||||
isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
|
||||
isConsole: function () { return this._console; },
|
||||
isImported: function () { return this._imported; },
|
||||
|
||||
/**
|
||||
* Helper methods for returning configuration for the recording.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
getConfiguration: function () { return this._configuration; },
|
||||
getLabel: function () { return this._label; },
|
||||
|
||||
/**
|
||||
* Gets duration of this recording, in milliseconds.
|
||||
* @return number
|
||||
*/
|
||||
getDuration: function () {
|
||||
// Compute an approximate ending time for the current recording if it is
|
||||
// still in progress. This is needed to ensure that the view updates even
|
||||
// when new data is not being generated. If recording is completed, use
|
||||
// the duration from the profiler; if between recording and being finalized,
|
||||
// use the last estimated duration.
|
||||
if (this.isRecording()) {
|
||||
return this._estimatedDuration = Date.now() - this._localStartTime;
|
||||
} else {
|
||||
return this._duration || this._estimatedDuration || 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper methods for returning recording data.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
getMarkers: function () { return this._markers; },
|
||||
getFrames: function () { return this._frames; },
|
||||
getMemory: function () { return this._memory; },
|
||||
getTicks: function () { return this._ticks; },
|
||||
getAllocations: function () { return this._allocations; },
|
||||
getProfile: function () { return this._profile; },
|
||||
getHostSystemInfo: function () { return this._systemHost; },
|
||||
getClientSystemInfo: function () { return this._systemClient; },
|
||||
getStartingBufferStatus: function () { return this._startingBufferStatus; },
|
||||
|
||||
getAllData: function () {
|
||||
let label = this.getLabel();
|
||||
let duration = this.getDuration();
|
||||
let markers = this.getMarkers();
|
||||
let frames = this.getFrames();
|
||||
let memory = this.getMemory();
|
||||
let ticks = this.getTicks();
|
||||
let allocations = this.getAllocations();
|
||||
let profile = this.getProfile();
|
||||
let configuration = this.getConfiguration();
|
||||
let systemHost = this.getHostSystemInfo();
|
||||
let systemClient = this.getClientSystemInfo();
|
||||
|
||||
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration, systemHost, systemClient };
|
||||
},
|
||||
};
|
||||
628
devtools/shared/performance/recording-utils.js
Normal file
628
devtools/shared/performance/recording-utils.js
Normal file
|
|
@ -0,0 +1,628 @@
|
|||
/* 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";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
loader.lazyRequireGetter(this, "extend",
|
||||
"sdk/util/object", true);
|
||||
|
||||
/**
|
||||
* 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 * 1000)) : 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) {
|
||||
let 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) {
|
||||
let firstThread = profile.threads[0];
|
||||
const TIME_SLOT = firstThread.samples.schema.time;
|
||||
let 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 (let 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) {
|
||||
let 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) {
|
||||
let cached = gProfileThreadFromAllocationCache.get(allocations);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
let { sites, timestamps, frames, sizes } = allocations;
|
||||
let 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.
|
||||
let stackTable = new Array(frames.length);
|
||||
let frameTable = new Array(frames.length);
|
||||
|
||||
// Array used to concat the location.
|
||||
let locationConcatArray = new Array(5);
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
let frame = frames[i];
|
||||
if (!frame) {
|
||||
stackTable[i] = frameTable[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
let 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("");
|
||||
let 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)];
|
||||
}
|
||||
|
||||
let 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.
|
||||
let stackIndex = sites[i];
|
||||
if (frames[stackIndex]) {
|
||||
samples[writePos++] = [stackIndex, timestamps[i], sizes[i]];
|
||||
}
|
||||
}
|
||||
samples.length = writePos;
|
||||
|
||||
let 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) => {
|
||||
let 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++) {
|
||||
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
|
||||
];
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
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) {
|
||||
// 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++,
|
||||
frameNumber: slot++,
|
||||
power: 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;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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]
|
||||
|
||||
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) {
|
||||
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;
|
||||
7
devtools/shared/performance/test/head.js
Normal file
7
devtools/shared/performance/test/head.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if allocations data received from the performance actor is properly
|
||||
* converted to something that follows the same structure as the samples data
|
||||
* received from the profiler.
|
||||
*/
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function () {
|
||||
const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
|
||||
let output = getProfileThreadFromAllocations(TEST_DATA);
|
||||
equal(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
|
||||
});
|
||||
|
||||
var TEST_DATA = {
|
||||
sites: [0, 0, 1, 2, 3],
|
||||
timestamps: [50, 100, 150, 200, 250],
|
||||
sizes: [0, 0, 100, 200, 300],
|
||||
frames: [
|
||||
null, {
|
||||
source: "A",
|
||||
line: 1,
|
||||
column: 2,
|
||||
functionDisplayName: "x",
|
||||
parent: 0
|
||||
}, {
|
||||
source: "B",
|
||||
line: 3,
|
||||
column: 4,
|
||||
functionDisplayName: "y",
|
||||
parent: 1
|
||||
}, {
|
||||
source: "C",
|
||||
line: 5,
|
||||
column: 6,
|
||||
functionDisplayName: null,
|
||||
parent: 2
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var EXPECTED_OUTPUT = {
|
||||
name: "allocations",
|
||||
samples: {
|
||||
"schema": {
|
||||
"stack": 0,
|
||||
"time": 1,
|
||||
"size": 2,
|
||||
},
|
||||
data: [
|
||||
[ 1, 150, 100 ],
|
||||
[ 2, 200, 200 ],
|
||||
[ 3, 250, 300 ]
|
||||
]
|
||||
},
|
||||
stackTable: {
|
||||
"schema": {
|
||||
"prefix": 0,
|
||||
"frame": 1
|
||||
},
|
||||
"data": [
|
||||
null,
|
||||
[ null, 1 ], // x (A:1:2)
|
||||
[ 1, 2 ], // x (A:1:2) > y (B:3:4)
|
||||
[ 2, 3 ] // x (A:1:2) > y (B:3:4) > C:5:6
|
||||
]
|
||||
},
|
||||
frameTable: {
|
||||
"schema": {
|
||||
"location": 0,
|
||||
"implementation": 1,
|
||||
"optimizations": 2,
|
||||
"line": 3,
|
||||
"category": 4
|
||||
},
|
||||
data: [
|
||||
null,
|
||||
[ 0 ],
|
||||
[ 1 ],
|
||||
[ 2 ]
|
||||
]
|
||||
},
|
||||
"stringTable": [
|
||||
"x (A:1:2)",
|
||||
"y (B:3:4)",
|
||||
"C:5:6"
|
||||
],
|
||||
};
|
||||
8
devtools/shared/performance/test/xpcshell.ini
Normal file
8
devtools/shared/performance/test/xpcshell.ini
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
tags = devtools
|
||||
head = head.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_perf-utils-allocations-to-samples.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue