Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions profiler-cli/src/commands/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ export function registerFunctionCommand(
)
.option(
'--symbol-server <url>',
'Symbol server URL for asm mode (default: http://localhost:3000)',
'http://localhost:3000'
'Symbol server URL for asm mode. Defaults to the ?symbolServer= value from the loaded URL, or the Mozilla symbol server.'
)
.option(
'--context <context>',
Expand Down
2 changes: 1 addition & 1 deletion profiler-cli/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ export class Daemon {
return this.querier!.functionAnnotate(
command.function,
command.annotateMode ?? 'src',
command.symbolServerUrl ?? 'http://localhost:3000',
command.symbolServerUrl,
command.annotateContext ?? '2'
);
default:
Expand Down
11 changes: 9 additions & 2 deletions src/profile-query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getTransformStack,
getCurrentSearchString,
getProfileSpecificState,
getSymbolServerUrl,
} from 'firefox-profiler/selectors/url-state';
import {
commitRange,
Expand Down Expand Up @@ -1140,20 +1141,26 @@ export class ProfileQuerier {

/**
* Annotate a function with per-line source or per-instruction assembly timing data.
*
* If `symbolServerUrl` is omitted, falls back to the symbol server resolved
* from the loaded profile's URL state (the ?symbolServer= query parameter,
* or the default Mozilla server when none was set).
*/
async functionAnnotate(
functionHandle: string,
mode: AnnotateMode,
symbolServerUrl: string,
symbolServerUrl: string | undefined,
contextOption: string = '2'
): Promise<WithContext<FunctionAnnotateResult>> {
const resolvedSymbolServerUrl =
symbolServerUrl ?? getSymbolServerUrl(this._store.getState());
const result = await computeFunctionAnnotate(
this._store,
this._threadMap,
this._archiveCache,
functionHandle,
mode,
symbolServerUrl,
resolvedSymbolServerUrl,
contextOption
);
return { ...result, context: this._getContext() };
Expand Down
60 changes: 60 additions & 0 deletions src/test/unit/profile-query/profile-querier-annotate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ProfileQuerier } from 'firefox-profiler/profile-query';
import { getProfileFromTextSamples } from '../../fixtures/profiles/processed-profile';
import { getProfileRootRange } from 'firefox-profiler/selectors/profile';
import { storeWithProfile } from '../../fixtures/stores';
import { updateUrlState } from 'firefox-profiler/actions/app';
import { getUrlState } from 'firefox-profiler/selectors/url-state';

jest.mock('firefox-profiler/utils/fetch-source');
jest.mock('firefox-profiler/utils/fetch-assembly');
Expand Down Expand Up @@ -45,6 +47,64 @@ describe('ProfileQuerier.functionAnnotate', function () {
fetchSource.mockResolvedValue({ type: 'ERROR', errors: [] });
});

describe('symbol server URL resolution', function () {
it('falls back to URL-state symbolServerUrl when not explicitly provided', async function () {
const { profile, funcNamesDictPerThread } = getProfileFromTextSamples(`
A[file:f.c][line:10]
`);

const store = storeWithProfile(profile);
const customSymbolServer = 'http://127.0.0.1:3000/some-samply-prefix';
store.dispatch(
updateUrlState({
...getUrlState(store.getState()),
symbolServerUrl: customSymbolServer,
})
);

const querier = new ProfileQuerier(
store,
getProfileRootRange(store.getState())
);
await querier.functionAnnotate(
funcHandle(funcNamesDictPerThread, 'A'),
'src',
undefined
);

expect(fetchSource).toHaveBeenCalledTimes(1);
expect(fetchSource.mock.calls[0][2]).toBe(customSymbolServer);
});

it('explicit symbolServerUrl argument overrides URL-state value', async function () {
const { profile, funcNamesDictPerThread } = getProfileFromTextSamples(`
A[file:f.c][line:10]
`);

const store = storeWithProfile(profile);
store.dispatch(
updateUrlState({
...getUrlState(store.getState()),
symbolServerUrl: 'http://127.0.0.1:3000/from-url-state',
})
);

const querier = new ProfileQuerier(
store,
getProfileRootRange(store.getState())
);
const explicitUrl = 'http://127.0.0.1:9999/explicit-override';
await querier.functionAnnotate(
funcHandle(funcNamesDictPerThread, 'A'),
'src',
explicitUrl
);

expect(fetchSource).toHaveBeenCalledTimes(1);
expect(fetchSource.mock.calls[0][2]).toBe(explicitUrl);
});
});

describe('aggregate self/total sample counts', function () {
it('counts self when function is the only frame (root = leaf)', async function () {
// Single-row samples: A is simultaneously root and leaf in all 3 samples.
Expand Down
Loading