Bug 1587503 - Provide a mechanism to inspect the current profiler configuration; r=gerald

This patch creates a new API to the nsIProfiler interface, through the activeConfiguration
property. It exposes the internal configuration of the profiler. In addition, this information
is serialized into the profile meta object.

Differential Revision: https://phabricator.services.mozilla.com/D48733

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Greg Tatum 2019-10-11 18:22:42 +00:00
parent 65efbcd070
commit 03cd55fe37
6 changed files with 201 additions and 0 deletions

View File

@ -745,6 +745,55 @@ class ActivePS {
sInstance->ThreadSelected(aInfo->Name()));
}
// Writes out the current active configuration of the profile.
static void WriteActiveConfiguration(PSLockRef aLock, JSONWriter& aWriter,
const char* aPropertyName = nullptr) {
if (!sInstance) {
if (aPropertyName) {
aWriter.NullProperty(aPropertyName);
} else {
aWriter.NullElement();
}
return;
};
if (aPropertyName) {
aWriter.StartObjectProperty(aPropertyName);
} else {
aWriter.StartObjectElement();
}
{
aWriter.StartArrayProperty("features", aWriter.SingleLineStyle);
#define WRITE_ACTIVE_FEATURES(n_, str_, Name_, desc_) \
if (profiler_feature_active(ProfilerFeature::Name_)) { \
aWriter.StringElement(str_); \
}
PROFILER_FOR_EACH_FEATURE(WRITE_ACTIVE_FEATURES)
#undef WRITE_ACTIVE_FEATURES
aWriter.EndArray();
}
{
aWriter.StartArrayProperty("threads", aWriter.SingleLineStyle);
for (const auto& filter : sInstance->mFilters) {
aWriter.StringElement(filter.c_str());
}
aWriter.EndArray();
}
{
// Now write all the simple values.
// The interval is also available on profile.meta.interval
aWriter.DoubleProperty("interval", sInstance->mInterval);
aWriter.IntProperty("capacity", sInstance->mCapacity.Value());
if (sInstance->mDuration) {
aWriter.DoubleProperty("duration", sInstance->mDuration.value());
}
}
aWriter.EndObject();
}
PS_GET(uint32_t, Generation)
PS_GET(PowerOfTwo32, Capacity)
@ -2036,6 +2085,8 @@ static void StreamMetaJSCustomObject(PSLockRef aLock,
StreamCategories(aWriter);
aWriter.EndArray();
ActivePS::WriteActiveConfiguration(aLock, aWriter, "configuration");
if (!NS_IsMainThread()) {
// Leave the rest of the properties out if we're not on the main thread.
// At the moment, the only case in which this function is called on a
@ -3970,6 +4021,12 @@ bool profiler_feature_active(uint32_t aFeature) {
return RacyFeatures::IsActiveWithFeature(aFeature);
}
void profiler_write_active_configuration(JSONWriter& aWriter) {
MOZ_RELEASE_ASSERT(CorePS::Exists());
PSAutoLock lock(gPSMutex);
ActivePS::WriteActiveConfiguration(lock, aWriter);
}
void profiler_add_sampled_counter(BaseProfilerCount* aCounter) {
DEBUG_LOG("profiler_add_sampled_counter(%s)", aCounter->mLabel);
PSAutoLock lock(gPSMutex);

View File

@ -103,6 +103,9 @@ enum class JSInstrumentationFlags {
// Record an exit profile from a child process.
void profiler_received_exit_profile(const nsCString& aExitProfile);
// Write out the information of the active profiling configuration.
void profiler_write_active_configuration(mozilla::JSONWriter& aWriter);
// Extract all received exit profiles that have not yet expired (i.e., they
// still intersect with this process' buffer range).
mozilla::Vector<nsCString> profiler_move_exit_profiles();

View File

@ -85,6 +85,15 @@ interface nsIProfiler : nsISupports
*/
Array<AUTF8String> GetFeatures();
/**
* Returns a JavaScript object that contains a description of the currently configured
* state of the profiler when the profiler is active. This can be useful to assert
* the UI of the profiler's recording panel in tests. It returns null when the profiler
* is not active.
*/
[implicit_jscontext]
readonly attribute jsval activeConfiguration;
/**
* Returns an array of all features that are supported by the profiler.
* The array may contain features that are not supported in this build.

View File

@ -223,6 +223,30 @@ nsProfiler::GetSharedLibraries(JSContext* aCx,
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::GetActiveConfiguration(JSContext* aCx,
JS::MutableHandle<JS::Value> aResult) {
JS::RootedValue jsValue(aCx);
{
nsString buffer;
JSONWriter writer(MakeUnique<StringWriteFunc>(buffer));
profiler_write_active_configuration(writer);
MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx,
static_cast<const char16_t*>(buffer.get()),
buffer.Length(), &jsValue));
}
if (jsValue.isNull()) {
aResult.setNull();
} else {
JS::RootedObject obj(aCx, &jsValue.toObject());
if (!obj) {
return NS_ERROR_FAILURE;
}
aResult.setObject(*obj);
}
return NS_OK;
}
NS_IMETHODIMP
nsProfiler::DumpProfileToFile(const char* aFilename) {
profiler_save_profile_to_file(aFilename);

View File

@ -0,0 +1,107 @@
/* 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/. */
function run_test() {
if (!AppConstants.MOZ_GECKO_PROFILER) {
return;
}
info(
"Checking that the profiler can fetch the information about the active " +
"configuration that is being used to power the profiler."
);
equal(
Services.profiler.activeConfiguration,
null,
"When the profile is off, there is no active configuration."
);
{
info("Start the profiler.");
const entries = 10000;
const interval = 1;
const threads = ["GeckoMain"];
const features = ["js", "leaf", "threads"];
Services.profiler.StartProfiler(entries, interval, features, threads);
info("Generate the activeConfiguration.");
const { activeConfiguration } = Services.profiler;
const expectedConfiguration = {
interval,
threads,
features,
// The buffer is created as a power of two that can fit all of the entires
// into it. If the ratio of entries to buffer size ever changes, this setting
// will need to be updated.
capacity: Math.pow(2, 14),
};
deepEqual(
activeConfiguration,
expectedConfiguration,
"The active configuration matches configuration given."
);
info("Get the profile.");
const profile = Services.profiler.getProfileData();
deepEqual(
profile.meta.configuration,
expectedConfiguration,
"The configuration also matches on the profile meta object."
);
}
{
const entries = 20000;
const interval = 0.5;
const threads = ["GeckoMain", "DOM Worker"];
const features = ["threads"];
const duration = 20;
info("Restart the profiler with a new configuration.");
Services.profiler.StartProfiler(
entries,
interval,
features,
threads,
// Also start it with duration, this property is optional.
duration
);
info("Generate the activeConfiguration.");
const { activeConfiguration } = Services.profiler;
const expectedConfiguration = {
interval,
threads,
features,
duration,
// The buffer is created as a power of two that can fit all of the entires
// into it. If the ratio of entries to buffer size ever changes, this setting
// will need to be updated.
capacity: Math.pow(2, 15),
};
deepEqual(
activeConfiguration,
expectedConfiguration,
"The active configuration matches the new configuration."
);
info("Get the profile.");
const profile = Services.profiler.getProfileData();
deepEqual(
profile.meta.configuration,
expectedConfiguration,
"The configuration also matches on the profile meta object."
);
}
Services.profiler.StopProfiler();
equal(
Services.profiler.activeConfiguration,
null,
"When the profile is off, there is no active configuration."
);
}

View File

@ -2,6 +2,7 @@
head = head_profiler.js
skip-if = toolkit == 'android'
[test_active_configuration.js]
[test_start.js]
skip-if = true
[test_get_features.js]