From fa23c7c6214412526619e179d328b77354f164d2 Mon Sep 17 00:00:00 2001 From: Arthur Breton Date: Tue, 18 Aug 2026 22:35:17 +0800 Subject: [PATCH 1/3] fix(perps): target preload performance measurements --- .../perps-controller/src/PerpsController.ts | 7 ++- packages/perps-controller/src/types/index.ts | 7 ++- .../PerpsController.providers-cache.test.ts | 53 +++++++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/packages/perps-controller/src/PerpsController.ts b/packages/perps-controller/src/PerpsController.ts index e6391f735c..8536b9c028 100644 --- a/packages/perps-controller/src/PerpsController.ts +++ b/packages/perps-controller/src/PerpsController.ts @@ -1236,6 +1236,9 @@ export class PerpsController extends BaseController< // Eagerly hydrate in-memory caches from disk so hooks see data on first render. // Must happen at construction time — before any React component mounts. this.#hydrateCacheFromDiskSync(); + this.#options.infrastructure.performance.onControllerConstructed?.( + this.#options.infrastructure.performance.now(), + ); } // ============================================================================ @@ -4057,6 +4060,7 @@ export class PerpsController extends BaseController< PerpsMeasurementName.PerpsMarketDataPreload, performance.now() - preloadStart, 'millisecond', + traceId, ); } catch (error) { traceData = { @@ -4198,7 +4202,6 @@ export class PerpsController extends BaseController< provider: activeProvider, isTestnet, }, - data: { userAddress }, }); this.#debugLog('PerpsController: Fetching user data in background', { @@ -4221,6 +4224,7 @@ export class PerpsController extends BaseController< PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond', + traceId, ); return; } @@ -4335,6 +4339,7 @@ export class PerpsController extends BaseController< PerpsMeasurementName.PerpsUserDataPreload, performance.now() - preloadStart, 'millisecond', + traceId, ); } catch (error) { traceData = { diff --git a/packages/perps-controller/src/types/index.ts b/packages/perps-controller/src/types/index.ts index a49ee71eb4..a6ca035197 100644 --- a/packages/perps-controller/src/types/index.ts +++ b/packages/perps-controller/src/types/index.ts @@ -1937,6 +1937,11 @@ export type PerpsStreamManager = { */ export type PerpsPerformance = { now(): number; + /** + * Optional platform hook invoked once after constructor disk hydration. + * Receives `performance.now()` — not a Sentry write. + */ + onControllerConstructed?(monotonicMs: number): void; }; /** @@ -1961,7 +1966,7 @@ export type PerpsTracer = { data?: Record; }): void; - setMeasurement(name: string, value: number, unit: string): void; + setMeasurement(name: string, value: number, unit: string, id?: string): void; addBreadcrumb(breadcrumb: { category: string; diff --git a/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts b/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts index 4e1d258757..7f84218816 100644 --- a/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts +++ b/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts @@ -1384,6 +1384,23 @@ describe('PerpsController', () => { ).toHaveLength(1); }); + it('publishes one construction timestamp after disk hydration and does not write Sentry at construct', () => { + const infra = createMockInfrastructure(); + (infra.performance.now as jest.Mock).mockReturnValue(321); + const onControllerConstructed = jest.fn(); + infra.performance.onControllerConstructed = onControllerConstructed; + + new TestablePerpsController({ + messenger: createMockMessenger(), + state: getDefaultPerpsControllerState(), + infrastructure: infra, + }); + + expect(onControllerConstructed).toHaveBeenCalledTimes(1); + expect(onControllerConstructed).toHaveBeenCalledWith(321); + expect(infra.tracer.setMeasurement).not.toHaveBeenCalled(); + }); + it('does not hydrate expired Terminal trend provenance from disk', () => { const infra = createMockInfrastructure(); (infra.diskCache.getItemSync as jest.Mock).mockImplementation( @@ -2115,6 +2132,13 @@ describe('PerpsController', () => { expect(mockInfrastructure.tracer.trace).toHaveBeenCalled(); expect(mockInfrastructure.tracer.endTrace).toHaveBeenCalled(); expect(mockInfrastructure.tracer.setMeasurement).toHaveBeenCalled(); + const traceId = mockInfrastructure.tracer.trace.mock.calls[0][0].id; + expect(mockInfrastructure.tracer.setMeasurement).toHaveBeenCalledWith( + expect.any(String), + expect.any(Number), + 'millisecond', + traceId, + ); }); }); @@ -2636,6 +2660,35 @@ describe('PerpsController', () => { ); }); + it('does not put userAddress on user-preload trace data and targets the named trace id', async () => { + preloadMockProvider.getMarketDataWithPrices.mockResolvedValue([]); + preloadMockProvider.getWebSocketConnectionState.mockReturnValue( + WSState.Disconnected, + ); + preloadController.testMarkInitialized(); + preloadController.testSetProviders( + new Map([['hyperliquid', preloadMockProvider]]), + ); + + preloadController.startMarketDataPreload(); + await jest.advanceTimersByTimeAsync(100); + + const userPreloadTrace = preloadInfrastructure.tracer.trace.mock.calls + .map((call) => call[0]) + .find((params) => params.name === 'Perps User Data Preload'); + expect(userPreloadTrace).toBeDefined(); + expect(userPreloadTrace?.data).toBeUndefined(); + expect(JSON.stringify(userPreloadTrace)).not.toContain( + mockEvmAccount.address, + ); + expect(preloadInfrastructure.tracer.setMeasurement).toHaveBeenCalledWith( + expect.any(String), + expect.any(Number), + 'millisecond', + userPreloadTrace?.id, + ); + }); + it.each([ [ 'provider', From 28560696661776053bcd529a596f6884ebc533a3 Mon Sep 17 00:00:00 2001 From: Arthur Breton Date: Tue, 18 Aug 2026 22:46:39 +0800 Subject: [PATCH 2/3] docs(perps): document trace targeting hooks --- packages/perps-controller/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index 75c648d587..089b9ca3a1 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add the optional `PerpsPerformance.onControllerConstructed` hook and optional trace ID parameter to `PerpsTracer.setMeasurement`, allowing clients to correlate controller construction and preload measurements with the intended trace ([#9906](https://github.com/MetaMask/core/pull/9906)) - Add `PERPS_EVENT_PROPERTY.PREVIOUS_LEVERAGE` (`previous_leverage`) for Perp UI Interaction `leverage_changed` events so clients can import the Segment property key from `@metamask/perps-controller` instead of a local interim constant ([#9881](https://github.com/MetaMask/core/pull/9881)) +### Changed + +- Target market and user preload measurements to their named traces, and omit wallet addresses from user-preload trace data ([#9906](https://github.com/MetaMask/core/pull/9906)) + ## [12.0.0] ### Added From 308f4c8b8c9a4fd2f2ced7d9b415b14c9240abb5 Mon Sep 17 00:00:00 2001 From: Arthur Breton Date: Wed, 19 Aug 2026 03:11:04 +0800 Subject: [PATCH 3/3] fix(perps): make trace targeting type-safe --- packages/perps-controller/CHANGELOG.md | 3 ++- packages/perps-controller/src/PerpsController.ts | 4 +--- packages/perps-controller/src/types/index.ts | 11 +++++++++-- .../tests/src/PerpsController.providers-cache.test.ts | 3 +++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index 089b9ca3a1..20a6b08251 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add the optional `PerpsPerformance.onControllerConstructed` hook and optional trace ID parameter to `PerpsTracer.setMeasurement`, allowing clients to correlate controller construction and preload measurements with the intended trace ([#9906](https://github.com/MetaMask/core/pull/9906)) +- Add the optional `PerpsPerformance.onControllerConstructed` post-hydration timestamp hook ([#9906](https://github.com/MetaMask/core/pull/9906)) +- Add an explicit trace ID overload to `PerpsTracer.setMeasurement`, allowing clients to target preload measurements to their named trace ([#9906](https://github.com/MetaMask/core/pull/9906)) - Add `PERPS_EVENT_PROPERTY.PREVIOUS_LEVERAGE` (`previous_leverage`) for Perp UI Interaction `leverage_changed` events so clients can import the Segment property key from `@metamask/perps-controller` instead of a local interim constant ([#9881](https://github.com/MetaMask/core/pull/9881)) ### Changed diff --git a/packages/perps-controller/src/PerpsController.ts b/packages/perps-controller/src/PerpsController.ts index 8536b9c028..9e3c03712d 100644 --- a/packages/perps-controller/src/PerpsController.ts +++ b/packages/perps-controller/src/PerpsController.ts @@ -4204,9 +4204,7 @@ export class PerpsController extends BaseController< }, }); - this.#debugLog('PerpsController: Fetching user data in background', { - userAddress, - }); + this.#debugLog('PerpsController: Fetching user data in background'); if (activeProvider === 'hyperliquid') { const snapshot = await this.getUserDataSnapshot(); diff --git a/packages/perps-controller/src/types/index.ts b/packages/perps-controller/src/types/index.ts index a6ca035197..a5e425843b 100644 --- a/packages/perps-controller/src/types/index.ts +++ b/packages/perps-controller/src/types/index.ts @@ -1941,9 +1941,16 @@ export type PerpsPerformance = { * Optional platform hook invoked once after constructor disk hydration. * Receives `performance.now()` — not a Sentry write. */ - onControllerConstructed?(monotonicMs: number): void; + onControllerConstructed?: (monotonicMs: number) => void; }; +type PerpsSetMeasurement = (( + name: string, + value: number, + unit: string, +) => void) & + ((name: string, value: number, unit: string, id: string) => void); + /** * Injectable tracer interface for Sentry/observability tracing. * Services use this to create spans and measure operation durations. @@ -1966,7 +1973,7 @@ export type PerpsTracer = { data?: Record; }): void; - setMeasurement(name: string, value: number, unit: string, id?: string): void; + setMeasurement: PerpsSetMeasurement; addBreadcrumb(breadcrumb: { category: string; diff --git a/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts b/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts index 7f84218816..ec40189984 100644 --- a/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts +++ b/packages/perps-controller/tests/src/PerpsController.providers-cache.test.ts @@ -1398,6 +1398,9 @@ describe('PerpsController', () => { expect(onControllerConstructed).toHaveBeenCalledTimes(1); expect(onControllerConstructed).toHaveBeenCalledWith(321); + expect( + (infra.diskCache.getItemSync as jest.Mock).mock.invocationCallOrder[0], + ).toBeLessThan(onControllerConstructed.mock.invocationCallOrder[0]); expect(infra.tracer.setMeasurement).not.toHaveBeenCalled(); });