Skip to content

Commit 69498a3

Browse files
authored
Merge pull request #3330 from ava-labs/stats-overview-update
update: overview page on stats
2 parents 51fab43 + e386666 commit 69498a3

File tree

13 files changed

+3070
-2718
lines changed

13 files changed

+3070
-2718
lines changed

app/(home)/stats/overview/page.tsx

Lines changed: 363 additions & 181 deletions
Large diffs are not rendered by default.

app/api/chain-stats/[chainId]/route.ts

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import { TimeSeriesDataPoint, TimeSeriesMetric, ICMDataPoint, ICMMetric, STATS_C
44
getTimestampsFromTimeRange, createTimeSeriesMetric, createICMMetric } from "@/types/stats";
55

66
interface ChainMetrics {
7-
activeAddresses: TimeSeriesMetric;
7+
activeAddresses: {
8+
daily: TimeSeriesMetric;
9+
weekly: TimeSeriesMetric;
10+
monthly: TimeSeriesMetric;
11+
};
812
activeSenders: TimeSeriesMetric;
913
cumulativeAddresses: TimeSeriesMetric;
1014
cumulativeDeployers: TimeSeriesMetric;
@@ -96,7 +100,62 @@ async function getTimeSeriesData(
96100
}
97101
}
98102

99-
async function getICMData(chainId: string, timeRange: string, startTimestamp?: number, endTimestamp?: number): Promise<ICMDataPoint[]> {
103+
// Separate active addresses fetching with proper time intervals (optimize other metrics as needed)
104+
async function getActiveAddressesData(chainId: string, timeRange: string, interval: 'day' | 'week' | 'month', pageSize: number = 365, fetchAllPages: boolean = false): Promise<TimeSeriesDataPoint[]> {
105+
try {
106+
const { startTimestamp, endTimestamp } = getTimestampsFromTimeRange(timeRange);
107+
let allResults: any[] = [];
108+
109+
const avalanche = new Avalanche({
110+
network: "mainnet"
111+
});
112+
113+
const rlToken = process.env.METRICS_BYPASS_TOKEN || '';
114+
const params: any = {
115+
chainId: chainId,
116+
metric: 'activeAddresses',
117+
startTimestamp,
118+
endTimestamp,
119+
timeInterval: interval,
120+
pageSize,
121+
};
122+
123+
if (rlToken) { params.rltoken = rlToken; }
124+
125+
const result = await avalanche.metrics.chains.getMetrics(params);
126+
127+
for await (const page of result) {
128+
if (!page?.result?.results || !Array.isArray(page.result.results)) {
129+
console.warn(`Invalid page structure for activeAddresses (${interval}) on chain ${chainId}:`, page);
130+
continue;
131+
}
132+
133+
allResults = allResults.concat(page.result.results);
134+
135+
if (!fetchAllPages) {
136+
break;
137+
}
138+
}
139+
140+
return allResults
141+
.sort((a: any, b: any) => b.timestamp - a.timestamp)
142+
.map((result: any) => ({
143+
timestamp: result.timestamp,
144+
value: result.value || 0,
145+
date: new Date(result.timestamp * 1000).toISOString().split('T')[0]
146+
}));
147+
} catch (error) {
148+
console.warn(`Failed to fetch activeAddresses data for chain ${chainId} with interval ${interval}:`, error);
149+
return [];
150+
}
151+
}
152+
153+
async function getICMData(
154+
chainId: string,
155+
timeRange: string,
156+
startTimestamp?: number,
157+
endTimestamp?: number
158+
): Promise<ICMDataPoint[]> {
100159
try {
101160
let days: number;
102161

@@ -215,7 +274,7 @@ export async function GET(
215274
// Only refetch ICM data if timeRange changed (not for timestamp-based queries)
216275
if (startTimestamp === undefined && endTimestamp === undefined && cached.icmTimeRange !== timeRange) {
217276
try {
218-
const newICMData = await getICMData(chainId, timeRange);
277+
const newICMData = await getICMData(chainId, timeRange, startTimestamp, endTimestamp);
219278
cached.data.icmMessages = createICMMetric(newICMData);
220279
cached.icmTimeRange = timeRange;
221280
cachedData.set(cacheKey, cached);
@@ -243,7 +302,9 @@ export async function GET(
243302
const { pageSize, fetchAllPages } = config;
244303

245304
const [
246-
activeAddressesData,
305+
dailyActiveAddressesData,
306+
weeklyActiveAddressesData,
307+
monthlyActiveAddressesData,
247308
activeSendersData,
248309
cumulativeAddressesData,
249310
cumulativeDeployersData,
@@ -262,7 +323,9 @@ export async function GET(
262323
feesPaidData,
263324
icmData,
264325
] = await Promise.all([
265-
getTimeSeriesData('activeAddresses', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
326+
getActiveAddressesData(chainId, timeRange, 'day', pageSize, fetchAllPages),
327+
getActiveAddressesData(chainId, timeRange, 'week', pageSize, fetchAllPages),
328+
getActiveAddressesData(chainId, timeRange, 'month', pageSize, fetchAllPages),
266329
getTimeSeriesData('activeSenders', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
267330
getTimeSeriesData('cumulativeAddresses', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
268331
getTimeSeriesData('cumulativeDeployers', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
@@ -283,7 +346,11 @@ export async function GET(
283346
]);
284347

285348
const metrics: ChainMetrics = {
286-
activeAddresses: createTimeSeriesMetric(activeAddressesData),
349+
activeAddresses: {
350+
daily: createTimeSeriesMetric(dailyActiveAddressesData),
351+
weekly: createTimeSeriesMetric(weeklyActiveAddressesData),
352+
monthly: createTimeSeriesMetric(monthlyActiveAddressesData),
353+
},
287354
activeSenders: createTimeSeriesMetric(activeSendersData),
288355
cumulativeAddresses: createTimeSeriesMetric(cumulativeAddressesData),
289356
cumulativeDeployers: createTimeSeriesMetric(cumulativeDeployersData),
@@ -340,7 +407,7 @@ export async function GET(
340407
if (cached) {
341408
if (cached.icmTimeRange !== fallbackTimeRange) {
342409
try {
343-
const newICMData = await getICMData(chainId, fallbackTimeRange);
410+
const newICMData = await getICMData(chainId, fallbackTimeRange, undefined, undefined);
344411
cached.data.icmMessages = createICMMetric(newICMData);
345412
cached.icmTimeRange = fallbackTimeRange;
346413
cachedData.set(fallbackCacheKey, cached);

app/api/overview-stats/route.ts

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ interface ChainOverviewMetrics {
1313
chainName: string;
1414
chainLogoURI: string;
1515
txCount: TimeSeriesMetric;
16-
activeAddresses: TimeSeriesMetric;
16+
activeAddresses: {
17+
daily: TimeSeriesMetric;
18+
weekly: TimeSeriesMetric;
19+
monthly: TimeSeriesMetric;
20+
};
1721
icmMessages: ICMMetric;
1822
validatorCount: number | string;
1923
}
@@ -22,7 +26,11 @@ interface OverviewMetrics {
2226
chains: ChainOverviewMetrics[];
2327
aggregated: {
2428
totalTxCount: TimeSeriesMetric;
25-
totalActiveAddresses: TimeSeriesMetric;
29+
totalActiveAddresses: {
30+
daily: TimeSeriesMetric;
31+
weekly: TimeSeriesMetric;
32+
monthly: TimeSeriesMetric;
33+
};
2634
totalICMMessages: ICMMetric;
2735
totalValidators: number;
2836
activeChains: number;
@@ -89,10 +97,7 @@ async function getTimeSeriesData(
8997
}
9098

9199
// separate active addresses fetching with proper time intervals
92-
async function getActiveAddressesData(chainId: string, timeRange: string): Promise<TimeSeriesDataPoint[]> {
93-
const intervalMapping = STATS_CONFIG.ACTIVE_ADDRESSES_INTERVALS[timeRange as keyof typeof STATS_CONFIG.ACTIVE_ADDRESSES_INTERVALS];
94-
if (!intervalMapping) { return [] }
95-
100+
async function getActiveAddressesData(chainId: string, timeRange: string, interval: 'day' | 'week' | 'month'): Promise<TimeSeriesDataPoint[]> {
96101
try {
97102
const { startTimestamp, endTimestamp } = getTimestampsFromTimeRange(timeRange);
98103
let allResults: any[] = [];
@@ -102,7 +107,7 @@ async function getActiveAddressesData(chainId: string, timeRange: string): Promi
102107
metric: 'activeAddresses',
103108
startTimestamp,
104109
endTimestamp,
105-
timeInterval: intervalMapping,
110+
timeInterval: interval,
106111
pageSize: 1,
107112
};
108113

@@ -122,7 +127,7 @@ async function getActiveAddressesData(chainId: string, timeRange: string): Promi
122127
date: new Date(result.timestamp * 1000).toISOString().split('T')[0]
123128
}));
124129
} catch (error) {
125-
console.warn(`Failed to fetch active addresses data for chain ${chainId}:`, error);
130+
console.warn(`Failed to fetch active addresses data for chain ${chainId} with interval ${interval}:`, error);
126131
return [];
127132
}
128133
}
@@ -194,9 +199,11 @@ async function fetchChainMetrics(chain: any, timeRange: string): Promise<ChainOv
194199
const config = STATS_CONFIG.TIME_RANGES[timeRange as keyof typeof STATS_CONFIG.TIME_RANGES] || STATS_CONFIG.TIME_RANGES['30d'];
195200
const { pageSize, fetchAllPages } = config;
196201

197-
const [txCountData, activeAddressesData, icmData, validatorCount] = await Promise.all([
202+
const [txCountData, dailyAddresses, weeklyAddresses, monthlyAddresses, icmData, validatorCount] = await Promise.all([
198203
getTimeSeriesData('txCount', chain.chainId, timeRange, pageSize, fetchAllPages),
199-
getActiveAddressesData(chain.chainId, timeRange),
204+
getActiveAddressesData(chain.chainId, timeRange, 'day'),
205+
getActiveAddressesData(chain.chainId, timeRange, 'week'),
206+
getActiveAddressesData(chain.chainId, timeRange, 'month'),
200207
getICMData(chain.chainId, timeRange),
201208
getValidatorCount(chain.subnetId),
202209
]);
@@ -206,7 +213,11 @@ async function fetchChainMetrics(chain: any, timeRange: string): Promise<ChainOv
206213
chainName: chain.chainName,
207214
chainLogoURI: chain.logoUri,
208215
txCount: createTimeSeriesMetricWithPeriodSum(txCountData), // Period sum for overview
209-
activeAddresses: createTimeSeriesMetric(activeAddressesData),
216+
activeAddresses: {
217+
daily: createTimeSeriesMetric(dailyAddresses),
218+
weekly: createTimeSeriesMetric(weeklyAddresses),
219+
monthly: createTimeSeriesMetric(monthlyAddresses),
220+
},
210221
icmMessages: createICMMetricWithPeriodSum(icmData), // Period sum
211222
validatorCount,
212223
};
@@ -268,88 +279,127 @@ export async function GET(request: Request) {
268279
}
269280

270281
const aggregatedTxData: TimeSeriesDataPoint[] = [];
271-
const aggregatedAddressData: TimeSeriesDataPoint[] = [];
282+
const aggregatedDailyAddressData: TimeSeriesDataPoint[] = [];
283+
const aggregatedWeeklyAddressData: TimeSeriesDataPoint[] = [];
284+
const aggregatedMonthlyAddressData: TimeSeriesDataPoint[] = [];
272285
const aggregatedICMData: ICMDataPoint[] = [];
273286

274287
let totalValidators = 0;
275288
let activeChains = 0;
276289
let totalTxCountAllTime = 0;
277-
let totalActiveAddressesAllTime = 0;
290+
let totalDailyActiveAddressesAllTime = 0;
291+
let totalWeeklyActiveAddressesAllTime = 0;
292+
let totalMonthlyActiveAddressesAllTime = 0;
278293
let totalICMMessagesAllTime = 0;
279294

280-
const dateMap = new Map<string, { tx: number; addresses: number; icm: number }>();
295+
const dateMaps = {
296+
tx: new Map<string, number>(),
297+
daily: new Map<string, number>(),
298+
weekly: new Map<string, number>(),
299+
monthly: new Map<string, number>(),
300+
icm: new Map<string, number>(),
301+
};
281302

282303
chainMetrics.forEach(chain => {
283304
if (typeof chain.validatorCount === 'number') {
284305
totalValidators += chain.validatorCount;
285306
}
286307

287308
const hasTx = chain.txCount.data.length > 0 && typeof chain.txCount.current_value === 'number' && chain.txCount.current_value > 0;
288-
const hasAddresses = chain.activeAddresses.data.length > 0 && typeof chain.activeAddresses.current_value === 'number' && chain.activeAddresses.current_value > 0;
309+
const hasAddresses = chain.activeAddresses.daily.data.length > 0 && typeof chain.activeAddresses.daily.current_value === 'number' && chain.activeAddresses.daily.current_value > 0;
289310

290311
if (hasTx || hasAddresses) { activeChains++; }
291312

292313
chain.txCount.data.forEach(point => {
293314
const date = point.date;
294315
const value = typeof point.value === 'number' ? point.value : 0;
295-
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
296-
current.tx += value;
297-
dateMap.set(date, current);
316+
const current = dateMaps.tx.get(date) || 0;
317+
dateMaps.tx.set(date, current + value);
298318
totalTxCountAllTime += value;
299319
});
300320

301-
chain.activeAddresses.data.forEach(point => {
321+
chain.activeAddresses.daily.data.forEach(point => {
302322
const date = point.date;
303323
const value = typeof point.value === 'number' ? point.value : 0;
304-
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
305-
current.addresses += value;
306-
dateMap.set(date, current);
307-
totalActiveAddressesAllTime += value;
324+
const current = dateMaps.daily.get(date) || 0;
325+
dateMaps.daily.set(date, current + value);
326+
totalDailyActiveAddressesAllTime += value;
327+
});
328+
329+
chain.activeAddresses.weekly.data.forEach(point => {
330+
const date = point.date;
331+
const value = typeof point.value === 'number' ? point.value : 0;
332+
const current = dateMaps.weekly.get(date) || 0;
333+
dateMaps.weekly.set(date, current + value);
334+
totalWeeklyActiveAddressesAllTime += value;
335+
});
336+
337+
chain.activeAddresses.monthly.data.forEach(point => {
338+
const date = point.date;
339+
const value = typeof point.value === 'number' ? point.value : 0;
340+
const current = dateMaps.monthly.get(date) || 0;
341+
dateMaps.monthly.set(date, current + value);
342+
totalMonthlyActiveAddressesAllTime += value;
308343
});
309344

310345
chain.icmMessages.data.forEach(point => {
311346
const date = point.date;
312-
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
313-
current.icm += point.messageCount;
314-
dateMap.set(date, current);
347+
const current = dateMaps.icm.get(date) || 0;
348+
dateMaps.icm.set(date, current + point.messageCount);
315349
totalICMMessagesAllTime += point.messageCount;
316350
});
317351
});
318352

319-
Array.from(dateMap.entries()).forEach(([date, values]) => {
353+
Array.from(dateMaps.tx.entries()).forEach(([date, value]) => {
320354
const timestamp = Math.floor(new Date(date).getTime() / 1000);
321-
aggregatedTxData.push({
322-
timestamp,
323-
value: values.tx,
324-
date
325-
});
355+
aggregatedTxData.push({ timestamp, value, date });
356+
});
326357

327-
aggregatedAddressData.push({
328-
timestamp,
329-
value: values.addresses,
330-
date
331-
});
358+
Array.from(dateMaps.daily.entries()).forEach(([date, value]) => {
359+
const timestamp = Math.floor(new Date(date).getTime() / 1000);
360+
aggregatedDailyAddressData.push({ timestamp, value, date });
361+
});
362+
363+
Array.from(dateMaps.weekly.entries()).forEach(([date, value]) => {
364+
const timestamp = Math.floor(new Date(date).getTime() / 1000);
365+
aggregatedWeeklyAddressData.push({ timestamp, value, date });
366+
});
367+
368+
Array.from(dateMaps.monthly.entries()).forEach(([date, value]) => {
369+
const timestamp = Math.floor(new Date(date).getTime() / 1000);
370+
aggregatedMonthlyAddressData.push({ timestamp, value, date });
371+
});
332372

373+
Array.from(dateMaps.icm.entries()).forEach(([date, messageCount]) => {
374+
const timestamp = Math.floor(new Date(date).getTime() / 1000);
333375
aggregatedICMData.push({
334376
timestamp,
335377
date,
336-
messageCount: values.icm,
378+
messageCount,
337379
incomingCount: 0,
338380
outgoingCount: 0
339381
});
340382
});
341383

342384
// Sort by timestamp descending
343385
aggregatedTxData.sort((a, b) => b.timestamp - a.timestamp);
344-
aggregatedAddressData.sort((a, b) => b.timestamp - a.timestamp);
386+
aggregatedDailyAddressData.sort((a, b) => b.timestamp - a.timestamp);
387+
aggregatedWeeklyAddressData.sort((a, b) => b.timestamp - a.timestamp);
388+
aggregatedMonthlyAddressData.sort((a, b) => b.timestamp - a.timestamp);
345389
aggregatedICMData.sort((a, b) => b.timestamp - a.timestamp);
346390

347391
// Create aggregated metrics using period sum methods
348392
const totalTxMetric = createTimeSeriesMetricWithPeriodSum(aggregatedTxData);
349393
totalTxMetric.current_value = totalTxCountAllTime;
350394

351-
const totalAddressMetric = createTimeSeriesMetricWithPeriodSum(aggregatedAddressData);
352-
totalAddressMetric.current_value = totalActiveAddressesAllTime;
395+
const totalDailyAddressMetric = createTimeSeriesMetric(aggregatedDailyAddressData);
396+
totalDailyAddressMetric.current_value = totalDailyActiveAddressesAllTime;
397+
398+
const totalWeeklyAddressMetric = createTimeSeriesMetric(aggregatedWeeklyAddressData);
399+
totalWeeklyAddressMetric.current_value = totalWeeklyActiveAddressesAllTime;
400+
401+
const totalMonthlyAddressMetric = createTimeSeriesMetric(aggregatedMonthlyAddressData);
402+
totalMonthlyAddressMetric.current_value = totalMonthlyActiveAddressesAllTime;
353403

354404
const totalICMMetric = createICMMetricWithPeriodSum(aggregatedICMData);
355405
totalICMMetric.current_value = totalICMMessagesAllTime;
@@ -358,7 +408,11 @@ export async function GET(request: Request) {
358408
chains: chainMetrics,
359409
aggregated: {
360410
totalTxCount: totalTxMetric,
361-
totalActiveAddresses: totalAddressMetric,
411+
totalActiveAddresses: {
412+
daily: totalDailyAddressMetric,
413+
weekly: totalWeeklyAddressMetric,
414+
monthly: totalMonthlyAddressMetric,
415+
},
362416
totalICMMessages: totalICMMetric,
363417
totalValidators,
364418
activeChains

0 commit comments

Comments
 (0)