+
+
diff --git a/devtools/client/performance-new/popup/popup.js b/devtools/client/performance-new/popup/popup.js
index 36a4ef10bfef..28fef235114a 100644
--- a/devtools/client/performance-new/popup/popup.js
+++ b/devtools/client/performance-new/popup/popup.js
@@ -33,6 +33,7 @@ const features = [
"tasktracer",
"jstracer",
"jsallocations",
+ "preferencereads",
"trackopts",
];
const threadPrefix = "perf-settings-thread-checkbox-";
diff --git a/js/public/ProfilingCategory.h b/js/public/ProfilingCategory.h
index 91c7179e3e9f..5935adb65402 100644
--- a/js/public/ProfilingCategory.h
+++ b/js/public/ProfilingCategory.h
@@ -35,6 +35,7 @@
SUBCATEGORY(IDLE, IDLE, "Other") \
END_CATEGORY \
BEGIN_CATEGORY(OTHER, "Other", "grey") \
+ SUBCATEGORY(OTHER, OTHER_PreferenceRead, "Preference Read") \
SUBCATEGORY(OTHER, OTHER, "Other") \
END_CATEGORY \
BEGIN_CATEGORY(LAYOUT, "Layout", "purple") \
diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
index 01b5d56d550e..50e49cefc51b 100644
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -14,6 +14,7 @@
#include "base/basictypes.h"
#include "GeckoProfiler.h"
+#include "ProfilerMarkerPayload.h"
#include "MainThreadUtils.h"
#include "mozilla/ArenaAllocatorExtensions.h"
#include "mozilla/ArenaAllocator.h"
@@ -4324,6 +4325,22 @@ static nsresult pref_ReadDefaultPrefs(const RefPtr jarReader,
return NS_OK;
}
+static void PrefValueToString(const bool* b, nsCString& value) {
+ value = nsCString(*b ? "true" : "false");
+}
+static void PrefValueToString(const int* i, nsCString& value) {
+ value = nsPrintfCString("%d", *i);
+}
+static void PrefValueToString(const uint32_t* u, nsCString& value) {
+ value = nsPrintfCString("%d", *u);
+}
+static void PrefValueToString(const float* f, nsCString& value) {
+ value = nsPrintfCString("%f", *f);
+}
+static void PrefValueToString(const nsACString& s, nsCString& value) {
+ value = s;
+}
+
// These preference getter wrappers allow us to look up the value for static
// preferences based on their native types, rather than manually mapping them to
// the appropriate Preferences::Get* functions.
@@ -4333,20 +4350,54 @@ struct Internals {
template
static nsresult GetPrefValue(const char* aPrefName, T&& aResult,
PrefValueKind aKind) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
NS_ENSURE_TRUE(Preferences::InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
+ TimeStamp prefAccessTime = TimeStamp::Now();
+ Maybe prefType = Nothing();
+ nsCString prefValue{""};
+
if (Maybe pref = pref_Lookup(aPrefName)) {
- return pref->GetValue(aKind, std::forward(aResult));
+ rv = pref->GetValue(aKind, std::forward(aResult));
+
+ if (profiler_feature_active(ProfilerFeature::PreferenceReads)) {
+ prefType = Some(pref->Type());
+ PrefValueToString(aResult, prefValue);
+ }
}
- return NS_ERROR_UNEXPECTED;
+ if (profiler_feature_active(ProfilerFeature::PreferenceReads)) {
+ profiler_add_marker(
+ "PreferenceRead", JS::ProfilingCategoryPair::OTHER_PreferenceRead,
+ MakeUnique(aPrefName, Some(aKind), prefType,
+ prefValue, prefAccessTime));
+ }
+ return rv;
}
template
static nsresult GetSharedPrefValue(const char* aName, T* aResult) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+
+ TimeStamp prefAccessTime = TimeStamp::Now();
+ Maybe prefType = Nothing();
+ nsCString prefValue{""};
+
if (Maybe pref = pref_SharedLookup(aName)) {
- return pref->GetValue(PrefValueKind::User, aResult);
+ rv = pref->GetValue(PrefValueKind::User, aResult);
+
+ if (profiler_feature_active(ProfilerFeature::PreferenceReads)) {
+ prefType = Some(pref->Type());
+ PrefValueToString(aResult, prefValue);
+ }
}
- return NS_ERROR_UNEXPECTED;
+
+ if (profiler_feature_active(ProfilerFeature::PreferenceReads)) {
+ profiler_add_marker(
+ "PreferenceRead", JS::ProfilingCategoryPair::OTHER_PreferenceRead,
+ MakeUnique(aName, Nothing() /* indicates Shared */,
+ prefType, prefValue, prefAccessTime));
+ }
+ return rv;
}
template
diff --git a/toolkit/components/extensions/schemas/geckoProfiler.json b/toolkit/components/extensions/schemas/geckoProfiler.json
index 3a6b35f004e2..a04bf1e5613f 100644
--- a/toolkit/components/extensions/schemas/geckoProfiler.json
+++ b/toolkit/components/extensions/schemas/geckoProfiler.json
@@ -36,7 +36,8 @@
"threads",
"trackopts",
"jstracer",
- "jsallocations"
+ "jsallocations",
+ "preferencereads"
]
},
{
diff --git a/tools/profiler/core/ProfilerMarkerPayload.cpp b/tools/profiler/core/ProfilerMarkerPayload.cpp
index 63cd83a4a703..ca39c5b5bfa7 100644
--- a/tools/profiler/core/ProfilerMarkerPayload.cpp
+++ b/tools/profiler/core/ProfilerMarkerPayload.cpp
@@ -14,6 +14,7 @@
#include "Layers.h"
#include "mozilla/Maybe.h"
#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/Preferences.h"
#include "mozilla/Sprintf.h"
#include
@@ -127,6 +128,44 @@ void DOMEventMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
aWriter.StringProperty("eventType", NS_ConvertUTF16toUTF8(mEventType).get());
}
+static const char* PrefValueKindToString(
+ const mozilla::Maybe& aKind) {
+ if (aKind) {
+ return *aKind == PrefValueKind::Default ? "Default" : "User";
+ }
+ return "Shared";
+}
+
+static const char* PrefTypeToString(const mozilla::Maybe& type) {
+ if (type) {
+ switch (*type) {
+ case PrefType::None:
+ return "None";
+ case PrefType::Int:
+ return "Int";
+ case PrefType::Bool:
+ return "Bool";
+ case PrefType::String:
+ return "String";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown preference type.");
+ }
+ }
+ return "Preference not found";
+}
+
+void PrefMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
+ const TimeStamp& aProcessStartTime,
+ UniqueStacks& aUniqueStacks) {
+ StreamCommonProps("PreferenceRead", aWriter, aProcessStartTime,
+ aUniqueStacks);
+ WriteTime(aWriter, aProcessStartTime, mPrefAccessTime, "prefAccessTime");
+ aWriter.StringProperty("prefName", mPrefName.get());
+ aWriter.StringProperty("prefKind", PrefValueKindToString(mPrefKind));
+ aWriter.StringProperty("prefType", PrefTypeToString(mPrefType));
+ aWriter.StringProperty("prefValue", mPrefValue.get());
+}
+
void LayerTranslationMarkerPayload::StreamPayload(
SpliceableJSONWriter& aWriter, const TimeStamp& aProcessStartTime,
UniqueStacks& aUniqueStacks) {
diff --git a/tools/profiler/public/GeckoProfiler.h b/tools/profiler/public/GeckoProfiler.h
index 235eb5da9fb8..2395f8cb2a3e 100644
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -159,7 +159,10 @@ class Vector;
MACRO(12, "jstracer", JSTracer, "Enable tracing of the JavaScript engine") \
\
MACRO(13, "jsallocations", JSAllocations, \
- "Have the JavaScript engine track allocations")
+ "Have the JavaScript engine track allocations") \
+ \
+ MACRO(15, "preferencereads", PreferenceReads, \
+ "Track when preferences are read")
struct ProfilerFeature {
# define DECLARE(n_, str_, Name_, desc_) \
diff --git a/tools/profiler/public/ProfilerMarkerPayload.h b/tools/profiler/public/ProfilerMarkerPayload.h
index 098c5c3e11d8..7d655711b36a 100644
--- a/tools/profiler/public/ProfilerMarkerPayload.h
+++ b/tools/profiler/public/ProfilerMarkerPayload.h
@@ -12,6 +12,7 @@
#include "mozilla/RefPtr.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
+#include "mozilla/Preferences.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/net/TimingStruct.h"
@@ -149,6 +150,35 @@ class DOMEventMarkerPayload : public TracingMarkerPayload {
nsString mEventType;
};
+class PrefMarkerPayload : public ProfilerMarkerPayload {
+ public:
+ PrefMarkerPayload(const char* aPrefName,
+ const mozilla::Maybe& aPrefKind,
+ const mozilla::Maybe& aPrefType,
+ const nsCString& aPrefValue,
+ const mozilla::TimeStamp& aPrefAccessTime)
+ : ProfilerMarkerPayload(aPrefAccessTime, aPrefAccessTime),
+ mPrefAccessTime(aPrefAccessTime),
+ mPrefName(aPrefName),
+ mPrefKind(aPrefKind),
+ mPrefType(aPrefType),
+ mPrefValue(aPrefValue) {}
+
+ DECL_STREAM_PAYLOAD
+
+ private:
+ mozilla::TimeStamp mPrefAccessTime;
+ nsCString mPrefName;
+ // Nothing means this is a shared preference. Something, on the other hand,
+ // holds an actual PrefValueKind indicating either a Default or User
+ // preference.
+ mozilla::Maybe mPrefKind;
+ // Nothing means that the mPrefName preference was not found. Something
+ // contains the type of the preference.
+ mozilla::Maybe mPrefType;
+ nsCString mPrefValue;
+};
+
class UserTimingMarkerPayload : public ProfilerMarkerPayload {
public:
UserTimingMarkerPayload(const nsAString& aName,
diff --git a/tools/profiler/tests/browser/browser.ini b/tools/profiler/tests/browser/browser.ini
index c0e22d88e20d..87d70562423e 100644
--- a/tools/profiler/tests/browser/browser.ini
+++ b/tools/profiler/tests/browser/browser.ini
@@ -2,12 +2,14 @@
support-files =
head.js
do_work_500ms.html
+ fixed_height.html
multi_frame.html
single_frame.html
single_frame_pushstate.html
single_frame_replacestate.html
[browser_test_feature_jsallocations.js]
+[browser_test_feature_preferencereads.js]
[browser_test_profile_single_frame_page_info.js]
[browser_test_profile_multi_frame_page_info.js]
[browser_test_profile_pushstate_page_info.js]
diff --git a/tools/profiler/tests/browser/browser_test_feature_jsallocations.js b/tools/profiler/tests/browser/browser_test_feature_jsallocations.js
index b92ad761b8c2..56b3f77c723f 100644
--- a/tools/profiler/tests/browser/browser_test_feature_jsallocations.js
+++ b/tools/profiler/tests/browser/browser_test_feature_jsallocations.js
@@ -80,48 +80,3 @@ add_task(async function test_profile_feature_jsallocations() {
}
});
});
-
-/**
- * Markers are collected only after a periodic sample. This function ensures that
- * at least one periodic sample has been done.
- */
-async function doAtLeastOnePeriodicSample() {
- async function getProfileSampleCount() {
- const profile = await Services.profiler.getProfileDataAsync();
- return profile.threads[0].samples.data.length;
- }
-
- const sampleCount = await getProfileSampleCount();
- // Create an infinite loop until a sample has been collected.
- while (true) {
- if (sampleCount < (await getProfileSampleCount())) {
- return;
- }
- }
-}
-
-async function stopProfilerAndGetThreads(contentPid) {
- await doAtLeastOnePeriodicSample();
-
- const profile = await Services.profiler.getProfileDataAsync();
- Services.profiler.StopProfiler();
-
- const parentThread = profile.threads[0];
- const contentProcess = profile.processes.find(
- p => p.threads[0].pid == contentPid
- );
- if (!contentProcess) {
- throw new Error("Could not find the content process.");
- }
- const contentThread = contentProcess.threads[0];
-
- if (!parentThread) {
- throw new Error("The parent thread was not found in the profile.");
- }
-
- if (!contentThread) {
- throw new Error("The content thread was not found in the profile.");
- }
-
- return { parentThread, contentThread };
-}
diff --git a/tools/profiler/tests/browser/browser_test_feature_preferencereads.js b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
new file mode 100644
index 000000000000..7e5809712c0e
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
@@ -0,0 +1,81 @@
+/* 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 countDpiPrefReadsInThread(thread) {
+ let count = 0;
+ for (let payload of getPayloadsOfType(thread, "PreferenceRead")) {
+ if (payload.prefName === "layout.css.dpi") {
+ count++;
+ }
+ }
+ return count;
+}
+
+/**
+ * Test the PreferenceRead feature.
+ */
+add_task(async function test_profile_feature_preferencereads() {
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfiler({ features: ["threads", "preferencereads"] });
+
+ const url = BASE_URL + "fixed_height.html";
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await ContentTask.spawn(
+ contentBrowser,
+ null,
+ () => Services.appinfo.processID
+ );
+
+ // Wait 100ms so that the tab finishes executing.
+ await wait(100);
+
+ // Check that some PreferenceRead profile markers were generated when the
+ // feature is enabled.
+ {
+ const { contentThread } = await stopProfilerAndGetThreads(contentPid);
+
+ const timesReadDpiInContent = countDpiPrefReadsInThread(contentThread);
+
+ Assert.greater(
+ timesReadDpiInContent,
+ 0,
+ "PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+ }
+
+ startProfiler({ features: ["threads"] });
+ // Now reload the tab with a clean run.
+ gBrowser.reload();
+ await wait(100);
+
+ // Check that no PreferenceRead markers were recorded when the feature
+ // is turned off.
+ {
+ const { parentThread, contentThread } = await stopProfilerAndGetThreads(
+ contentPid
+ );
+ Assert.equal(
+ getPayloadsOfType(parentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+
+ Assert.equal(
+ getPayloadsOfType(contentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/fixed_height.html b/tools/profiler/tests/browser/fixed_height.html
new file mode 100644
index 000000000000..7d21f3b74632
--- /dev/null
+++ b/tools/profiler/tests/browser/fixed_height.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
Testing
+
+
diff --git a/tools/profiler/tests/browser/head.js b/tools/profiler/tests/browser/head.js
index 559d7c8d54f3..e8b65f5c2d95 100644
--- a/tools/profiler/tests/browser/head.js
+++ b/tools/profiler/tests/browser/head.js
@@ -21,6 +21,60 @@ function startProfiler(callersSettings) {
settings.duration
);
}
+/**
+ * This function spins on a while loop until at least one
+ * periodic sample is taken. Use this function to ensure
+ * that markers are properly collected for a test or that
+ * at least one sample in which we are interested is collected.
+ */
+async function doAtLeastOnePeriodicSample() {
+ async function getProfileSampleCount() {
+ const profile = await Services.profiler.getProfileDataAsync();
+ return profile.threads[0].samples.data.length;
+ }
+
+ const sampleCount = await getProfileSampleCount();
+ // Create an infinite loop until a sample has been collected.
+ while (true) {
+ if (sampleCount < (await getProfileSampleCount())) {
+ return;
+ }
+ }
+}
+
+/**
+ * This is a helper function that will stop the profiler of the browser
+ * running with PID contentPid. The profiler in that PID
+ * will not stop until there is at least one periodic sample taken, though.
+ *
+ * @param {number} contentPid
+ * @returns {Promise}
+ */
+async function stopProfilerAndGetThreads(contentPid) {
+ await doAtLeastOnePeriodicSample();
+
+ const profile = await Services.profiler.getProfileDataAsync();
+ Services.profiler.StopProfiler();
+
+ const parentThread = profile.threads[0];
+ const contentProcess = profile.processes.find(
+ p => p.threads[0].pid == contentPid
+ );
+ if (!contentProcess) {
+ throw new Error("Could not find the content process.");
+ }
+ const contentThread = contentProcess.threads[0];
+
+ if (!parentThread) {
+ throw new Error("The parent thread was not found in the profile.");
+ }
+
+ if (!contentThread) {
+ throw new Error("The content thread was not found in the profile.");
+ }
+
+ return { parentThread, contentThread };
+}
/**
* This is a helper function be able to run `await wait(500)`. Unfortunately this