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:
Will Hawkins 2019-08-23 13:12:51 +03:00
Родитель 584570ec40
Коммит 9e72ec1f8d
15 изменённых файлов: 298 добавлений и 51 удалений

Просмотреть файл

@ -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