From 8f502d354388aae59512cd6710c38a34beda4d59 Mon Sep 17 00:00:00 2001 From: fatadel Date: Fri, 10 Jul 2026 14:53:06 +0200 Subject: [PATCH] Show each counter's owning process in profiler-cli Counters listed by "pq counter list" gave no hint about which process they belonged to, so profiles with many identically-named counters (one "Process CPU" per process) were impossible to tell apart. Each counter line now carries its process handle, name, and pid, mirroring how "profile info" labels processes so the two outputs cross-reference: c-2: Process CPU (CPU) [p-0 Parent Process, pid 47021] [2787 samples] "counter info" gains a matching Process line. The handle is dropped when the owning process can't be resolved, leaving just the name and pid. Closes #6134 --- profiler-cli/schemas.txt | 3 ++- profiler-cli/src/formatters.ts | 17 +++++++++++++- .../src/test/unit/counter-formatting.test.ts | 15 +++++++++++-- src/profile-query/formatters/counter-info.ts | 22 ++++++++++++++----- src/profile-query/formatters/profile-info.ts | 14 +++++++----- src/profile-query/index.ts | 7 +++++- src/profile-query/process-thread-list.ts | 7 ++++++ src/profile-query/types.ts | 3 +++ .../profile-query/profile-querier.test.ts | 5 ++++- 9 files changed, 76 insertions(+), 17 deletions(-) diff --git a/profiler-cli/schemas.txt b/profiler-cli/schemas.txt index b3a5de298f..3547de4696 100644 --- a/profiler-cli/schemas.txt +++ b/profiler-cli/schemas.txt @@ -30,7 +30,8 @@ CounterSummary: { counterHandle, counterIndex, name, label, category, unit, graphType, - color, pid, mainThreadIndex, mainThreadHandle, mainThreadName, + color, pid, processIndex, processName, etld1?, + mainThreadIndex, mainThreadHandle, mainThreadName, rangeSampleCount, stats: [{ source, label, value, formattedValue, carbon? }], graph: [number] diff --git a/profiler-cli/src/formatters.ts b/profiler-cli/src/formatters.ts index 9b07369de2..04e028a79d 100644 --- a/profiler-cli/src/formatters.ts +++ b/profiler-cli/src/formatters.ts @@ -476,8 +476,20 @@ function formatCounterStats(counter: CounterSummary): string { return `${stats} [${counter.rangeSampleCount} samples]`; } +/** `p-N Process Name (etld+1)`, dropping the handle when the process is unknown. */ +function formatCounterProcessName(counter: CounterSummary): string { + const handle = counter.processIndex >= 0 ? `p-${counter.processIndex} ` : ''; + const etld1 = counter.etld1 ? ` (${counter.etld1})` : ''; + return `${handle}${counter.processName}${etld1}`; +} + +/** The ` [p-N Process Name (etld+1), pid X]` segment identifying the owning process. */ +function formatCounterProcess(counter: CounterSummary): string { + return ` [${formatCounterProcessName(counter)}, pid ${counter.pid}]`; +} + function formatCounterSummaryLine(counter: CounterSummary): string { - return ` ${counter.counterHandle}: ${counter.label} (${counter.category})${formatCounterStats(counter)}`; + return ` ${counter.counterHandle}: ${counter.label} (${counter.category})${formatCounterProcess(counter)}${formatCounterStats(counter)}`; } /** @@ -566,6 +578,9 @@ export function formatCounterInfoResult( } lines.push(` Unit: ${result.unit || '(none)'}`); lines.push(` Graph type: ${result.graphType}`); + lines.push( + ` Process: ${formatCounterProcessName(result)} [pid ${result.pid}]` + ); lines.push( ` Main thread: ${result.mainThreadHandle} (${result.mainThreadName})` ); diff --git a/profiler-cli/src/test/unit/counter-formatting.test.ts b/profiler-cli/src/test/unit/counter-formatting.test.ts index 7377d0720c..7c1e4a6714 100644 --- a/profiler-cli/src/test/unit/counter-formatting.test.ts +++ b/profiler-cli/src/test/unit/counter-formatting.test.ts @@ -34,6 +34,8 @@ function makeCounter(overrides: Partial = {}): CounterSummary { graphType: 'line-accumulated', color: 'orange', pid: '123', + processIndex: 0, + processName: 'Parent Process', mainThreadIndex: 0, mainThreadHandle: 't-0', mainThreadName: 'GeckoMain', @@ -64,6 +66,10 @@ describe('formatCounterListResult', function () { label: 'Bandwidth', category: 'Bandwidth', graphType: 'line-rate', + processIndex: 3, + processName: 'Isolated Web Content', + etld1: 'example.com', + pid: '456', stats: [ { source: 'count-range', @@ -78,10 +84,14 @@ describe('formatCounterListResult', function () { const output = formatCounterListResult(result); expect(output).toContain('Counters (2):'); - expect(output).toContain('c-0: Memory (Memory)'); + expect(output).toContain( + 'c-0: Memory (Memory) [p-0 Parent Process, pid 123]' + ); expect(output).toContain('memory range in graph: 27B'); expect(output).toContain('[7 samples]'); - expect(output).toContain('c-1: Bandwidth (Bandwidth)'); + expect(output).toContain( + 'c-1: Bandwidth (Bandwidth) [p-3 Isolated Web Content (example.com), pid 456]' + ); expect(output).toContain('Data transferred in the visible range: 2KB'); }); @@ -180,6 +190,7 @@ describe('formatCounterInfoResult', function () { expect(output).toContain('Category: Memory'); expect(output).toContain('Unit: bytes'); expect(output).toContain('Graph type: line-accumulated'); + expect(output).toContain('Process: p-0 Parent Process [pid 123]'); expect(output).toContain('Main thread: t-0 (GeckoMain)'); expect(output).toContain('Description: Amount of allocated memory'); expect(output).toContain('memory range in graph: 27B'); diff --git a/src/profile-query/formatters/counter-info.ts b/src/profile-query/formatters/counter-info.ts index 5d8caeb7ee..debd8dcc67 100644 --- a/src/profile-query/formatters/counter-info.ts +++ b/src/profile-query/formatters/counter-info.ts @@ -25,6 +25,7 @@ import { } from 'firefox-profiler/components/timeline/TrackCounterTooltipFormat'; import { getSampleIndexRangeForSelection } from 'firefox-profiler/profile-logic/profile-data'; import { getCounterHandle, parseCounterHandle } from '../counter-map'; +import { getProcessName } from '../process-thread-list'; import type { CounterIndex, CounterDisplayConfig, @@ -184,6 +185,7 @@ function collectCounterStats( export function collectCounterSummary( store: Store, threadMap: ThreadMap, + processIndexMap: Map, counterIndex: CounterIndex ): CounterSummary { const state = store.getState(); @@ -197,7 +199,7 @@ export function collectCounterSummary( counterIndex ); - const mainThreadName = profile.threads[counter.mainThreadIndex]?.name ?? ''; + const mainThread = profile.threads[counter.mainThreadIndex]; return { counterHandle: getCounterHandle(counterIndex), @@ -209,9 +211,12 @@ export function collectCounterSummary( graphType: display.graphType, color: display.color, pid: counter.pid, + processIndex: processIndexMap.get(counter.pid) ?? -1, + processName: mainThread ? getProcessName(mainThread) : 'unknown', + etld1: mainThread?.['eTLD+1'], mainThreadIndex: counter.mainThreadIndex, mainThreadHandle: threadMap.handleForThreadIndex(counter.mainThreadIndex), - mainThreadName, + mainThreadName: mainThread?.name ?? '', rangeSampleCount: Math.max(0, rangeEndIndex - rangeStartIndex), stats: collectCounterStats(store, counterIndex), graph: collectCounterGraph(store, counterIndex), @@ -224,12 +229,13 @@ export function collectCounterSummary( */ export function collectCounterList( store: Store, - threadMap: ThreadMap + threadMap: ThreadMap, + processIndexMap: Map ): CounterListResult { return { type: 'counter-list', counters: getSortedCounterIndexes(store).map((index) => - collectCounterSummary(store, threadMap, index) + collectCounterSummary(store, threadMap, processIndexMap, index) ), }; } @@ -503,6 +509,7 @@ function collectCounterGraph( export function collectCounterInfo( store: Store, threadMap: ThreadMap, + processIndexMap: Map, timestampManager: TimestampManager, counterHandle: string ): CounterInfoResult { @@ -510,7 +517,12 @@ export function collectCounterInfo( const counters = getCounters(state) ?? []; const counterIndex = parseCounterHandle(counterHandle, counters.length); - const summary = collectCounterSummary(store, threadMap, counterIndex); + const summary = collectCounterSummary( + store, + threadMap, + processIndexMap, + counterIndex + ); const selectors = getCounterSelectors(counterIndex); const counter = selectors.getCounter(state); const [rangeStartIndex, rangeEndIndex] = getInRangeSampleIndexes( diff --git a/src/profile-query/formatters/profile-info.ts b/src/profile-query/formatters/profile-info.ts index 664738a18b..1061df06d9 100644 --- a/src/profile-query/formatters/profile-info.ts +++ b/src/profile-query/formatters/profile-info.ts @@ -8,7 +8,7 @@ import { getRangeFilteredCombinedThreadActivitySlices, } from 'firefox-profiler/selectors/profile'; import { getProfileNameWithDefault } from 'firefox-profiler/selectors/url-state'; -import { buildProcessThreadList } from '../process-thread-list'; +import { buildProcessThreadList, getProcessName } from '../process-thread-list'; import { collectSliceTree } from '../cpu-activity'; import { collectCounterSummary, getSortedCounterIndexes } from './counter-info'; import type { Store } from '../../types/store'; @@ -92,10 +92,7 @@ export function collectProfileInfo( (t) => t.pid === processItem.pid ); if (threadFromProcess) { - processItem.name = - threadFromProcess.processName || - threadFromProcess.processType || - 'unknown'; + processItem.name = getProcessName(threadFromProcess); processItem.etld1 = threadFromProcess['eTLD+1']; processItem.startTime = threadFromProcess.processStartupTime; processItem.endTime = threadFromProcess.processShutdownTime; @@ -110,7 +107,12 @@ export function collectProfileInfo( const countersByPid = new Map(); for (const index of getSortedCounterIndexes(store)) { - const counter = collectCounterSummary(store, threadMap, index); + const counter = collectCounterSummary( + store, + threadMap, + processIndexMap, + index + ); const list = countersByPid.get(counter.pid) ?? []; list.push(counter); countersByPid.set(counter.pid, list); diff --git a/src/profile-query/index.ts b/src/profile-query/index.ts index 369725a12e..019d75743a 100644 --- a/src/profile-query/index.ts +++ b/src/profile-query/index.ts @@ -220,7 +220,11 @@ export class ProfileQuerier { } async counterList(): Promise> { - const result = collectCounterList(this._store, this._threadMap); + const result = collectCounterList( + this._store, + this._threadMap, + this._processIndexMap + ); return { ...result, context: this._getContext() }; } @@ -230,6 +234,7 @@ export class ProfileQuerier { const result = collectCounterInfo( this._store, this._threadMap, + this._processIndexMap, this._timestampManager, counterHandle ); diff --git a/src/profile-query/process-thread-list.ts b/src/profile-query/process-thread-list.ts index 0c9ae36b9c..bb5ccaeaf9 100644 --- a/src/profile-query/process-thread-list.ts +++ b/src/profile-query/process-thread-list.ts @@ -2,6 +2,13 @@ * 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/. */ +import type { RawThread } from 'firefox-profiler/types'; + +/** The display name of the process a thread belongs to. */ +export function getProcessName(thread: RawThread): string { + return thread.processName || thread.processType || 'unknown'; +} + export type ThreadInfo = { threadIndex: number; name: string; diff --git a/src/profile-query/types.ts b/src/profile-query/types.ts index d3bc43c43b..e770c7b1c2 100644 --- a/src/profile-query/types.ts +++ b/src/profile-query/types.ts @@ -699,6 +699,9 @@ export type CounterSummary = { graphType: CounterGraphType; color: string; pid: string; + processIndex: number; + processName: string; // e.g. "Parent Process" + etld1?: string; // eTLD+1 of an isolated content process, when known mainThreadIndex: number; mainThreadHandle: string; // e.g. "t-0" mainThreadName: string; diff --git a/src/test/unit/profile-query/profile-querier.test.ts b/src/test/unit/profile-query/profile-querier.test.ts index b5f5ce1c38..65b08e0f57 100644 --- a/src/test/unit/profile-query/profile-querier.test.ts +++ b/src/test/unit/profile-query/profile-querier.test.ts @@ -370,12 +370,15 @@ describe('ProfileQuerier', function () { } it('counterList returns a schema-driven summary per counter', async function () { - const { profile } = profileWithMemoryCounter(); + const { profile, counter } = profileWithMemoryCounter(); const result = await querierFor(profile).counterList(); expect(result.counters).toHaveLength(1); expect(result.counters[0].counterHandle).toBe('c-0'); expect(result.counters[0].label).toBe('Memory'); + expect(result.counters[0].pid).toBe(counter.pid); + expect(result.counters[0].processIndex).toBeGreaterThanOrEqual(0); + expect(result.counters[0].processName).toBeTruthy(); expect( result.counters[0].stats.some((s) => s.source === 'count-range') ).toBe(true);