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
2 changes: 1 addition & 1 deletion profiler-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ profiler-cli thread markers # List markers with aggregated statis
profiler-cli thread functions # List all functions with CPU percentages
profiler-cli thread network # Show network requests with timing phases [--search] [--min-duration] [--max-duration] [--limit] [--sort]
profiler-cli thread page-load # Show page load summary (navigation timing, resources, CPU, jank)
profiler-cli marker info <handle> # Show detailed marker information (e.g., m-1234)
profiler-cli marker info <handle>... # Show detailed marker information; accepts several handles and ranges (e.g., m-1234, m-1234..m-1240)
profiler-cli marker stack <handle> # Show full stack trace for a marker
profiler-cli function expand <handle> # Show full untruncated function name (e.g., f-123)
profiler-cli function info <handle> # Show detailed function information and category breakdown
Expand Down
2 changes: 2 additions & 0 deletions profiler-cli/guide.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ CORE WORKFLOW

Step 6: Drill into specifics
profiler-cli marker info m-1234 Full details for a marker (from handles in marker list)
profiler-cli marker info m-1234 m-1240 Several markers in one call (one record per handle)
profiler-cli marker info m-1234..m-1240 Inclusive range of handles, e.g. consecutive list rows
profiler-cli marker stack m-1234 Full stack trace at the time of a marker
profiler-cli function info f-12 Function details (source location, library)
profiler-cli function expand f-12 Show full untruncated function name
Expand Down
12 changes: 11 additions & 1 deletion profiler-cli/schemas.txt
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ profiler-cli function info --json
context: SessionContext
}

profiler-cli marker info --json
profiler-cli marker info <handle> --json
{
type: "marker-info",
threadHandle, friendlyThreadName, markerHandle, markerIndex, name,
Expand All @@ -286,6 +286,16 @@ profiler-cli strategy --json
context: SessionContext
}

profiler-cli marker info <handle>... --json
{
type: "marker-info-multi",
requested: [markerHandle],
markers: [MarkerInfoResult],
errors: [{ markerHandle, error }],
rangeSpansThreadsWarning?: { ranges: [spec], threadHandles: [threadHandle] },
context: SessionContext
}

profiler-cli status --json
{
type: "status",
Expand Down
51 changes: 44 additions & 7 deletions profiler-cli/src/commands/marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import type { Command } from 'commander';
import { expandMarkerHandleSpecs } from 'firefox-profiler/profile-query/marker-map';
import { addGlobalOptions, runCommand } from './shared';

export function registerMarkerCommand(
Expand All @@ -17,16 +18,32 @@ export function registerMarkerCommand(

addGlobalOptions(
marker
.command('info [handle]')
.description('Show detailed marker information (e.g. m-1234)')
.option('--marker <handle>', 'Marker handle')
).action(async (handleArg: string | undefined, opts) => {
const markerHandle = handleArg ?? opts.marker;
await runCommand(
.command('info [handles...]')
.description(
'Show detailed marker information for one or more markers ' +
'(e.g. m-1234, m-1234 m-1240, m-1234..m-1240)'
)
.option(
'--marker <handle,...>',
'Marker handle(s) or range(s); a range covers at most 256 handles'
)
).action(async (handleArgs: string[], opts) => {
const specs = (handleArgs.length > 0 ? handleArgs : [opts.marker]).filter(
(spec): spec is string => spec !== undefined
);

const result = await runCommand(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A spec that expands to nothing (eg, marker info '' or marker info --marker) exits 0 and prints only the context header. specs is non-empty, so the daemon's markers.length > 0 branch is taken and the "marker handle required" check is never reached. I think, we should reject an empty expanded list.

sessionDir,
{ command: 'marker', subcommand: 'info', marker: markerHandle },
{ command: 'marker', subcommand: 'info', markers: specs },
opts
);
if (
typeof result !== 'string' &&
result.type === 'marker-info-multi' &&
(result.errors.length > 0 || result.rangeSpansThreadsWarning)
) {
process.exitCode = 1;
}
});

addGlobalOptions(
Expand All @@ -36,6 +53,26 @@ export function registerMarkerCommand(
.option('--marker <handle>', 'Marker handle')
).action(async (handleArg: string | undefined, opts) => {
const markerHandle = handleArg ?? opts.marker;
// Expanding with the real grammar keeps this from being a second, drifting
// definition of it. A range reaching the daemon would come back as "Unknown
// marker m-1..m-3", which reads like a bad handle rather than bad syntax.
if (typeof markerHandle === 'string') {
let expanded: string[];
try {
expanded = expandMarkerHandleSpecs([markerHandle]);
} catch (error) {
console.error(
`Error: ${error instanceof Error ? error.message : String(error)}`
);
process.exit(1);
}
if (expanded.length > 1) {
console.error(
`Error: marker stack takes a single handle; ranges and lists are only supported by 'marker info'.`
);
process.exit(1);
}
}
await runCommand(
sessionDir,
{ command: 'marker', subcommand: 'stack', marker: markerHandle },
Expand Down
9 changes: 7 additions & 2 deletions profiler-cli/src/commands/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { collectStrings } from '../utils/parse';
import { sendCommand } from '../client';
import { formatOutput } from '../output';
import { CALL_TREE_SUMMARY_STRATEGIES } from 'firefox-profiler/profile-logic/profile-data';
import type { ClientCommand, CallTreeSummaryStrategy } from '../protocol';
import type {
ClientCommand,
CallTreeSummaryStrategy,
CommandResult,
} from '../protocol';

/**
* Options shared by every command action via `addGlobalOptions`.
Expand All @@ -31,9 +35,10 @@ export async function runCommand(
sessionDir: string,
command: ClientCommand,
opts: GlobalOptions
): Promise<void> {
): Promise<string | CommandResult> {
const result = await sendCommand(sessionDir, command, opts.session);
console.log(formatOutput(result, opts.json ?? false));
return result;
}

/**
Expand Down
16 changes: 13 additions & 3 deletions profiler-cli/src/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
toErrorMessage,
} from './diagnostics';
import { assertExhaustiveCheck } from 'firefox-profiler/utils/types';
import { expandMarkerHandleSpecs } from 'firefox-profiler/profile-query/marker-map';
import { BUILD_HASH, PACKAGE_NAME } from './constants';

/**
Expand Down Expand Up @@ -470,11 +471,20 @@ export class Daemon {
}
case 'marker':
switch (command.subcommand) {
case 'info':
if (!command.marker) {
case 'info': {
// Expand once here, so the single/multi result shape follows what
// the specs actually resolve to: every spelling of one marker
// ("m-1", "m-1,", "m-1..m-1") returns the single-marker shape.
const specs = command.markers ?? [];
const handles = expandMarkerHandleSpecs(specs);
if (handles.length === 0) {
throw new Error('marker handle required for marker info');
}
return this.querier.markerInfo(command.marker);
if (handles.length === 1) {
return this.querier.markerInfo(handles[0]);
}
return this.querier.markerInfoMulti(specs);
}
case 'stack':
if (!command.marker) {
throw new Error('marker handle required for marker stack');
Expand Down
60 changes: 58 additions & 2 deletions profiler-cli/src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
ThreadListResult,
MarkerStackResult,
MarkerInfoResult,
MarkerInfoMultiResult,
ProfileInfoResult,
ProfileMetaResult,
ThreadSamplesResult,
Expand Down Expand Up @@ -488,9 +489,64 @@ export function formatMarkerInfoResult(
result: WithContext<MarkerInfoResult>
): string {
const contextHeader = formatContextHeader(result.context);
let output = `${contextHeader}
return `${contextHeader}\n\n${formatMarkerInfoBody(result)}`;
}

Marker ${result.markerHandle}: ${result.name}`;
/**
* Format several marker info records as plain text, one per requested handle
* under a single context header. Handles that did not resolve are reported in
* place.
*/
export function formatMarkerInfoMultiResult(
result: WithContext<MarkerInfoMultiResult>
): string {
const contextHeader = formatContextHeader(result.context);
const total = result.markers.length + result.errors.length;
const records: string[] = [];

// Walk the requested handles, so failed lookups keep their place in the order.
const byHandle = new Map(result.markers.map((m) => [m.markerHandle, m]));
const errorsByHandle = new Map(
result.errors.map((e) => [e.markerHandle, e.error])
);
let position = 0;
for (const markerHandle of result.requested) {
position++;
const prefix = `[${position}/${total}] `;
const marker = byHandle.get(markerHandle);
if (marker) {
records.push(prefix + formatMarkerInfoBody(marker).trimEnd());
continue;
}
const error = errorsByHandle.get(markerHandle);
if (error !== undefined) {
records.push(`${prefix}Marker ${markerHandle}: error: ${error}`);
}
}

let output = `${contextHeader}\n\n${records.join('\n\n----------\n\n')}`;
if (result.errors.length > 0) {
const verb = result.errors.length === 1 ? 'was' : 'were';
output += `\n\n${result.errors.length} of ${total} requested markers ${verb} not found.`;
}
const spread = result.rangeSpansThreadsWarning;
if (spread) {
const rangeList = spread.ranges.join(', ');
const threadList = spread.threadHandles.join(', ');
output +=
`\n\nWarning: the range ${rangeList} covers markers in more than one thread ` +
`(${threadList}). Handle ranges are numeric, so a range that runs past the end of ` +
`the listing you were reading picks up unrelated markers. Re-run the listing and ` +
`check the handles.`;
}
return output;
}

/**
* Format one marker info record, below the context header.
*/
function formatMarkerInfoBody(result: MarkerInfoResult): string {
let output = `Marker ${result.markerHandle}: ${result.name}`;
if (result.tooltipLabel) {
output += ` - ${result.tooltipLabel}`;
}
Expand Down
3 changes: 3 additions & 0 deletions profiler-cli/src/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
formatThreadListResult,
formatMarkerStackResult,
formatMarkerInfoResult,
formatMarkerInfoMultiResult,
formatProfileInfoResult,
formatProfileMetaResult,
formatThreadSamplesResult,
Expand Down Expand Up @@ -77,6 +78,8 @@ export function formatOutput(
return formatMarkerStackResult(result);
case 'marker-info':
return formatMarkerInfoResult(result);
case 'marker-info-multi':
return formatMarkerInfoMultiResult(result);
case 'profile-info':
return formatProfileInfoResult(result);
case 'profile-meta':
Expand Down
6 changes: 6 additions & 0 deletions profiler-cli/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type {
RateStats,
MarkerGroupData,
MarkerInfoResult,
MarkerInfoMultiResult,
MarkerStackResult,
StackTraceData,
ProfileInfoResult,
Expand Down Expand Up @@ -97,6 +98,7 @@ import type {
ThreadListOptions,
MarkerStackResult,
MarkerInfoResult,
MarkerInfoMultiResult,
ProfileInfoResult,
ProfileMetaResult,
ThreadSamplesResult,
Expand Down Expand Up @@ -182,7 +184,10 @@ export type ClientCommand =
| {
command: 'marker';
subcommand: 'info' | 'select' | 'stack';
/** Single handle, for `stack`. */
marker?: string;
/** Handle specs for `info`, e.g. ["m-42", "m-50..m-53"]. */
markers?: string[];
}
| {
command: 'counter';
Expand Down Expand Up @@ -247,6 +252,7 @@ export type CommandResult =
| WithContext<ThreadListResult>
| WithContext<MarkerStackResult>
| WithContext<MarkerInfoResult>
| WithContext<MarkerInfoMultiResult>
| WithContext<ProfileInfoResult>
| WithContext<ProfileMetaResult>
| WithContext<ThreadSamplesResult>
Expand Down
Loading
Loading