Skip to content

Commit b570f1a

Browse files
committed
Drop idle samples by default and add a --incude-idle flag for including them
1 parent 67fff3c commit b570f1a

6 files changed

Lines changed: 156 additions & 43 deletions

File tree

profile-query-cli/src/daemon.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,21 @@ export class Daemon {
293293
}
294294
return this.querier!.threadSelect(command.thread);
295295
case 'samples':
296-
return this.querier!.threadSamples(command.thread);
296+
return this.querier!.threadSamples(
297+
command.thread,
298+
command.includeIdle
299+
);
297300
case 'samples-top-down':
298301
return this.querier!.threadSamplesTopDown(
299302
command.thread,
300-
command.callTreeOptions
303+
command.callTreeOptions,
304+
command.includeIdle
301305
);
302306
case 'samples-bottom-up':
303307
return this.querier!.threadSamplesBottomUp(
304308
command.thread,
305-
command.callTreeOptions
309+
command.callTreeOptions,
310+
command.includeIdle
306311
);
307312
case 'markers':
308313
return this.querier!.threadMarkers(
@@ -312,7 +317,8 @@ export class Daemon {
312317
case 'functions':
313318
return this.querier!.threadFunctions(
314319
command.thread,
315-
command.functionFilters
320+
command.functionFilters,
321+
command.includeIdle
316322
);
317323
default:
318324
throw assertExhaustiveCheck(command);

profile-query-cli/src/formatters.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,9 +490,12 @@ export function formatThreadSamplesResult(
490490
result: WithContext<ThreadSamplesResult>
491491
): string {
492492
const contextHeader = formatContextHeader(result.context);
493+
const activeOnlyNote = result.activeOnly
494+
? 'Note: active samples only (idle excluded) — use --include-idle to include idle samples.\n\n'
495+
: '';
493496
let output = `${contextHeader}
494497
495-
Thread: ${result.friendlyThreadName}\n\n`;
498+
Thread: ${result.friendlyThreadName}\n\n${activeOnlyNote}`;
496499

497500
// Top functions by total time
498501
output += 'Top Functions (by total time):\n';
@@ -575,9 +578,12 @@ export function formatThreadSamplesTopDownResult(
575578
result: WithContext<ThreadSamplesTopDownResult>
576579
): string {
577580
const contextHeader = formatContextHeader(result.context);
581+
const activeOnlyNote = result.activeOnly
582+
? 'Note: active samples only (idle excluded) — use --include-idle to include idle samples.\n\n'
583+
: '';
578584
let output = `${contextHeader}
579585
580-
Thread: ${result.friendlyThreadName}\n\n`;
586+
Thread: ${result.friendlyThreadName}\n\n${activeOnlyNote}`;
581587

582588
// Top-down call tree
583589
output += formatCallTree(result.regularCallTree, 'Top-Down');
@@ -592,9 +598,12 @@ export function formatThreadSamplesBottomUpResult(
592598
result: WithContext<ThreadSamplesBottomUpResult>
593599
): string {
594600
const contextHeader = formatContextHeader(result.context);
601+
const activeOnlyNote = result.activeOnly
602+
? 'Note: active samples only (idle excluded) — use --include-idle to include idle samples.\n\n'
603+
: '';
595604
let output = `${contextHeader}
596605
597-
Thread: ${result.friendlyThreadName}\n\n`;
606+
Thread: ${result.friendlyThreadName}\n\n${activeOnlyNote}`;
598607

599608
// Bottom-up call tree (inverted tree shows callers)
600609
if (result.invertedCallTree) {
@@ -794,6 +803,12 @@ export function formatThreadFunctionsResult(
794803
`Functions in thread ${result.threadHandle} (${result.friendlyThreadName}) — ${result.filteredFunctionCount} functions${filterSuffix}\n`
795804
);
796805

806+
if (result.activeOnly) {
807+
lines.push(
808+
'Note: active samples only (idle excluded) — use --include-idle to include idle samples.\n'
809+
);
810+
}
811+
797812
if (result.filteredFunctionCount === 0) {
798813
if (hasFilters) {
799814
lines.push('No functions match the specified filters.');

profile-query-cli/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,13 @@ async function main(): Promise<void> {
476476
}
477477
}
478478

479+
const includeIdle =
480+
(subcommand === 'samples' ||
481+
subcommand === 'samples-top-down' ||
482+
subcommand === 'samples-bottom-up' ||
483+
subcommand === 'functions') &&
484+
argv['include-idle'] === true;
485+
479486
if (
480487
subcommand === 'info' ||
481488
subcommand === 'select' ||
@@ -491,6 +498,7 @@ async function main(): Promise<void> {
491498
command: 'thread',
492499
subcommand,
493500
thread,
501+
includeIdle: includeIdle || undefined,
494502
markerFilters,
495503
functionFilters,
496504
callTreeOptions,

profile-query-cli/src/protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export type ClientCommand =
7575
| 'markers'
7676
| 'functions';
7777
thread?: string;
78+
includeIdle?: boolean;
7879
markerFilters?: MarkerFilterOptions;
7980
functionFilters?: FunctionFilterOptions;
8081
callTreeOptions?: CallTreeCollectionOptions;

src/profile-query/index.ts

Lines changed: 115 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import {
2525
import {
2626
getAllCommittedRanges,
2727
getSelectedThreadIndexes,
28+
getTransformStack,
2829
} from 'firefox-profiler/selectors/url-state';
2930
import {
3031
commitRange,
3132
popCommittedRanges,
3233
changeSelectedThreads,
34+
addTransformToStack,
3335
} from '../actions/profile-view';
3436
import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread';
3537
import { TimestampManager } from './timestamps';
@@ -72,7 +74,8 @@ import type {
7274
} from './types';
7375
import type { CallTreeCollectionOptions } from './formatters/call-tree';
7476

75-
import type { StartEndRange } from 'firefox-profiler/types';
77+
import { getThreadsKey } from 'firefox-profiler/profile-logic/profile-data';
78+
import type { StartEndRange, ThreadIndex } from 'firefox-profiler/types';
7679
import type { Store } from '../types/store';
7780

7881
export class ProfileQuerier {
@@ -133,43 +136,73 @@ export class ProfileQuerier {
133136
}
134137

135138
async threadSamples(
136-
threadHandle?: string
139+
threadHandle?: string,
140+
includeIdle: boolean = false
137141
): Promise<WithContext<ThreadSamplesResult>> {
138-
const result = await collectThreadSamples(
139-
this._store,
140-
this._threadMap,
141-
this._functionMap,
142-
threadHandle
143-
);
144-
return { ...result, context: this._getContext() };
142+
const activeOnly = !includeIdle;
143+
const threadIndexes =
144+
threadHandle !== undefined
145+
? this._threadMap.threadIndexesForHandle(threadHandle)
146+
: getSelectedThreadIndexes(this._store.getState());
147+
const collect = () =>
148+
collectThreadSamples(
149+
this._store,
150+
this._threadMap,
151+
this._functionMap,
152+
threadHandle
153+
);
154+
const result = activeOnly
155+
? this._withDroppedIdle(threadIndexes, collect)
156+
: collect();
157+
return { ...result, activeOnly, context: this._getContext() };
145158
}
146159

147160
async threadSamplesTopDown(
148161
threadHandle?: string,
149-
callTreeOptions?: CallTreeCollectionOptions
162+
callTreeOptions?: CallTreeCollectionOptions,
163+
includeIdle: boolean = false
150164
): Promise<WithContext<ThreadSamplesTopDownResult>> {
151-
const result = await collectThreadSamplesTopDown(
152-
this._store,
153-
this._threadMap,
154-
this._functionMap,
155-
threadHandle,
156-
callTreeOptions
157-
);
158-
return { ...result, context: this._getContext() };
165+
const activeOnly = !includeIdle;
166+
const threadIndexes =
167+
threadHandle !== undefined
168+
? this._threadMap.threadIndexesForHandle(threadHandle)
169+
: getSelectedThreadIndexes(this._store.getState());
170+
const collect = () =>
171+
collectThreadSamplesTopDown(
172+
this._store,
173+
this._threadMap,
174+
this._functionMap,
175+
threadHandle,
176+
callTreeOptions
177+
);
178+
const result = activeOnly
179+
? this._withDroppedIdle(threadIndexes, collect)
180+
: collect();
181+
return { ...result, activeOnly, context: this._getContext() };
159182
}
160183

161184
async threadSamplesBottomUp(
162185
threadHandle?: string,
163-
callTreeOptions?: CallTreeCollectionOptions
186+
callTreeOptions?: CallTreeCollectionOptions,
187+
includeIdle: boolean = false
164188
): Promise<WithContext<ThreadSamplesBottomUpResult>> {
165-
const result = await collectThreadSamplesBottomUp(
166-
this._store,
167-
this._threadMap,
168-
this._functionMap,
169-
threadHandle,
170-
callTreeOptions
171-
);
172-
return { ...result, context: this._getContext() };
189+
const activeOnly = !includeIdle;
190+
const threadIndexes =
191+
threadHandle !== undefined
192+
? this._threadMap.threadIndexesForHandle(threadHandle)
193+
: getSelectedThreadIndexes(this._store.getState());
194+
const collect = () =>
195+
collectThreadSamplesBottomUp(
196+
this._store,
197+
this._threadMap,
198+
this._functionMap,
199+
threadHandle,
200+
callTreeOptions
201+
);
202+
const result = activeOnly
203+
? this._withDroppedIdle(threadIndexes, collect)
204+
: collect();
205+
return { ...result, activeOnly, context: this._getContext() };
173206
}
174207

175208
/**
@@ -397,6 +430,42 @@ export class ProfileQuerier {
397430
return `Selected ${threadIndexes.size} threads: ${threadHandle} (${names})`;
398431
}
399432

433+
/**
434+
* Apply a drop-category transform for the Idle category around a computation,
435+
* then restore the transform stack to its previous state.
436+
* If the profile has no Idle category, fn() is called without changes.
437+
*/
438+
private _withDroppedIdle<T>(threadIndexes: Set<ThreadIndex>, fn: () => T): T {
439+
const state = this._store.getState();
440+
const profile = getProfile(state);
441+
const idleCategoryIndex =
442+
profile.meta.categories?.findIndex((c) => c.name === 'Idle') ?? -1;
443+
444+
if (idleCategoryIndex === -1) {
445+
return fn();
446+
}
447+
448+
const threadsKey = getThreadsKey(threadIndexes);
449+
const stackLengthBefore = getTransformStack(state, threadsKey).length;
450+
451+
this._store.dispatch(
452+
addTransformToStack(threadsKey, {
453+
type: 'drop-category',
454+
category: idleCategoryIndex,
455+
})
456+
);
457+
458+
try {
459+
return fn();
460+
} finally {
461+
this._store.dispatch({
462+
type: 'POP_TRANSFORMS_FROM_STACK',
463+
threadsKey,
464+
firstPoppedFilterIndex: stackLengthBefore,
465+
});
466+
}
467+
}
468+
400469
/**
401470
* Get current session context for display in command outputs.
402471
* This is a lightweight version of getStatus() that includes only
@@ -643,16 +712,26 @@ export class ProfileQuerier {
643712
*/
644713
async threadFunctions(
645714
threadHandle?: string,
646-
filterOptions?: FunctionFilterOptions
715+
filterOptions?: FunctionFilterOptions,
716+
includeIdle: boolean = false
647717
): Promise<WithContext<ThreadFunctionsResult>> {
648-
const result = await collectThreadFunctions(
649-
this._store,
650-
this._threadMap,
651-
this._functionMap,
652-
threadHandle,
653-
filterOptions
654-
);
655-
return { ...result, context: this._getContext() };
718+
const activeOnly = !includeIdle;
719+
const threadIndexes =
720+
threadHandle !== undefined
721+
? this._threadMap.threadIndexesForHandle(threadHandle)
722+
: getSelectedThreadIndexes(this._store.getState());
723+
const collect = () =>
724+
collectThreadFunctions(
725+
this._store,
726+
this._threadMap,
727+
this._functionMap,
728+
threadHandle,
729+
filterOptions
730+
);
731+
const result = activeOnly
732+
? this._withDroppedIdle(threadIndexes, collect)
733+
: collect();
734+
return { ...result, activeOnly, context: this._getContext() };
656735
}
657736

658737
/**

src/profile-query/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export type ThreadSamplesResult = {
187187
type: 'thread-samples';
188188
threadHandle: string;
189189
friendlyThreadName: string;
190+
activeOnly?: boolean;
190191
topFunctionsByTotal: TopFunctionInfo[];
191192
topFunctionsBySelf: TopFunctionInfo[];
192193
heaviestStack: {
@@ -207,13 +208,15 @@ export type ThreadSamplesTopDownResult = {
207208
type: 'thread-samples-top-down';
208209
threadHandle: string;
209210
friendlyThreadName: string;
211+
activeOnly?: boolean;
210212
regularCallTree: CallTreeNode;
211213
};
212214

213215
export type ThreadSamplesBottomUpResult = {
214216
type: 'thread-samples-bottom-up';
215217
threadHandle: string;
216218
friendlyThreadName: string;
219+
activeOnly?: boolean;
217220
invertedCallTree: CallTreeNode | null;
218221
};
219222

@@ -315,6 +318,7 @@ export type ThreadFunctionsResult = {
315318
type: 'thread-functions';
316319
threadHandle: string;
317320
friendlyThreadName: string;
321+
activeOnly?: boolean;
318322
totalFunctionCount: number;
319323
filteredFunctionCount: number;
320324
filters?: {

0 commit comments

Comments
 (0)