Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/perps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- 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

- 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
Expand Down
11 changes: 7 additions & 4 deletions packages/perps-controller/src/PerpsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
}

// ============================================================================
Expand Down Expand Up @@ -4057,6 +4060,7 @@ export class PerpsController extends BaseController<
PerpsMeasurementName.PerpsMarketDataPreload,
performance.now() - preloadStart,
'millisecond',
traceId,
);
} catch (error) {
traceData = {
Expand Down Expand Up @@ -4198,12 +4202,9 @@ export class PerpsController extends BaseController<
provider: activeProvider,
isTestnet,
},
data: { userAddress },
});

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();
Expand All @@ -4221,6 +4222,7 @@ export class PerpsController extends BaseController<
PerpsMeasurementName.PerpsUserDataPreload,
performance.now() - preloadStart,
'millisecond',
traceId,
);
return;
}
Expand Down Expand Up @@ -4335,6 +4337,7 @@ export class PerpsController extends BaseController<
PerpsMeasurementName.PerpsUserDataPreload,
performance.now() - preloadStart,
'millisecond',
traceId,
);
} catch (error) {
traceData = {
Expand Down
14 changes: 13 additions & 1 deletion packages/perps-controller/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1937,8 +1937,20 @@ 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;
};

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.
Expand All @@ -1961,7 +1973,7 @@ export type PerpsTracer = {
data?: Record<string, PerpsTraceValue>;
}): void;

setMeasurement(name: string, value: number, unit: string): void;
setMeasurement: PerpsSetMeasurement;

addBreadcrumb(breadcrumb: {
category: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,26 @@ 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.diskCache.getItemSync as jest.Mock).mock.invocationCallOrder[0],
).toBeLessThan(onControllerConstructed.mock.invocationCallOrder[0]);
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(
Expand Down Expand Up @@ -2115,6 +2135,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,
);
});
});

Expand Down Expand Up @@ -2636,6 +2663,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',
Expand Down