Backed out changeset e4a4b630e627 (bug 1897914) for causing build bustages in platform.cpp CLOSED TREE

This commit is contained in:
Cristian Tuns 2024-07-09 21:44:22 -04:00
Родитель 291613e267
Коммит 02e472a9ac
9 изменённых файлов: 96 добавлений и 423 удалений

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

@ -45,12 +45,10 @@
#include "ProfilerStackWalk.h"
#include "ProfilerRustBindings.h"
#include "mozilla/Assertions.h"
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsISupports.h"
#include "nsXPCOM.h"
#include "shared-libraries.h"
#include "VTuneProfiler.h"
@ -117,7 +115,6 @@
#include <sstream>
#include <string_view>
#include <type_traits>
#include <pthread.h>
// To simplify other code in this file, define a helper definition to avoid
// repeating the same preprocessor checks.
@ -767,7 +764,6 @@ class AsyncSignalControlThread {
};
static void* AsyncSignalControlThreadEntry(void* aArg) {
NS_SetCurrentThreadName("AsyncSignalControlThread");
auto* thread = static_cast<AsyncSignalControlThread*>(aArg);
thread->Watch();
return nullptr;
@ -5698,113 +5694,39 @@ Maybe<nsAutoCString> profiler_find_dump_path() {
#endif
}
void profiler_dump_and_stop() {
// Do nothing unless we're the parent process, as we're sandboxed and can't
// write anyway.
if (XRE_IsParentProcess()) {
// pause the profiler until we are done dumping
profiler_pause();
// Try to save the profile to a file
if (auto path = profiler_find_dump_path()) {
profiler_save_profile_to_file(path.value().get());
} else {
LOG("Failed to dump profile to disk");
}
// Stop the profiler
profiler_stop();
}
}
void profiler_start_from_signal() {
// Do nothing unless we're the parent process, as we're sandboxed and can't
// write any data that we gather anyway.
if (XRE_IsParentProcess()) {
// Start the profiler here directly, as we're on a background thread.
// set of preferences, configuration of them is TODO, see Bug 1866007
// Enabling the JS feature leaks an 8-byte object during testing, but is too
// useful to disable. See Bug 1904897, Bug 1699681, and browser.toml for
// more details.
uint32_t features = ProfilerFeature::JS | ProfilerFeature::StackWalk |
ProfilerFeature::CPUUtilization;
// as we often don't know what threads we'll care about, tell the
// profiler to profile all threads.
const char* filters[] = {"*"};
if (MOZ_UNLIKELY(NS_IsMainThread())) {
// We are on the main thread here, so `NotifyProfilerStarted` will
// start the profiler in content/child processes.
profiler_start(PROFILER_DEFAULT_SIGHANDLE_ENTRIES,
PROFILER_DEFAULT_INTERVAL, features, filters,
MOZ_ARRAY_LENGTH(filters), 0);
} else {
// Directly start the profiler on this thread. We know we're not the main
// thread here, so this will not start the profiler in child processes,
// but we want to make sure that we do it here in case the main thread is
// stuck.
profiler_start(PROFILER_DEFAULT_SIGHANDLE_ENTRIES,
PROFILER_DEFAULT_INTERVAL, features, filters,
MOZ_ARRAY_LENGTH(filters), 0);
// Now also try and start the profiler from the main thread, so that the
// ParentProfiler will start child threads.
NS_DispatchToMainThread(
NS_NewRunnableFunction("StartProfilerInChildProcesses", [=] {
Unused << NotifyProfilerStarted(PROFILER_DEFAULT_SIGHANDLE_ENTRIES,
Nothing(),
PROFILER_DEFAULT_INTERVAL, features,
const_cast<const char**>(filters),
MOZ_ARRAY_LENGTH(filters), 0);
}));
}
}
}
void profiler_dump_and_stop() {
// Do nothing unless we're the parent process, as we're sandboxed and can't
// open a file handle anyway.
if (!XRE_IsParentProcess()) {
return;
}
// pause the profiler until we are done dumping
profiler_pause();
// Try to save the profile to a file
auto path = profiler_find_dump_path();
// Exit quickly if we can't find the path, while stopping the profiler
if (!path) {
LOG("Failed to find a valid dump path to write profile to disk");
profiler_stop();
return;
}
// Dump the profile of this process first, in case the multi-process
// gathering is unsuccessful (e.g. due to a blocked main threaed).
profiler_save_profile_to_file(path.value().get());
// We are probably not the main thread, but check anyway, and dispatch
// directly.
if (NS_IsMainThread()) {
nsCOMPtr<nsIProfiler> nsProfiler(
do_GetService("@mozilla.org/tools/profiler;1"));
nsProfiler->DumpProfileToFileAsyncNoJs(path.value(), 0)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[](void_t ok) {
LOG("Stopping profiler after dumping profile to disk");
profiler_stop();
},
[](nsresult aRv) {
LOG("Dumping to disk failed with error \"%s\", stopping "
"profiler.",
GetStaticErrorName(aRv));
profiler_stop();
});
} else {
// Dispatch a runnable, as nsProfiler classes are currently main-thread
// only. We also stop the profiler within the runnable, as otherwise we
// may find ourselves stopping the profiler before the runnable has
// gathered all the profile data.
NS_DispatchToMainThread(
NS_NewRunnableFunction("WriteProfileDataToFile", [=] {
nsCOMPtr<nsIProfiler> nsProfiler(
do_GetService("@mozilla.org/tools/profiler;1"));
nsProfiler->DumpProfileToFileAsyncNoJs(path.value(), 0)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[](void_t ok) {
LOG("Stopping profiler after dumping profile to disk");
profiler_stop();
},
[](nsresult aRv) {
LOG("Dumping to disk failed with error \"%s\", stopping "
"profiler.",
GetStaticErrorName(aRv));
profiler_stop();
});
}));
profiler_start(PROFILER_DEFAULT_SIGHANDLE_ENTRIES,
PROFILER_DEFAULT_INTERVAL, features, filters,
MOZ_ARRAY_LENGTH(filters), 0);
}
}

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

@ -16,8 +16,6 @@
[ref] native StringArrayRef(const nsTArray<nsCString>);
native ProfileDataBufferMozPromise(RefPtr<mozilla::MozPromise<FallibleTArray<uint8_t>, nsresult, true>>);
native ProfileFileDumpMozPromise(RefPtr<mozilla::MozPromise<mozilla::void_t, nsresult, true>>);
/**
* Start-up parameters for subprocesses are passed through nsIObserverService,
* which, unfortunately, means we need to implement nsISupports in order to
@ -108,15 +106,6 @@ interface nsIProfiler : nsISupports
Promise dumpProfileToFileAsync(in ACString aFilename,
[optional] in double aSinceTime);
/**
* Asynchronously dump the profile collected so far to a file. This is
* essentially the same as `dumpProfileToFileAsync`, but rather than returning
* a javascript Promise, it instead returns a MozPromise, meaning that we do
* not require a js context.
*/
[notxpcom, nostdcall] ProfileFileDumpMozPromise dumpProfileToFileAsyncNoJs(in ACString aFilename, in double aSinceTime);
/**
* Synchronously dump the profile collected so far in this process to a file.
* This profile will only contain data from the parent process, and from child

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

@ -668,44 +668,6 @@ nsProfiler::DumpProfileToFileAsync(const nsACString& aFilename,
return NS_OK;
}
RefPtr<nsProfiler::GatheringPromiseFileDump>
nsProfiler::DumpProfileToFileAsyncNoJs(const nsACString& aFilename,
double aSinceTime) {
if (!profiler_is_active()) {
return GatheringPromiseFileDump::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
nsCString filename(aFilename);
return StartGathering(aSinceTime)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[filename](const mozilla::ProfileAndAdditionalInformation& aResult) {
if (aResult.mProfile.Length() >=
size_t(std::numeric_limits<std::streamsize>::max())) {
return GatheringPromiseFileDump::CreateAndReject(
NS_ERROR_FILE_TOO_BIG, __func__);
}
std::ofstream stream;
stream.open(filename.get());
if (!stream.is_open()) {
return GatheringPromiseFileDump::CreateAndReject(
NS_ERROR_FILE_UNRECOGNIZED_PATH, __func__);
}
stream.write(aResult.mProfile.get(),
std::streamsize(aResult.mProfile.Length()));
stream.close();
return GatheringPromiseFileDump::CreateAndResolve(void_t(),
__func__);
},
[](nsresult aRv) {
return GatheringPromiseFileDump::CreateAndReject(aRv, __func__);
});
}
NS_IMETHODIMP
nsProfiler::GetSymbolTable(const nsACString& aDebugPath,
const nsACString& aBreakpadID, JSContext* aCx,

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

@ -7,7 +7,6 @@
#ifndef nsProfiler_h
#define nsProfiler_h
#include "ErrorList.h"
#include "base/process.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
@ -17,7 +16,6 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/ipc/IPCCore.h"
#include "nsIProfiler.h"
#include "nsITimer.h"
#include "nsServiceManagerUtils.h"
@ -48,8 +46,6 @@ class nsProfiler final : public nsIProfiler {
using GatheringPromiseAndroid =
mozilla::MozPromise<FallibleTArray<uint8_t>, nsresult, true>;
using GatheringPromiseFileDump =
mozilla::MozPromise<mozilla::void_t, nsresult, true>;
using GatheringPromise =
mozilla::MozPromise<mozilla::ProfileAndAdditionalInformation, nsresult,
false>;

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

@ -11,20 +11,6 @@ support-files = ["simple.html"]
["browser_test_feature_jsallocations.js"]
support-files = ["do_work_500ms.html"]
["browser_test_feature_multiprocess_capture_with_signal.js"]
support-files = ["do_work_500ms.html"]
skip-if = [
# Enabling the JS feature leaks an 8-byte object. This causes failures when
# the leak checker is enabled, and run with test-verify (i.e. with both
# "debug" and "verify"). Having JS support for this feature is more important
# than these tests, so we're disabling them for now. See Bug 1904897.
"verify && debug",
"tsan", # We have intermittent timeout issues in TSan, see Bug 1889828
"ccov", # The signals for the profiler conflict with the ccov signals
"os == 'win'", # Not yet supported on windows - Bug 1867328
"os == 'android'", # Not yet supported on android - Bug 1904639
]
["browser_test_feature_nostacksampling.js"]
support-files = ["do_work_500ms.html"]

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

@ -1,182 +0,0 @@
/* 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/. */
requestLongerTimeout(10);
async function get_profile_path_on_disk(pid) {
// Get the system downloads directory, and use it to build a profile file
let profile = FileUtils.File(await Downloads.getSystemDownloadsDirectory());
// use the pid to construct the name of the profile, and resulting file
profile.append(`profile_0_${pid}.json`);
return profile;
}
function check_profile_contains_parent_and_content_pids(
parent_pid,
content_pid,
profile
) {
info(
`Checking that the profile contains pids for the parent process ${parent_pid} and content process ${content_pid}`
);
Assert.equal(
profile.threads[0].pid,
parent_pid,
"We expect the pid of the main profile thread to be the parent pid"
);
// Keep a record of the pids found in the profile, so that we can give a
// better error message.
const child_pids = [];
let found = false;
for (const process of profile.processes) {
child_pids.push(process.threads[0].pid);
if (process.threads[0].pid === content_pid) {
found = true;
info(`Found content pid: ${process.threads[0].pid}.`);
}
}
Assert.ok(
found,
`We expect the child process ids to contain the id of the content process (${content_pid}). Actually found: ${child_pids}`
);
}
function check_profile_for_synthetic_marker(profile) {
// Essentially the same test as `browser_test_markers_parent_process.js`
const markers = getInflatedMarkerData(profile.threads[0]);
{
const domEventStart = markers.find(
({ phase, data }) =>
phase === INTERVAL_START && data?.eventType === "synthetic"
);
const domEventEnd = markers.find(
({ phase, data }) =>
phase === INTERVAL_END && data?.eventType === "synthetic"
);
ok(domEventStart, "A start DOMEvent was generated");
ok(domEventEnd, "An end DOMEvent was generated");
Assert.greater(
domEventEnd.data.latency,
0,
"DOMEvent had a a latency value generated."
);
Assert.strictEqual(domEventEnd.data.type, "DOMEvent");
Assert.strictEqual(domEventEnd.name, "DOMEvent");
}
}
// Test signal handling within the profiler: Start the profiler with a POSIX
// signal, and capture a profile with a POSIX signal. Along the way, record a
// marker from a synthetic dom event. Check that the marker shows up in the
// final profile, and that the processes that we expect to see are also there.
//
// We would ideally like to have three tests for the three following scenarios:
//
// 1) Starting the profiler normally, and stopping it with a signal,
// 2) Starting the profiler with a signal, and stopping it normally
// 3) Both starting and stopping the profiler with a signal
//
// That way, if any one of the three tests fails, we can quickly isolate which
// part of the signal-handling code has failed. If (1 & 3) fail, then it's the
// stopping code, if (2 & 3) fail, it's the starting code, and if just (3)
// fails, it's something else entirely. However, this would use up a lot of time
// in CI, so instead we just have test (3). This can be easily modified to act
// like (1) or (2) when debugging.
//
// - To make this test act like (1), replace the the call to
// `raiseSignal(ppid.pid, SIGUSR1)` with (e.g.) `await startProfiler({
// features: [""], threads: ["GeckoMain"] });`
// - To make this test act like (2), replace the call to `raiseSignal(ppid.pid,
// SIGUSR2)` etc with (e.g.) `const profile = await stopNowAndGetProfile();`
//
add_task(
async function test_profile_feature_multiprocess_start_and_capture_with_signal() {
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
let ppid = await ChromeUtils.requestProcInfo();
let parent_pid = ppid.pid;
let startPromise = TestUtils.topicObserved("profiler-started");
info(`Raising signal SIGUSR1 with pid ${parent_pid} to start the profiler`);
// Try and start the profiler using a signal.
let result = raiseSignal(parent_pid, SIGUSR1);
Assert.ok(result, "Raising a signal should succeed");
// Wait for the profiler to stop
Assert.ok(await startPromise, "The profiler should start");
// Wait until the profiler is active
Assert.ok(
Services.profiler.IsActive(),
"The profiler should now be active."
);
const url = BASE_URL + "do_work_500ms.html";
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
info("Finding the PId of the content process.");
const content_pid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
// Dispatch a synthetic event so that we can search for the marker in the
// profile
info("Dispatching a synthetic DOMEvent");
window.dispatchEvent(new Event("synthetic"));
// Wait 500ms so that the tab finishes executing.
info("Waiting for the tab to do some work");
await wait(500);
// Set up an observer to watch for the profiler stopping
let stopPromise = TestUtils.topicObserved("profiler-stopped");
// Try and stop the profiler using a signal.
info(
`Raising signal SIGUSR2 with pid ${parent_pid} to stop the profiler`
);
let result = raiseSignal(parent_pid, SIGUSR2);
Assert.ok(result, "Raising a SIGUSR2 signal should succeed.");
// Wait for the profiler to stop
info(`Waiting for the profiler to stop.`);
Assert.ok(await stopPromise, "The profiler should stop");
// Check that we have a profile written to disk:
info(`Retrieving profile file.`);
let profile_file = await get_profile_path_on_disk(parent_pid);
Assert.ok(
await IOUtils.exists(profile_file.path),
"A profile file should be written to disk."
);
// Read the profile from the json file
let profile = await IOUtils.readJSON(profile_file.path);
info("Found this many proceses: " + profile.processes.length);
// check for processes and the synthetic marker
info(
`Checking that the profile file contains the parent and content processes.`
);
check_profile_contains_parent_and_content_pids(
parent_pid,
content_pid,
profile
);
info(`Checking for the synthetic DOM marker`);
check_profile_for_synthetic_marker(profile);
});
}
);

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

@ -389,78 +389,6 @@ function escapeStringRegexp(string) {
return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
}
/** ------ Utility functions and definitions for raising POSIX signals ------ */
// Hardcode the constants SIGUSR1 and SIGUSR2.
// This is an absolutely terrible idea, as they are implementation defined!
// However, it turns out that for 99% of the platforms we care about, and for
// 99.999% of the platforms we test, these constants are, well, constant.
// Additionally, these constants are only for _testing_ the signal handling
// feature - the actual feature relies on platform specific definitions. This
// may cause a mismatch if we test on on, say, a gnu hurd kernel, or on a
// linux kernel running on sparc, but the feature will not break - only
// the testing.
const SIGUSR1 = Services.appinfo.OS === "Darwin" ? 30 : 10;
const SIGUSR2 = Services.appinfo.OS === "Darwin" ? 31 : 12;
// Derived heavily from equivalent sandbox testing code. For more details see:
// https://searchfox.org/mozilla-central/rev/1aaacaeb4fa3aca6837ecc157e43e947229ba8ce/security/sandbox/test/browser_content_sandbox_utils.js#89
function raiseSignal(pid, sig) {
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
// Derived from functionality in js/src/devtools/rootAnalysis/utility.js
function openLibrary(names) {
for (const name of names) {
try {
return ctypes.open(name);
} catch (e) {}
}
return undefined;
}
try {
const libc = openLibrary([
"libc.so.6",
"libc.so",
"libc.dylib",
"libSystem.B.dylib",
]);
if (!libc) {
info("Failed to open any libc shared object");
return { ok: false };
}
// c.f. https://man7.org/linux/man-pages/man2/kill.2.html
// This choice of typing for `pid` is complex, and brittle, as it's platform
// dependent. Getting it wrong can result in incoreect generation/calling of
// the `kill` function. Unfortunately, as it's defined as `pid_t` in a
// header, we can't easily get access to it. For now, we just use an
// integer, and hope that the system int size aligns with the `pid_t` size.
const kill = libc.declare(
"kill",
ctypes.default_abi,
ctypes.int, // return value
ctypes.int32_t, // pid
ctypes.int // sig
);
let kres = kill(pid, sig);
if (kres != 0) {
info(`Kill returned a non-zero result ${kres}.`);
return { ok: false };
}
libc.close();
} catch (e) {
info(`Exception ${e} thrown while trying to call kill`);
return { ok: false };
}
return { ok: true };
}
/** ------ Assertions helper ------ */
/**
* This assert helper function makes it easy to check a lot of properties in an

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

@ -8,6 +8,66 @@ ChromeUtils.defineESModuleGetters(this, {
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
});
const { ctypes } = ChromeUtils.importESModule(
"resource://gre/modules/ctypes.sys.mjs"
);
// Derived from functionality in js/src/devtools/rootAnalysis/utility.js
function openLibrary(names) {
for (const name of names) {
try {
return ctypes.open(name);
} catch (e) {}
}
return undefined;
}
// Derived heavily from equivalent sandbox testing code.
// For more details see:
// https://searchfox.org/mozilla-central/rev/1aaacaeb4fa3aca6837ecc157e43e947229ba8ce/security/sandbox/test/browser_content_sandbox_utils.js#89
function raiseSignal(pid, sig) {
try {
const libc = openLibrary([
"libc.so.6",
"libc.so",
"libc.dylib",
"libSystem.B.dylib",
]);
if (!libc) {
info("Failed to open any libc shared object");
return { ok: false };
}
// c.f. https://man7.org/linux/man-pages/man2/kill.2.html
// This choice of typing for `pid` is complex, and brittle, as it's
// platform dependent. Getting it wrong can result in incoreect
// generation/calling of the `kill` function. Unfortunately, as it's
// defined as `pid_t` in a header, we can't easily get access to it.
// For now, we just use an integer, and hope that the system int size
// aligns with the `pid_t` size.
const kill = libc.declare(
"kill",
ctypes.default_abi,
ctypes.int, // return value
ctypes.int32_t, // pid
ctypes.int // sig
);
let kres = kill(pid, sig);
if (kres != 0) {
info(`Kill returned a non-zero result ${kres}.`);
return { ok: false };
}
libc.close();
} catch (e) {
info(`Exception ${e} thrown while trying to call kill`);
return { ok: false };
}
return { ok: true };
}
async function cleanupAfterTest() {
// We need to cleanup written profiles after a test
// Get the system downloads directory, and use it to build a profile file
@ -26,6 +86,18 @@ async function cleanupAfterTest() {
await Services.profiler.StopProfiler();
}
// Hardcode the constants SIGUSR1 and SIGUSR2.
// This is an absolutely terrible idea, as they are implementation defined!
// However, it turns out that for 99% of the platforms we care about, and for
// 99.999% of the platforms we test, these constants are, well, constant.
// Additionally, these constants are only for _testing_ the signal handling
// feature - the actual feature relies on platform specific definitions. This
// may cause a mismatch if we test on on, say, a gnu hurd kernel, or on a
// linux kernel running on sparc, but the feature will not break - only
// the testing.
const SIGUSR1 = Services.appinfo.OS === "Darwin" ? 30 : 10;
const SIGUSR2 = Services.appinfo.OS === "Darwin" ? 31 : 12;
add_task(async () => {
info("Test that starting the profiler with a posix signal works.");

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

@ -51,8 +51,8 @@ skip-if = [
skip-if = [
"tsan", # We have intermittent timeout issues in TSan, see Bug 1889828
"ccov", # The signals for the profiler conflict with the ccov signals
"os == 'win'", # Not yet supported on windows - Bug 1867328
"os == 'android'", # Not yet supported on android - Bug 1904639
"os == 'win'", # Not yet supported on windows
"os == 'android'", # Not yet supported on android
]
# Native stackwalking is somewhat unreliable depending on the platform.