Bug 1581963 - [devtools] Handle errors properly when capturing a profile r=profiler-reviewers,mstange

Differential Revision: https://phabricator.services.mozilla.com/D245886
This commit is contained in:
Julien Wajsberg
2025-05-22 16:26:42 +00:00
committed by jwajsberg@mozilla.com
parent 2417113533
commit 29c9a8dba5
5 changed files with 61 additions and 11 deletions

View File

@@ -215,7 +215,8 @@ export interface SymbolicationService {
* profile has been obtained. * profile has been obtained.
*/ */
export type OnProfileReceived = ( export type OnProfileReceived = (
profileAndAdditionalInformation: ProfileAndAdditionalInformation profileAndAdditionalInformation: ProfileAndAdditionalInformation | null,
error?: Error | string
) => void; ) => void;
/** /**
@@ -643,7 +644,7 @@ export class ProfilerWebChannel {
*/ */
export type ProfilerBrowserInfo = { export type ProfilerBrowserInfo = {
profileCaptureResult: ProfileCaptureResult; profileCaptureResult: ProfileCaptureResult;
symbolicationService: SymbolicationService; symbolicationService: SymbolicationService | null;
}; };
export type ProfileCaptureResult = export type ProfileCaptureResult =

View File

@@ -75,9 +75,14 @@ class RecordingButton extends PureComponent {
_onCaptureButtonClick = async () => { _onCaptureButtonClick = async () => {
const { getProfileAndStopProfiler, onProfileReceived, perfFront } = const { getProfileAndStopProfiler, onProfileReceived, perfFront } =
this.props; this.props;
try {
const profileAndAdditionalInformation = const profileAndAdditionalInformation =
await getProfileAndStopProfiler(perfFront); await getProfileAndStopProfiler(perfFront);
onProfileReceived(profileAndAdditionalInformation); onProfileReceived(profileAndAdditionalInformation);
} catch (e) {
const assertedError = /** @type {Error | string} */ (e);
onProfileReceived(null, assertedError);
}
}; };
_onStopButtonClick = () => { _onStopButtonClick = () => {

View File

@@ -131,11 +131,31 @@ async function gInit(perfFront, traits, pageContext, openAboutProfiling) {
); );
/** /**
* @param {MockedExports.ProfileAndAdditionalInformation} profileAndAdditionalInformation * @param {MockedExports.ProfileAndAdditionalInformation | null} profileAndAdditionalInformation
* @param {Error | string} [error]
*/ */
const onProfileReceived = async ({ profile, additionalInformation }) => { const onProfileReceived = async (profileAndAdditionalInformation, error) => {
const objdirs = selectors.getObjdirs(store.getState()); const objdirs = selectors.getObjdirs(store.getState());
const profilerViewMode = getProfilerViewModeForCurrentPreset(pageContext); const profilerViewMode = getProfilerViewModeForCurrentPreset(pageContext);
const browser = await openProfilerTab({ profilerViewMode });
if (error || !profileAndAdditionalInformation) {
if (!error) {
error =
"No profile data has been passed to onProfileReceived, and no specific error has been specified. This is unexpected.";
}
/**
* @type {ProfileCaptureResult}
*/
const profileCaptureResult = {
type: "ERROR",
error: typeof error === "string" ? new Error(error) : error,
};
registerProfileCaptureForBrowser(browser, profileCaptureResult, null);
return;
}
const { profile, additionalInformation } = profileAndAdditionalInformation;
const sharedLibraries = additionalInformation?.sharedLibraries ?? []; const sharedLibraries = additionalInformation?.sharedLibraries ?? [];
if (!sharedLibraries.length) { if (!sharedLibraries.length) {
console.error( console.error(
@@ -147,7 +167,6 @@ async function gInit(perfFront, traits, pageContext, openAboutProfiling) {
objdirs, objdirs,
perfFront perfFront
); );
const browser = await openProfilerTab({ profilerViewMode });
/** /**
* @type {ProfileCaptureResult} * @type {ProfileCaptureResult}

View File

@@ -289,11 +289,17 @@ async function getResponseForMessage(request, browser) {
case "GET_SYMBOL_TABLE": { case "GET_SYMBOL_TABLE": {
const { debugName, breakpadId } = request; const { debugName, breakpadId } = request;
const symbolicationService = getSymbolicationServiceForBrowser(browser); const symbolicationService = getSymbolicationServiceForBrowser(browser);
if (!symbolicationService) {
throw new Error("No symbolication service has been found for this tab");
}
return symbolicationService.getSymbolTable(debugName, breakpadId); return symbolicationService.getSymbolTable(debugName, breakpadId);
} }
case "QUERY_SYMBOLICATION_API": { case "QUERY_SYMBOLICATION_API": {
const { path, requestJson } = request; const { path, requestJson } = request;
const symbolicationService = getSymbolicationServiceForBrowser(browser); const symbolicationService = getSymbolicationServiceForBrowser(browser);
if (!symbolicationService) {
throw new Error("No symbolication service has been found for this tab");
}
return symbolicationService.querySymbolicationApi(path, requestJson); return symbolicationService.querySymbolicationApi(path, requestJson);
} }
case "GET_EXTERNAL_POWER_TRACKS": { case "GET_EXTERNAL_POWER_TRACKS": {
@@ -351,7 +357,7 @@ async function getResponseForMessage(request, browser) {
* tab, or a fallback service for browsers from tabs opened by the user. * tab, or a fallback service for browsers from tabs opened by the user.
* *
* @param {MockedExports.Browser} browser * @param {MockedExports.Browser} browser
* @return {SymbolicationService} * @return {SymbolicationService | null}
*/ */
function getSymbolicationServiceForBrowser(browser) { function getSymbolicationServiceForBrowser(browser) {
// We try to serve symbolication requests that come from tabs that we // We try to serve symbolication requests that come from tabs that we
@@ -429,7 +435,7 @@ export async function handleWebChannelMessage(channel, id, message, target) {
/** /**
* @param {MockedExports.Browser} browser - The tab's browser. * @param {MockedExports.Browser} browser - The tab's browser.
* @param {ProfileCaptureResult} profileCaptureResult - The Gecko profile. * @param {ProfileCaptureResult} profileCaptureResult - The Gecko profile.
* @param {SymbolicationService} symbolicationService - An object which implements the * @param {SymbolicationService | null} symbolicationService - An object which implements the
* SymbolicationService interface, whose getSymbolTable method will be invoked * SymbolicationService interface, whose getSymbolTable method will be invoked
* when profiler.firefox.com sends GET_SYMBOL_TABLE WebChannel messages to us. This * when profiler.firefox.com sends GET_SYMBOL_TABLE WebChannel messages to us. This
* method should obtain a symbol table for the requested binary and resolve the * method should obtain a symbol table for the requested binary and resolve the

View File

@@ -78,7 +78,26 @@ add_task(async function () {
"The profiler was stopped and the profile discarded." "The profiler was stopped and the profile discarded."
); );
// Clean up. await front.destroy();
await client.close();
});
add_task(async function test_error_case() {
const { front, client } = await initPerfFront();
try {
// We try to get the profile without starting the profiler first. This should
// trigger an error in the our C++ code.
await front.getProfileAndStopProfiler();
ok(false, "Getting the profile should fail");
} catch (e) {
Assert.stringContains(
e.message,
"The profiler is not active.",
"The error contains the expected error message."
);
}
await front.destroy(); await front.destroy();
await client.close(); await client.close();
}); });