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);