From e175a41f312cb7608e90095c4c2ec35e85e6ee84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 27 Jul 2026 13:58:51 +0200 Subject: [PATCH 1/2] Scope profiler-cli thread markers to the committed range The default 'thread markers' read `getFullMarkerListIndexes`, which ignores the committed range, so zooming had no effect unless --search was also passed. Switch the default paths to `getCommittedRangeFilteredMarkerIndexes` so both branches respect the current zoom consistently. --- src/profile-query/formatters/marker-info.ts | 8 ++- .../profile-query/profile-querier.test.ts | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/profile-query/formatters/marker-info.ts b/src/profile-query/formatters/marker-info.ts index 530c1fb164..0b86eabdae 100644 --- a/src/profile-query/formatters/marker-info.ts +++ b/src/profile-query/formatters/marker-info.ts @@ -702,12 +702,14 @@ export function collectThreadMarkers( const markerSchemaByName = getMarkerSchemaByName(state); const stringTable = getStringTable(state); - // Get marker indexes - use search-filtered if search is active, otherwise all markers + // Get marker indexes scoped to the committed (zoom) range. When a search is + // active we use the search-filtered set, which is itself built on top of the + // committed-range-filtered indexes, so both paths respect the current zoom. const originalCount = - threadSelectors.getFullMarkerListIndexes(state).length; + threadSelectors.getCommittedRangeFilteredMarkerIndexes(state).length; let filteredIndexes = searchString ? threadSelectors.getSearchFilteredMarkerIndexes(state) - : threadSelectors.getFullMarkerListIndexes(state); + : threadSelectors.getCommittedRangeFilteredMarkerIndexes(state); // Apply all marker filters filteredIndexes = applyMarkerFilters( diff --git a/src/test/unit/profile-query/profile-querier.test.ts b/src/test/unit/profile-query/profile-querier.test.ts index 062a1fbf43..12e6249ca7 100644 --- a/src/test/unit/profile-query/profile-querier.test.ts +++ b/src/test/unit/profile-query/profile-querier.test.ts @@ -694,4 +694,59 @@ describe('ProfileQuerier', function () { expect(zoomed.networkActivity!.inFlightMs).toBeLessThan(fullInFlight); }); }); + + describe('threadMarkers', function () { + function querierWithMarkers() { + const profile = getProfileWithMarkers([ + ['Alpha', 10, null, { type: 'tracing', category: 'Test' }], + ['Beta', 20, null, { type: 'tracing', category: 'Test' }], + ['Gamma', 30, null, { type: 'tracing', category: 'Test' }], + ['Delta', 40, null, { type: 'tracing', category: 'Test' }], + ]); + const store = storeWithProfile(profile); + const rootRange = getProfileRootRange(store.getState()); + return { querier: new ProfileQuerier(store, rootRange), rootRange }; + } + + it('restricts the default marker list to the committed (zoom) range', async function () { + const { querier, rootRange } = querierWithMarkers(); + + const full = await querier.threadMarkers('t-0'); + expect(full.totalMarkerCount).toBe(4); + expect(full.filteredMarkerCount).toBe(4); + + // Zoom to a window that only contains the marker at 20ms. + const startName = querier._timestampManager.nameForTimestamp( + rootRange.start + 2 + ); + const endName = querier._timestampManager.nameForTimestamp( + rootRange.start + 18 + ); + await querier.pushViewRange(`${startName},${endName}`); + + const zoomed = await querier.threadMarkers('t-0'); + expect(zoomed.totalMarkerCount).toBe(1); + expect(zoomed.filteredMarkerCount).toBe(1); + const zoomedNames = zoomed.byType.map((t) => t.markerName); + expect(zoomedNames).toContain('Beta'); + expect(zoomedNames).not.toContain('Alpha'); + expect(zoomedNames).not.toContain('Delta'); + }); + + it('restricts the --list output to the committed (zoom) range', async function () { + const { querier, rootRange } = querierWithMarkers(); + + const startName = querier._timestampManager.nameForTimestamp( + rootRange.start + 2 + ); + const endName = querier._timestampManager.nameForTimestamp( + rootRange.start + 18 + ); + await querier.pushViewRange(`${startName},${endName}`); + + const zoomed = await querier.threadMarkers('t-0', { list: true }); + const listedNames = zoomed.flatMarkers!.map((m) => m.name); + expect(listedNames).toEqual(['Beta']); + }); + }); }); From 6d734d26b87b9e9ca3deeacf8f4c47b021f1dd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Naz=C4=B1m=20Can=20Alt=C4=B1nova?= Date: Mon, 27 Jul 2026 19:17:12 +0200 Subject: [PATCH 2/2] Show full-range marker count when zoomed in 'thread markers' When zoomed into a committed range, the marker count line now reads 'N markers in view (of M in the full range)', mirroring the 'of view / of full' percentages that 'thread samples' already shows. The baseline is this thread's marker count over the full time range (ignoring zoom), exposed as a new optional fullRangeMarkerCount field set only when zoomed. --- profiler-cli/schemas.txt | 1 + profiler-cli/src/formatters.ts | 8 ++++++- .../src/test/unit/marker-formatting.test.ts | 21 +++++++++++++++++++ src/profile-query/formatters/marker-info.ts | 12 ++++++++++- src/profile-query/types.ts | 1 + .../profile-query/profile-querier.test.ts | 4 ++++ 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/profiler-cli/schemas.txt b/profiler-cli/schemas.txt index 302f8f7d61..9aff7cae56 100644 --- a/profiler-cli/schemas.txt +++ b/profiler-cli/schemas.txt @@ -168,6 +168,7 @@ profiler-cli thread markers --json type: "thread-markers", threadHandle, friendlyThreadName, totalMarkerCount, filteredMarkerCount, + fullRangeMarkerCount?, byType: [{ markerName, count, isInterval, durationStats?: { min, max, avg, median, p95, p99 }, diff --git a/profiler-cli/src/formatters.ts b/profiler-cli/src/formatters.ts index f34eaa7021..a3f0c08fc3 100644 --- a/profiler-cli/src/formatters.ts +++ b/profiler-cli/src/formatters.ts @@ -1124,8 +1124,14 @@ export function formatThreadMarkersResult( ? ` (filtered from ${result.totalMarkerCount})` : ''; + // When zoomed, show the in-view count against this thread's full-range total. + const zoomSuffix = + result.fullRangeMarkerCount !== undefined + ? ` in view (of ${result.fullRangeMarkerCount} in the full range)` + : ''; + lines.push( - `Markers in thread ${result.threadHandle} (${result.friendlyThreadName}) — ${result.filteredMarkerCount} markers${filterSuffix}` + `Markers in thread ${result.threadHandle} (${result.friendlyThreadName}) — ${result.filteredMarkerCount} markers${filterSuffix}${zoomSuffix}` ); lines.push('Legend: ✓ = has stack trace, ✗ = no stack trace\n'); diff --git a/profiler-cli/src/test/unit/marker-formatting.test.ts b/profiler-cli/src/test/unit/marker-formatting.test.ts index 283acc8723..d45353e509 100644 --- a/profiler-cli/src/test/unit/marker-formatting.test.ts +++ b/profiler-cli/src/test/unit/marker-formatting.test.ts @@ -148,3 +148,24 @@ describe('formatThreadMarkersResult flat list mode', function () { expect(output).not.toContain('By Category'); }); }); + +describe('formatThreadMarkersResult zoom baseline', function () { + it('notes the full-range total when zoomed', function () { + const result = makeResult({ + filteredMarkerCount: 3, + totalMarkerCount: 3, + fullRangeMarkerCount: 42, + }); + + const output = formatThreadMarkersResult(result); + expect(output).toContain('3 markers in view (of 42 in the full range)'); + }); + + it('omits the full-range note when not zoomed', function () { + const result = makeResult({ filteredMarkerCount: 3, totalMarkerCount: 3 }); + + const output = formatThreadMarkersResult(result); + expect(output).not.toContain('in the full range'); + expect(output).toContain('3 markers'); + }); +}); diff --git a/src/profile-query/formatters/marker-info.ts b/src/profile-query/formatters/marker-info.ts index 0b86eabdae..ce1c2614fa 100644 --- a/src/profile-query/formatters/marker-info.ts +++ b/src/profile-query/formatters/marker-info.ts @@ -2,7 +2,10 @@ * 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 { getSelectedThreadIndexes } from 'firefox-profiler/selectors/url-state'; +import { + getSelectedThreadIndexes, + getAllCommittedRanges, +} from 'firefox-profiler/selectors/url-state'; import { getProfile, getCategories, @@ -711,6 +714,12 @@ export function collectThreadMarkers( ? threadSelectors.getSearchFilteredMarkerIndexes(state) : threadSelectors.getCommittedRangeFilteredMarkerIndexes(state); + // When zoomed, show this thread's marker count over the full time range. + const isZoomed = getAllCommittedRanges(state).length > 0; + const fullRangeMarkerCount = isZoomed + ? threadSelectors.getFullMarkerListIndexes(state).length + : undefined; + // Apply all marker filters filteredIndexes = applyMarkerFilters( filteredIndexes, @@ -866,6 +875,7 @@ export function collectThreadMarkers( friendlyThreadName, totalMarkerCount: originalCount, filteredMarkerCount: filteredIndexes.length, + fullRangeMarkerCount, filters, byType, byCategory, diff --git a/src/profile-query/types.ts b/src/profile-query/types.ts index 536c6d908f..3fba1cdf44 100644 --- a/src/profile-query/types.ts +++ b/src/profile-query/types.ts @@ -570,6 +570,7 @@ export type ThreadMarkersResult = { friendlyThreadName: string; totalMarkerCount: number; filteredMarkerCount: number; + fullRangeMarkerCount?: number; filters?: { searchString?: string; minDuration?: number; diff --git a/src/test/unit/profile-query/profile-querier.test.ts b/src/test/unit/profile-query/profile-querier.test.ts index 12e6249ca7..81cb904e42 100644 --- a/src/test/unit/profile-query/profile-querier.test.ts +++ b/src/test/unit/profile-query/profile-querier.test.ts @@ -714,6 +714,8 @@ describe('ProfileQuerier', function () { const full = await querier.threadMarkers('t-0'); expect(full.totalMarkerCount).toBe(4); expect(full.filteredMarkerCount).toBe(4); + // Not zoomed: no full-range baseline is reported. + expect(full.fullRangeMarkerCount).toBeUndefined(); // Zoom to a window that only contains the marker at 20ms. const startName = querier._timestampManager.nameForTimestamp( @@ -727,6 +729,8 @@ describe('ProfileQuerier', function () { const zoomed = await querier.threadMarkers('t-0'); expect(zoomed.totalMarkerCount).toBe(1); expect(zoomed.filteredMarkerCount).toBe(1); + // Zoomed: the whole-profile baseline is surfaced alongside the in-view count. + expect(zoomed.fullRangeMarkerCount).toBe(4); const zoomedNames = zoomed.byType.map((t) => t.markerName); expect(zoomedNames).toContain('Beta'); expect(zoomedNames).not.toContain('Alpha');