Analytics: add performance metrics
This commit is contained in:
@@ -85,6 +85,8 @@ export enum AnalyticsEvent {
|
||||
// Trading
|
||||
TradeOrderTypeSelected = 'TradeOrderTypeSelected',
|
||||
TradePlaceOrder = 'TradePlaceOrder',
|
||||
TradePlaceOrderConfirmed = 'TradePlaceOrderConfirmed',
|
||||
TradeCancelOrderConfirmed = 'TradeCancelOrderConfirmed',
|
||||
}
|
||||
|
||||
export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
@@ -94,6 +96,12 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
: T extends AnalyticsEvent.NetworkStatus ?
|
||||
{
|
||||
status: typeof AbacusApiStatus['name'];
|
||||
/** Last time indexer node was queried successfully */
|
||||
lastSuccessfulIndexerRpcQuery?: number;
|
||||
/** Time elapsed since indexer node was queried successfully */
|
||||
elapsedTime?: number;
|
||||
blockHeight?: number;
|
||||
indexerBlockHeight?: number;
|
||||
}
|
||||
|
||||
// Navigation
|
||||
@@ -151,6 +159,19 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
SubaccountPlaceOrderPayload & {
|
||||
isClosePosition: boolean;
|
||||
}
|
||||
|
||||
: T extends AnalyticsEvent.TradePlaceOrderConfirmed ?
|
||||
{
|
||||
/** roundtrip time between user placing an order and confirmation from indexer (client → validator → indexer → client) */
|
||||
roundtripMs: number;
|
||||
/** URL/IP of node the order was sent to */
|
||||
validator: string;
|
||||
}
|
||||
: T extends AnalyticsEvent.TradeCancelOrderConfirmed ?
|
||||
{
|
||||
/** roundtrip time between user canceling an order and confirmation from indexer (client → validator → indexer → client) */
|
||||
roundtripMs: number;
|
||||
/** URL/IP of node the order was sent to */
|
||||
validator: string;
|
||||
}
|
||||
:
|
||||
{};
|
||||
never;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useApiState } from './useApiState';
|
||||
import { useBreakpoints } from './useBreakpoints';
|
||||
import { useSelectedNetwork } from './useSelectedNetwork';
|
||||
import { useAccounts } from './useAccounts';
|
||||
import { useDydxClient } from './useDydxClient';
|
||||
|
||||
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||
import { getOnboardingState, getSubaccountId } from '@/state/accountSelectors';
|
||||
@@ -22,6 +23,7 @@ import { getInputTradeData } from '@/state/inputsSelectors';
|
||||
|
||||
export const useAnalytics = () => {
|
||||
const { walletType, walletConnectionType, evmAddress, dydxAddress, selectedWalletType } = useAccounts();
|
||||
const { compositeClient } = useDydxClient();
|
||||
|
||||
/** User properties */
|
||||
|
||||
@@ -95,10 +97,25 @@ export const useAnalytics = () => {
|
||||
}, []);
|
||||
|
||||
// AnalyticsEvent.NetworkStatus
|
||||
const { status } = useApiState();
|
||||
const { height, indexerHeight, status } = useApiState();
|
||||
|
||||
useEffect(() => {
|
||||
if (status) track(AnalyticsEvent.NetworkStatus, { status: status.name });
|
||||
if (status) {
|
||||
const websocketEndpoint = compositeClient?.indexerClient?.config.websocketEndpoint;
|
||||
|
||||
const lastSuccessfulIndexerRpcQuery =
|
||||
(websocketEndpoint &&
|
||||
lastSuccessfulWebsocketRequestByOrigin[new URL(websocketEndpoint).origin]) ||
|
||||
undefined;
|
||||
|
||||
track(AnalyticsEvent.NetworkStatus, {
|
||||
status: status.name,
|
||||
lastSuccessfulIndexerRpcQuery,
|
||||
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
|
||||
blockHeight: height ?? undefined,
|
||||
indexerBlockHeight: indexerHeight ?? undefined,
|
||||
});
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
// AnalyticsEvent.NavigatePage
|
||||
@@ -191,3 +208,6 @@ export const useAnalytics = () => {
|
||||
}
|
||||
}, [selectedOrderType]);
|
||||
};
|
||||
|
||||
export const lastSuccessfulRestRequestByOrigin: Record<URL['origin'], number> = {};
|
||||
export const lastSuccessfulWebsocketRequestByOrigin: Record<URL['origin'], number> = {};
|
||||
|
||||
@@ -235,8 +235,10 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
execution: OrderExecution;
|
||||
postOnly: boolean;
|
||||
reduceOnly: boolean;
|
||||
}) =>
|
||||
await compositeClient?.placeOrder(
|
||||
}) => {
|
||||
const startTimestamp = performance.now();
|
||||
|
||||
const result = await compositeClient?.placeOrder(
|
||||
subaccount,
|
||||
marketId,
|
||||
type,
|
||||
@@ -249,7 +251,17 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
execution,
|
||||
postOnly,
|
||||
reduceOnly
|
||||
),
|
||||
);
|
||||
|
||||
const endTimestamp = performance.now();
|
||||
|
||||
track(AnalyticsEvent.TradePlaceOrderConfirmed, {
|
||||
roundtripMs: endTimestamp - startTimestamp,
|
||||
validator: compositeClient!.validatorClient.config.restEndpoint,
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
cancelOrderForSubaccount: async ({
|
||||
subaccount,
|
||||
@@ -265,15 +277,27 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
clobPairId: number;
|
||||
goodTilBlock?: number;
|
||||
goodTilBlockTime?: number;
|
||||
}) =>
|
||||
await compositeClient?.cancelOrder(
|
||||
}) => {
|
||||
const startTimestamp = performance.now();
|
||||
|
||||
const result = await compositeClient?.cancelOrder(
|
||||
subaccount,
|
||||
clientId,
|
||||
orderFlags,
|
||||
clobPairId,
|
||||
goodTilBlock,
|
||||
goodTilBlockTime
|
||||
),
|
||||
)
|
||||
|
||||
const endTimestamp = performance.now();
|
||||
|
||||
track(AnalyticsEvent.TradeCancelOrderConfirmed, {
|
||||
roundtripMs: endTimestamp - startTimestamp,
|
||||
validator: compositeClient!.validatorClient.config.restEndpoint,
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
}),
|
||||
[compositeClient]
|
||||
);
|
||||
|
||||
+16
-10
@@ -2,6 +2,8 @@ import type { Nullable, kollections } from '@dydxprotocol/abacus';
|
||||
|
||||
import type { AbacusRestProtocol } from '@/constants/abacus';
|
||||
|
||||
import { lastSuccessfulRestRequestByOrigin } from '@/hooks/useAnalytics';
|
||||
|
||||
type Headers = Nullable<kollections.Map<string, string>>;
|
||||
type FetchResponseCallback = (p0: Nullable<string>, p1: number) => void;
|
||||
|
||||
@@ -46,16 +48,20 @@ class AbacusRest implements AbacusRestProtocol {
|
||||
};
|
||||
|
||||
fetch(url, options)
|
||||
.then((response) =>
|
||||
response.text().then((data) => {
|
||||
if (response.ok) {
|
||||
callback(data, response.status);
|
||||
} else {
|
||||
// response not OK, call callback with null data and the status, this includes 400/500 status codes
|
||||
callback(null, response.status);
|
||||
}
|
||||
})
|
||||
)
|
||||
.then(async (response) => {
|
||||
const data = await response.text();
|
||||
|
||||
if (response.ok) {
|
||||
callback(data, response.status);
|
||||
} else {
|
||||
// response not OK, call callback with null data and the status, this includes 400/500 status codes
|
||||
callback(null, response.status);
|
||||
}
|
||||
|
||||
try {
|
||||
lastSuccessfulRestRequestByOrigin[new URL(url).origin] = Date.now();
|
||||
} catch {}
|
||||
})
|
||||
.catch(() => callback(null, 0)); // Network error or request couldn't be made
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
||||
import { mapCandle } from '@/lib/tradingView/utils';
|
||||
|
||||
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
||||
|
||||
import { log } from '../telemetry';
|
||||
|
||||
const RECONNECT_INTERVAL_MS = 10_000;
|
||||
@@ -154,6 +156,8 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
||||
this.receivedCallback(m.data);
|
||||
}
|
||||
}
|
||||
|
||||
lastSuccessfulWebsocketRequestByOrigin[new URL(this.url!).origin] = Date.now();
|
||||
} catch (error) {
|
||||
log('AbacusWebsocketProtocol/onmessage', error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user