Analytics: add performance metrics

This commit is contained in:
Darryl Yeo
2023-08-18 13:02:25 -07:00
parent 6e179191d8
commit c5e79980d1
5 changed files with 95 additions and 20 deletions
+23 -2
View File
@@ -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;
+22 -2
View File
@@ -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> = {};
+30 -6
View File
@@ -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
View File
@@ -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
}
+4
View File
@@ -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);
}