mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1674476 - ChromeUtils.addProfilerMarker should support capturing a stack or setting the category, r=gerald,gregtatum.
Differential Revision: https://phabricator.services.mozilla.com/D95406
This commit is contained in:
parent
4db97d1029
commit
0d164d3a9f
@ -193,12 +193,37 @@ void ChromeUtils::ReleaseAssert(GlobalObject& aGlobal, bool aCondition,
|
||||
/* static */
|
||||
void ChromeUtils::AddProfilerMarker(
|
||||
GlobalObject& aGlobal, const nsACString& aName,
|
||||
const Optional<DOMHighResTimeStamp>& aStartTime,
|
||||
const ProfilerMarkerOptionsOrDouble& aOptions,
|
||||
const Optional<nsACString>& aText) {
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
MarkerOptions options;
|
||||
MarkerCategory category = ::geckoprofiler::category::JS;
|
||||
|
||||
if (aStartTime.WasPassed()) {
|
||||
DOMHighResTimeStamp startTime = 0;
|
||||
if (aOptions.IsDouble()) {
|
||||
startTime = aOptions.GetAsDouble();
|
||||
} else {
|
||||
const ProfilerMarkerOptions& opt = aOptions.GetAsProfilerMarkerOptions();
|
||||
startTime = opt.mStartTime;
|
||||
|
||||
if (opt.mCaptureStack) {
|
||||
options.Set(MarkerStack::Capture());
|
||||
}
|
||||
# define BEGIN_CATEGORY(name, labelAsString, color) \
|
||||
if (opt.mCategory.Equals(labelAsString)) { \
|
||||
category = ::geckoprofiler::category::name; \
|
||||
} else
|
||||
# define SUBCATEGORY(supercategory, name, labelAsString)
|
||||
# define END_CATEGORY
|
||||
MOZ_PROFILING_CATEGORY_LIST(BEGIN_CATEGORY, SUBCATEGORY, END_CATEGORY)
|
||||
# undef BEGIN_CATEGORY
|
||||
# undef SUBCATEGORY
|
||||
# undef END_CATEGORY
|
||||
{
|
||||
category = ::geckoprofiler::category::OTHER;
|
||||
}
|
||||
}
|
||||
if (startTime) {
|
||||
RefPtr<Performance> performance;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
@ -218,18 +243,22 @@ void ChromeUtils::AddProfilerMarker(
|
||||
if (performance) {
|
||||
options.Set(MarkerTiming::IntervalUntilNowFrom(
|
||||
performance->CreationTimeStamp() +
|
||||
TimeDuration::FromMilliseconds(aStartTime.Value())));
|
||||
TimeDuration::FromMilliseconds(startTime)));
|
||||
} else {
|
||||
options.Set(MarkerTiming::IntervalUntilNowFrom(
|
||||
TimeStamp::ProcessCreation() +
|
||||
TimeDuration::FromMilliseconds(aStartTime.Value())));
|
||||
TimeDuration::FromMilliseconds(startTime)));
|
||||
}
|
||||
}
|
||||
|
||||
if (aText.WasPassed()) {
|
||||
PROFILER_MARKER_TEXT(aName, JS, std::move(options), aText.Value());
|
||||
} else {
|
||||
PROFILER_MARKER_UNTYPED(aName, JS, std::move(options));
|
||||
{
|
||||
AUTO_PROFILER_STATS(ChromeUtils::AddProfilerMarker);
|
||||
if (aText.WasPassed()) {
|
||||
profiler_add_marker(aName, category, std::move(options),
|
||||
::geckoprofiler::markers::Text{}, aText.Value());
|
||||
} else {
|
||||
profiler_add_marker(aName, category, std::move(options));
|
||||
}
|
||||
}
|
||||
#endif // MOZ_GECKO_PROFILER
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class ChromeUtils {
|
||||
const nsAString& aMessage);
|
||||
|
||||
static void AddProfilerMarker(GlobalObject& aGlobal, const nsACString& aName,
|
||||
const Optional<DOMHighResTimeStamp>& aStartTime,
|
||||
const ProfilerMarkerOptionsOrDouble& aOptions,
|
||||
const Optional<nsACString>& text);
|
||||
|
||||
static void OriginAttributesToSuffix(
|
||||
|
@ -24,6 +24,30 @@ interface MozQueryInterface {
|
||||
legacycaller any (any aIID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for a marker created with the addProfilerMarker method.
|
||||
* All fields are optional.
|
||||
*/
|
||||
dictionary ProfilerMarkerOptions {
|
||||
// A timestamp to use as the start time of the marker.
|
||||
// If no start time is provided, the marker will have no duration.
|
||||
// In window and ChromeWorker contexts, use a timestamp from
|
||||
// `performance.now()`.
|
||||
// In JS modules, use `Cu.now()` to get a timestamp.
|
||||
DOMHighResTimeStamp startTime = 0;
|
||||
|
||||
// If captureStack is true, a profiler stack will be captured and associated
|
||||
// with the marker.
|
||||
boolean captureStack = false;
|
||||
|
||||
// Markers are created by default in the JavaScript category, but this can be
|
||||
// overridden.
|
||||
// Examples of correct values: "JavaScript", "Test", "Other", ...
|
||||
// See ProfilingCategoryList.h for the complete list of valid values.
|
||||
// Using an unrecognized value will set the category to "Other".
|
||||
ByteString category = "JavaScript";
|
||||
};
|
||||
|
||||
/**
|
||||
* A collection of static utility methods that are only exposed to system code.
|
||||
* This is exposed in all the system globals where we can expose stuff by
|
||||
@ -171,15 +195,18 @@ namespace ChromeUtils {
|
||||
* add a marker for the current thread. No-op otherwise.
|
||||
*
|
||||
* @param name The name of the marker.
|
||||
* @param startTime The timestamp to use as the start of the marker.
|
||||
* If omitted, the marker will have no duration.
|
||||
* @param options Either a timestamp to use as the start time of the
|
||||
* marker, or a ProfilerMarkerOptions object that can
|
||||
* contain startTime, captureStack or category fields.
|
||||
* If this parameter is omitted, the marker will have
|
||||
* no duration.
|
||||
* In window and ChromeWorker contexts, use a
|
||||
* timestamp from `performance.now()`.
|
||||
* In JS modules, use `Cu.now()` to get a timestamp.
|
||||
* @param text Text to associate with the marker.
|
||||
*/
|
||||
void addProfilerMarker(UTF8String name,
|
||||
optional DOMHighResTimeStamp startTime,
|
||||
optional (ProfilerMarkerOptions or DOMHighResTimeStamp) options = {},
|
||||
optional UTF8String text);
|
||||
|
||||
/**
|
||||
|
@ -36,6 +36,9 @@
|
||||
SUBCATEGORY(OTHER, OTHER_PreferenceRead, "Preference Read") \
|
||||
SUBCATEGORY(OTHER, OTHER_Profiling, "Profiling") \
|
||||
END_CATEGORY \
|
||||
BEGIN_CATEGORY(TEST, "Test", "darkgray") \
|
||||
SUBCATEGORY(TEST, TEST, "Test") \
|
||||
END_CATEGORY \
|
||||
BEGIN_CATEGORY(LAYOUT, "Layout", "purple") \
|
||||
SUBCATEGORY(LAYOUT, LAYOUT, "Other") \
|
||||
SUBCATEGORY(LAYOUT, LAYOUT_FrameConstruction, "Frame construction") \
|
||||
@ -103,6 +106,7 @@
|
||||
SUBCATEGORY(IPC, IPC, "Other") \
|
||||
END_CATEGORY \
|
||||
BEGIN_CATEGORY(MEDIA, "Media", "orange") \
|
||||
SUBCATEGORY(MEDIA, MEDIA, "Other") \
|
||||
SUBCATEGORY(MEDIA, MEDIA_CUBEB, "Cubeb") \
|
||||
SUBCATEGORY(MEDIA, MEDIA_PLAYBACK, "Playback") \
|
||||
SUBCATEGORY(MEDIA, MEDIA_RT, "Real-time rendering") \
|
||||
|
184
tools/profiler/tests/xpcshell/test_addProfilerMarker.js
Normal file
184
tools/profiler/tests/xpcshell/test_addProfilerMarker.js
Normal file
@ -0,0 +1,184 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Test that ChromeUtils.addProfilerMarker is working correctly.
|
||||
*/
|
||||
|
||||
const markerNamePrefix = "test_addProfilerMarker";
|
||||
const markerText = "Text payload";
|
||||
// The same startTime will be used for all markers with a duration,
|
||||
// and we store this value globally so that expectDuration and
|
||||
// expectNoDuration can access it. The value isn't set here as we
|
||||
// want a start time after the profiler has started
|
||||
var startTime;
|
||||
|
||||
function expectNoDuration(marker) {
|
||||
Assert.equal(
|
||||
typeof marker.startTime,
|
||||
"number",
|
||||
"startTime should be a number"
|
||||
);
|
||||
Assert.greater(
|
||||
marker.startTime,
|
||||
startTime,
|
||||
"startTime should be after the begining of the test"
|
||||
);
|
||||
Assert.equal(typeof marker.endTime, "number", "endTime should be a number");
|
||||
Assert.equal(marker.endTime, 0, "endTime should be 0");
|
||||
}
|
||||
|
||||
function expectDuration(marker) {
|
||||
Assert.equal(
|
||||
typeof marker.startTime,
|
||||
"number",
|
||||
"startTime should be a number"
|
||||
);
|
||||
Assert.equal(
|
||||
Math.round(marker.startTime * 10 ** 6) / 10 ** 6,
|
||||
startTime,
|
||||
"startTime should be the expected time"
|
||||
);
|
||||
Assert.equal(typeof marker.endTime, "number", "endTime should be a number");
|
||||
Assert.greater(
|
||||
marker.endTime,
|
||||
startTime,
|
||||
"endTime should be after startTime"
|
||||
);
|
||||
}
|
||||
|
||||
function expectNoData(marker) {
|
||||
Assert.equal(
|
||||
typeof marker.data,
|
||||
"undefined",
|
||||
"The data property should be undefined"
|
||||
);
|
||||
}
|
||||
|
||||
function expectText(marker) {
|
||||
Assert.equal(
|
||||
typeof marker.data,
|
||||
"object",
|
||||
"The data property should be an object"
|
||||
);
|
||||
Assert.equal(marker.data.type, "Text", "Should be a Text marker");
|
||||
Assert.equal(
|
||||
marker.data.name,
|
||||
markerText,
|
||||
"The payload should contain the expected text"
|
||||
);
|
||||
}
|
||||
|
||||
function expectNoStack(marker) {
|
||||
Assert.ok(!marker.data || !marker.data.stack, "There should be no stack");
|
||||
}
|
||||
|
||||
function expectStack(marker) {
|
||||
Assert.ok(marker.data.stack, "There should be a stack");
|
||||
}
|
||||
|
||||
add_task(async () => {
|
||||
if (!AppConstants.MOZ_GECKO_PROFILER) {
|
||||
return;
|
||||
}
|
||||
|
||||
startProfiler();
|
||||
startTime = Math.round(Cu.now() * 10 ** 6) / 10 ** 6;
|
||||
info("startTime used for markers with durations: " + startTime);
|
||||
|
||||
/* Each call to testMarker will record a marker with a unique name.
|
||||
* The testFunctions and testCases objects contain respectively test
|
||||
* functions to verify that the marker found in the captured profile
|
||||
* matches expectations, and a string that can be printed to describe
|
||||
* in which way ChromeUtils.addProfilerMarker was called. */
|
||||
let testFunctions = {};
|
||||
let testCases = {};
|
||||
let markerId = 0;
|
||||
function testMarker(args, checks) {
|
||||
let name = markerNamePrefix + markerId++;
|
||||
ChromeUtils.addProfilerMarker(name, ...args);
|
||||
testFunctions[name] = checks;
|
||||
testCases[name] = `ChromeUtils.addProfilerMarker(${[name, ...args]
|
||||
.toSource()
|
||||
.slice(1, -1)})`;
|
||||
}
|
||||
|
||||
info("Record markers without options object.");
|
||||
testMarker([], m => {
|
||||
expectNoDuration(m);
|
||||
expectNoData(m);
|
||||
});
|
||||
testMarker([startTime], m => {
|
||||
expectDuration(m);
|
||||
expectNoData(m);
|
||||
});
|
||||
testMarker([undefined, markerText], m => {
|
||||
expectNoDuration(m);
|
||||
expectText(m);
|
||||
});
|
||||
testMarker([startTime, markerText], m => {
|
||||
expectDuration(m);
|
||||
expectText(m);
|
||||
});
|
||||
|
||||
info("Record markers providing the duration as the startTime property.");
|
||||
testMarker([{ startTime }], m => {
|
||||
expectDuration(m);
|
||||
expectNoData(m);
|
||||
});
|
||||
testMarker([{}, markerText], m => {
|
||||
expectNoDuration(m);
|
||||
expectText(m);
|
||||
});
|
||||
testMarker([{ startTime }, markerText], m => {
|
||||
expectDuration(m);
|
||||
expectText(m);
|
||||
});
|
||||
|
||||
info("Record markers to test the captureStack property.");
|
||||
const captureStack = true;
|
||||
testMarker([], expectNoStack);
|
||||
testMarker([startTime, markerText], expectNoStack);
|
||||
testMarker([{ captureStack: false }], expectNoStack);
|
||||
testMarker([{ captureStack }], expectStack);
|
||||
testMarker([{ startTime, captureStack }], expectStack);
|
||||
testMarker([{ captureStack }, markerText], expectStack);
|
||||
testMarker([{ startTime, captureStack }, markerText], expectStack);
|
||||
|
||||
info("Record markers to test the category property");
|
||||
function testCategory(args, expectedCategory) {
|
||||
testMarker(args, marker => {
|
||||
Assert.equal(marker.category, expectedCategory);
|
||||
});
|
||||
}
|
||||
testCategory([], "JavaScript");
|
||||
testCategory([{ category: "Test" }], "Test");
|
||||
testCategory([{ category: "Test" }, markerText], "Test");
|
||||
testCategory([{ category: "JavaScript" }], "JavaScript");
|
||||
testCategory([{ category: "Other" }], "Other");
|
||||
testCategory([{ category: "DOM" }], "DOM");
|
||||
testCategory([{ category: "does not exist" }], "Other");
|
||||
|
||||
info("Capture the profile");
|
||||
const profile = await stopAndGetProfile();
|
||||
const mainThread = profile.threads.find(({ name }) => name === "GeckoMain");
|
||||
const markers = getInflatedMarkerData(mainThread).filter(m =>
|
||||
m.name.startsWith(markerNamePrefix)
|
||||
);
|
||||
Assert.equal(
|
||||
markers.length,
|
||||
Object.keys(testFunctions).length,
|
||||
`Found ${markers.length} test markers in the captured profile`
|
||||
);
|
||||
|
||||
for (let marker of markers) {
|
||||
marker.category = profile.meta.categories[marker.category].name;
|
||||
info(`${testCases[marker.name]} -> ${marker.toSource()}`);
|
||||
|
||||
testFunctions[marker.name](marker);
|
||||
delete testFunctions[marker.name];
|
||||
}
|
||||
|
||||
Assert.equal(0, Object.keys(testFunctions).length, "all markers were found");
|
||||
});
|
@ -5,6 +5,7 @@ support-files =
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_active_configuration.js]
|
||||
[test_addProfilerMarker.js]
|
||||
[test_start.js]
|
||||
skip-if = true
|
||||
[test_get_features.js]
|
||||
|
Loading…
Reference in New Issue
Block a user