зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1551313: Insert profiler markers when preferences are accessed. r=squib,gregtatum
Reviewers: squib, mstange, gregtatum Reviewed By: squib, gregtatum Subscribers: julienw, Fallen, reviewbot, mixedpuppy, mstange Bug #: 1551313 Differential Revision: https://phabricator.services.mozilla.com/D39796 --HG-- extra : rebase_source : e7e0b41b2a4c6f7228f3d6c19ede1fe2e4e42343 extra : histedit_source : 55daf5b19e4f262f522339b5b00ee31130abbf1b
This commit is contained in:
Родитель
584570ec40
Коммит
9e72ec1f8d
|
@ -181,6 +181,11 @@ const featureCheckboxes = [
|
|||
value: "jstracer",
|
||||
title: "Trace JS engine (Experimental, requires custom build.)",
|
||||
},
|
||||
{
|
||||
name: "Preference Read",
|
||||
value: "preferencereads",
|
||||
title: "Track Preference Reads",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -245,6 +245,7 @@ function intializeState() {
|
|||
tasktracer: false,
|
||||
trackopts: false,
|
||||
jstracer: false,
|
||||
preferencereads: false,
|
||||
jsallocations: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -168,7 +168,12 @@
|
|||
id="perf-settings-feature-checkbox-jsallocations" type="checkbox" value="jsallocations" />
|
||||
<div class="perf-settings-feature-name">JS Allocations</div>
|
||||
<div class="perf-settings-feature-title">Track JavaScript allocations (Experimental.)</div>
|
||||
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
|
||||
id="perf-settings-feature-checkbox-preferencereads" type="checkbox" value="preferencereads" />
|
||||
<div class="perf-settings-feature-name">Preference Reads</div>
|
||||
<div class="perf-settings-feature-title">Track Preference Reads</div>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -33,6 +33,7 @@ const features = [
|
|||
"tasktracer",
|
||||
"jstracer",
|
||||
"jsallocations",
|
||||
"preferencereads",
|
||||
"trackopts",
|
||||
];
|
||||
const threadPrefix = "perf-settings-thread-checkbox-";
|
||||
|
|
|
@ -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") \
|
||||
|
|
|
@ -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<nsZipArchive> 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 <typename T>
|
||||
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> prefType = Nothing();
|
||||
nsCString prefValue{""};
|
||||
|
||||
if (Maybe<PrefWrapper> pref = pref_Lookup(aPrefName)) {
|
||||
return pref->GetValue(aKind, std::forward<T>(aResult));
|
||||
rv = pref->GetValue(aKind, std::forward<T>(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<PrefMarkerPayload>(aPrefName, Some(aKind), prefType,
|
||||
prefValue, prefAccessTime));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static nsresult GetSharedPrefValue(const char* aName, T* aResult) {
|
||||
nsresult rv = NS_ERROR_UNEXPECTED;
|
||||
|
||||
TimeStamp prefAccessTime = TimeStamp::Now();
|
||||
Maybe<PrefType> prefType = Nothing();
|
||||
nsCString prefValue{""};
|
||||
|
||||
if (Maybe<PrefWrapper> 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<PrefMarkerPayload>(aName, Nothing() /* indicates Shared */,
|
||||
prefType, prefValue, prefAccessTime));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
"threads",
|
||||
"trackopts",
|
||||
"jstracer",
|
||||
"jsallocations"
|
||||
"jsallocations",
|
||||
"preferencereads"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 <inttypes.h>
|
||||
|
@ -127,6 +128,44 @@ void DOMEventMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
|
|||
aWriter.StringProperty("eventType", NS_ConvertUTF16toUTF8(mEventType).get());
|
||||
}
|
||||
|
||||
static const char* PrefValueKindToString(
|
||||
const mozilla::Maybe<PrefValueKind>& aKind) {
|
||||
if (aKind) {
|
||||
return *aKind == PrefValueKind::Default ? "Default" : "User";
|
||||
}
|
||||
return "Shared";
|
||||
}
|
||||
|
||||
static const char* PrefTypeToString(const mozilla::Maybe<PrefType>& 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) {
|
||||
|
|
|
@ -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_) \
|
||||
|
|
|
@ -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<mozilla::PrefValueKind>& aPrefKind,
|
||||
const mozilla::Maybe<mozilla::PrefType>& 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<mozilla::PrefValueKind> mPrefKind;
|
||||
// Nothing means that the mPrefName preference was not found. Something
|
||||
// contains the type of the preference.
|
||||
mozilla::Maybe<mozilla::PrefType> mPrefType;
|
||||
nsCString mPrefValue;
|
||||
};
|
||||
|
||||
class UserTimingMarkerPayload : public ProfilerMarkerPayload {
|
||||
public:
|
||||
UserTimingMarkerPayload(const nsAString& aName,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<!--
|
||||
Using an absolute value here should invoke Firefox to get
|
||||
the layout.css.dpi preference.
|
||||
-->
|
||||
<style type="text/css">
|
||||
div.fixed_height {
|
||||
height: 15in;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="fixed_height">Testing</div>
|
||||
</body>
|
||||
</html>
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче