Skip to content

Commit 1f16300

Browse files
feat: remaining seconds for startup phases
1 parent cb93c35 commit 1f16300

23 files changed

+371
-324
lines changed

src/atoms.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ export const peersAtom = atomWithImmer<Record<string, Peer>>({});
426426

427427
export const peersListAtom = atom((get) => Object.values(get(peersAtom)));
428428

429+
export const peersCountAtom = atom((get) => get(peersListAtom).length);
430+
429431
export const peersAtomFamily = atomFamily((peer?: string) =>
430432
atom((get) => (peer !== undefined ? get(peersAtom)[peer] : undefined)),
431433
);

src/features/StartupProgress/Firedancer/Body.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import {
66
isStartupProgressExpandedAtom,
77
showStartupProgressAtom,
88
} from "../atoms";
9-
import { Flex } from "@radix-ui/themes";
9+
import { Box, Flex } from "@radix-ui/themes";
1010
import clsx from "clsx";
11-
import { Header } from "./Header";
11+
import { Header } from "./PhaseHeader/Header";
1212
import { BootPhaseEnum } from "../../../api/entities";
1313
import { bootProgressContainerElAtom } from "../../../atoms";
1414
import Gossip from "./Gossip";
@@ -56,29 +56,30 @@ function BootProgressContent({ phase }: BootProgressContentProps) {
5656
const isNarrow = useMedia("(max-width: 750px)");
5757

5858
return (
59-
<Flex
59+
<Box
6060
ref={(el: HTMLDivElement) => setBootProgressContainerEl(el)}
61-
direction="column"
6261
overflowY="auto"
6362
className={clsx(styles.container, phaseClass, {
6463
[styles.collapsed]: !showStartupProgress || !isStartupProgressExpanded,
6564
})}
6665
>
6766
<Flex
6867
direction="column"
68+
height="100%"
6969
width="100%"
7070
maxWidth={appMaxWidth}
7171
mx="auto"
7272
px={isNarrow ? "20px" : "89px"}
73-
pb="20px"
7473
>
7574
<Header />
7675

7776
{phase === BootPhaseEnum.joining_gossip && <Gossip />}
7877
{(phase === BootPhaseEnum.loading_full_snapshot ||
7978
phase === BootPhaseEnum.loading_incremental_snapshot) && <Snapshot />}
8079
{phase === BootPhaseEnum.catching_up && <CatchingUp />}
80+
81+
<Box pb="20px" />
8182
</Flex>
82-
</Flex>
83+
</Box>
8384
);
8485
}

src/features/StartupProgress/Firedancer/CatchingUp/BarsStats.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,18 @@ import { useAtomValue } from "jotai";
33
import styles from "./catchingUp.module.css";
44
import { catchingUpStartSlotAtom, latestTurbineSlotAtom } from "./atoms";
55
import { completedSlotAtom } from "../../../../api/atoms";
6+
import type { CatchingUpRates } from "./useCatchingUpRates";
67

78
interface CatchingUpBarsProps {
8-
catchingUpRatesRef: React.MutableRefObject<{
9-
totalSlotsEstimate?: number;
10-
replaySlotsPerSecond?: number;
11-
turbineSlotsPerSecond?: number;
12-
}>;
9+
catchingUpRates: CatchingUpRates;
1310
}
14-
export function BarsStats({ catchingUpRatesRef }: CatchingUpBarsProps) {
11+
export function BarsStats({ catchingUpRates }: CatchingUpBarsProps) {
1512
const startSlot = useAtomValue(catchingUpStartSlotAtom);
1613
const latestTurbineSlot = useAtomValue(latestTurbineSlotAtom);
1714
const latestReplaySlot = useAtomValue(completedSlotAtom);
1815

19-
const replayRate = catchingUpRatesRef.current.replaySlotsPerSecond;
20-
const turbineHeadRate = catchingUpRatesRef.current.turbineSlotsPerSecond;
16+
const replayRate = catchingUpRates.replaySlotsPerSecond;
17+
const turbineHeadRate = catchingUpRates.turbineSlotsPerSecond;
2118
const catchUpRate =
2219
replayRate == null || turbineHeadRate == null
2320
? undefined

src/features/StartupProgress/Firedancer/CatchingUp/CatchingUpBars.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@ import { catchingUpBarsPlugin } from "./catchingUpBarsPlugin";
1414
import { Box } from "@radix-ui/themes";
1515
import { useThrottledCallback } from "use-debounce";
1616
import { completedSlotAtom } from "../../../../api/atoms";
17+
import type { CatchingUpRates } from "./useCatchingUpRates";
1718

1819
const emptyChartData: uPlot.AlignedData = [[0], [null]];
1920

2021
interface CatchingUpBarsProps {
21-
catchingUpRatesRef: React.MutableRefObject<{
22-
totalSlotsEstimate?: number;
23-
replaySlotsPerSecond?: number;
24-
turbineSlotsPerSecond?: number;
25-
}>;
22+
catchingUpRatesRef: React.MutableRefObject<CatchingUpRates>;
2623
}
2724
export function CatchingUpBars({ catchingUpRatesRef }: CatchingUpBarsProps) {
2825
const startSlot = useAtomValue(catchingUpStartSlotAtom);

src/features/StartupProgress/Firedancer/CatchingUp/catchingUp.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
padding: 14px;
66
gap: 14px;
77
border: 1px solid rgba(255, 255, 255, 0.1);
8-
background: rgba(234, 103, 103, 0.05);
8+
background: rgba(250, 250, 250, 0.05);
99
color: var(--boot-progress-primary-text-color);
1010
}
1111

src/features/StartupProgress/Firedancer/CatchingUp/index.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,60 @@ import { CatchingUpBars } from "./CatchingUpBars";
33
import { BarsFooter } from "./BarsFooter";
44
import BarsLabels from "./BarsLabels";
55
import { useAtomValue, useSetAtom } from "jotai";
6-
import { catchingUpContainerElAtom, hasCatchingUpDataAtom } from "./atoms";
6+
import {
7+
catchingUpContainerElAtom,
8+
catchingUpStartSlotAtom,
9+
hasCatchingUpDataAtom,
10+
latestTurbineSlotAtom,
11+
} from "./atoms";
712
import ShredsChart from "../../../Overview/ShredsProgression/ShredsChart";
813
import styles from "./catchingUp.module.css";
914
import CatchingUpTiles from "./CatchingUpTiles";
10-
import { PhaseHeader } from "../PhaseHeader";
15+
import PhaseHeader from "../PhaseHeader";
1116
import useEstimateTotalSlots from "./useCatchingUpRates";
1217
import { BarsStats } from "./BarsStats";
1318
import { ShredsChartLegend } from "../../../Overview/ShredsProgression/ShredsChartLegend";
19+
import { completedSlotAtom } from "../../../../api/atoms";
20+
import { useMemo } from "react";
1421

1522
export default function CatchingUp() {
1623
const setContainerEl = useSetAtom(catchingUpContainerElAtom);
1724
const hasCatchingUpData = useAtomValue(hasCatchingUpDataAtom);
1825
const catchingUpRatesRef = useEstimateTotalSlots();
1926

27+
const startSlot = useAtomValue(catchingUpStartSlotAtom);
28+
const latestTurbineSlot = useAtomValue(latestTurbineSlotAtom);
29+
const latestReplaySlot = useAtomValue(completedSlotAtom);
30+
31+
const phaseCompletePct = useMemo(() => {
32+
if (
33+
startSlot == null ||
34+
latestTurbineSlot == null ||
35+
latestReplaySlot == null
36+
) {
37+
return 0;
38+
}
39+
40+
const totalSlotsToReplay = latestTurbineSlot - startSlot + 1;
41+
if (!totalSlotsToReplay) return 0;
42+
43+
const replayedSlots = latestReplaySlot - startSlot + 1;
44+
return (100 * replayedSlots) / totalSlotsToReplay;
45+
}, [latestReplaySlot, latestTurbineSlot, startSlot]);
2046
return (
2147
<>
22-
<PhaseHeader phase="catching_up" />
48+
<PhaseHeader
49+
phase="catching_up"
50+
phaseCompletePct={phaseCompletePct}
51+
remainingSeconds={catchingUpRatesRef.current.remainingSeconds}
52+
/>
2353
<Flex direction="column" height="100%" mt="8px" gap="8px">
2454
{hasCatchingUpData && (
2555
<Flex ref={setContainerEl} direction="column" gap="5px">
2656
<BarsLabels />
2757
<CatchingUpBars catchingUpRatesRef={catchingUpRatesRef} />
2858
<BarsFooter />
29-
<BarsStats catchingUpRatesRef={catchingUpRatesRef} />
59+
<BarsStats catchingUpRates={catchingUpRatesRef.current} />
3060
</Flex>
3161
)}
3262

src/features/StartupProgress/Firedancer/CatchingUp/useCatchingUpRates.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
11
import { useAtomValue } from "jotai";
22
import { useRef, useEffect } from "react";
33
import { useInterval } from "react-use";
4-
import { useValuePerSecond } from "../useValuePerSecond";
54
import { completedSlotAtom } from "../../../../api/atoms";
65
import { catchingUpStartSlotAtom, latestTurbineSlotAtom } from "./atoms";
7-
8-
const rateCalcWindowMs = 10_000;
9-
6+
import { useEmaValue } from "../../../../hooks/useEma";
7+
8+
export interface CatchingUpRates {
9+
targetTotalSlotsEstimate?: number;
10+
totalSlotsEstimate?: number;
11+
replaySlotsPerSecond?: number;
12+
turbineSlotsPerSecond?: number;
13+
remainingSeconds?: number;
14+
}
1015
/**
1116
* Provides a ref that estimates how many slots will be replayed in total
1217
*/
1318
export default function useCatchingUpRates() {
14-
const catchingUpRatesRef = useRef<{
15-
totalSlotsEstimate?: number;
16-
replaySlotsPerSecond?: number;
17-
turbineSlotsPerSecond?: number;
18-
}>({});
19+
const catchingUpRatesRef = useRef<CatchingUpRates>({});
1920
const startSlot = useAtomValue(catchingUpStartSlotAtom);
2021
const latestTurbineSlot = useAtomValue(latestTurbineSlotAtom);
2122
const latestReplaySlot = useAtomValue(completedSlotAtom);
2223

2324
const replaySlot =
2425
latestReplaySlot ?? (startSlot == null ? undefined : startSlot - 1);
2526

26-
const { valuePerSecond: replayRate } = useValuePerSecond(
27-
replaySlot,
28-
rateCalcWindowMs,
29-
);
30-
const { valuePerSecond: turbineRate } = useValuePerSecond(
31-
latestTurbineSlot,
32-
rateCalcWindowMs,
33-
);
27+
const replayRate = useEmaValue(replaySlot);
28+
const turbineRate = useEmaValue(latestTurbineSlot);
29+
30+
catchingUpRatesRef.current.replaySlotsPerSecond = replayRate;
31+
catchingUpRatesRef.current.turbineSlotsPerSecond = turbineRate;
3432

3533
// initialize estimate of how many slots we'll need to replay
3634
// determines initial widths
@@ -44,7 +42,7 @@ export default function useCatchingUpRates() {
4442

4543
const replaySlotsPerSecond = 400;
4644
const turbineSlotsPerSecond = 100;
47-
const totalSlotsEsimtate = calculateTotalSlots(
45+
const totalSlotsEstimate = calculateTotalSlots(
4846
replaySlotsPerSecond,
4947
turbineSlotsPerSecond,
5048
startSlot,
@@ -53,9 +51,7 @@ export default function useCatchingUpRates() {
5351
);
5452

5553
catchingUpRatesRef.current = {
56-
totalSlotsEstimate: totalSlotsEsimtate,
57-
replaySlotsPerSecond,
58-
turbineSlotsPerSecond,
54+
totalSlotsEstimate,
5955
};
6056
}, [latestReplaySlot, latestTurbineSlot, startSlot, catchingUpRatesRef]);
6157

@@ -81,6 +77,18 @@ export default function useCatchingUpRates() {
8177
latestTurbineSlot,
8278
);
8379

80+
const remainingReplaySlots =
81+
latestReplaySlot == null || newEstimate == null
82+
? undefined
83+
: newEstimate + startSlot - 1 - latestReplaySlot;
84+
const remainingSeconds =
85+
replayRate === 0 || remainingReplaySlots == null
86+
? undefined
87+
: remainingReplaySlots / replayRate;
88+
89+
catchingUpRatesRef.current.remainingSeconds = remainingSeconds;
90+
91+
// only update total estimate (determining number of bars) if decreasing estimate
8492
if (!newEstimate || newEstimate >= prevEstimate) return;
8593

8694
// decrement gradually
@@ -90,11 +98,8 @@ export default function useCatchingUpRates() {
9098
);
9199

92100
const updatedEstimate = prevEstimate - diffToApply;
93-
catchingUpRatesRef.current = {
94-
totalSlotsEstimate: updatedEstimate,
95-
replaySlotsPerSecond: replayRate,
96-
turbineSlotsPerSecond: turbineRate,
97-
};
101+
102+
catchingUpRatesRef.current.totalSlotsEstimate = updatedEstimate;
98103
}, 500);
99104

100105
return catchingUpRatesRef;

src/features/StartupProgress/Firedancer/Gossip/index.tsx

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,76 @@ import { Card, Flex, Text } from "@radix-ui/themes";
33
import styles from "./gossip.module.css";
44
import { gossipNetworkStatsAtom } from "../../../../api/atoms";
55
import { useAtomValue } from "jotai";
6-
import { getFmtStake, formatBytesAsBits } from "../../../../utils";
6+
import { formatBytesAsBits } from "../../../../utils";
77
import { Bars } from "../Bars";
8-
import { PhaseHeader } from "../PhaseHeader";
8+
import PhaseHeader from "../PhaseHeader";
9+
import { useDebounce } from "use-debounce";
10+
import { lamportsPerSol } from "../../../../consts";
11+
import { compactZeroDecimalFormatter } from "../../../../numUtils";
12+
import { peersCountAtom } from "../../../../atoms";
13+
import { useEmaValue } from "../../../../hooks/useEma";
914

1015
const MAX_THROUGHPUT_BYTES = 1_8750_000; // 150Mbit
16+
const TOTAL_PEERS_COUNT = 5_000;
1117

1218
export default function Gossip() {
19+
const peersCount = useAtomValue(peersCountAtom);
20+
const phaseCompletePct = (peersCount / TOTAL_PEERS_COUNT) * 100;
21+
const peersCountRate = useEmaValue(peersCount);
22+
const remainingSeconds =
23+
peersCountRate === 0 ? undefined : TOTAL_PEERS_COUNT / peersCountRate;
24+
1325
const networkStats = useAtomValue(gossipNetworkStatsAtom);
14-
if (!networkStats) return null;
26+
const [dbNetworkStats] = useDebounce(networkStats, 100, {
27+
maxWait: 100,
28+
});
1529

16-
const { health, ingress, egress } = networkStats;
30+
if (!dbNetworkStats) return null;
1731

18-
const connectedStake =
19-
health.connected_stake == null ? null : getFmtStake(health.connected_stake);
32+
const { health, ingress, egress } = dbNetworkStats;
2033

21-
const ingressThroughput =
22-
ingress.total_throughput == null
23-
? undefined
24-
: formatBytesAsBits(ingress.total_throughput);
34+
const solConnectedStake = Number(health.connected_stake) / lamportsPerSol;
35+
const formattedConnectedStake =
36+
compactZeroDecimalFormatter.format(solConnectedStake);
2537

26-
const egressThroughput =
27-
egress.total_throughput == null
28-
? undefined
29-
: formatBytesAsBits(egress.total_throughput);
38+
const ingressThroughput = formatBytesAsBits(ingress.total_throughput);
39+
const egressThroughput = formatBytesAsBits(egress.total_throughput);
3040

3141
return (
3242
<>
33-
<PhaseHeader phase="joining_gossip" />
43+
<PhaseHeader
44+
phase="joining_gossip"
45+
phaseCompletePct={phaseCompletePct}
46+
remainingSeconds={remainingSeconds}
47+
/>
48+
3449
<Flex gapX="162px" mt="52px">
3550
<Flex direction="column" gap="20px" flexGrow="1" flexBasis="1">
3651
<Flex justify="between" gap="20px" align="stretch">
3752
<GossipCard
3853
title="Staked Peers"
39-
value={health.connected_staked_peers}
54+
value={health.connected_staked_peers.toLocaleString(undefined, {
55+
maximumFractionDigits: 0,
56+
})}
4057
/>
4158
<GossipCard
4259
title="Unstaked Peers"
43-
value={health.connected_unstaked_peers}
60+
value={health.connected_unstaked_peers.toLocaleString(undefined, {
61+
maximumFractionDigits: 0,
62+
})}
63+
/>
64+
<GossipCard
65+
title="Connected Stake"
66+
value={formattedConnectedStake}
4467
/>
45-
<GossipCard title="Connected Stake" value={connectedStake} />
4668
</Flex>
4769

4870
<Flex direction="column" gap="10px">
4971
<Text className={styles.barTitle}>Ingress</Text>
5072
<Text className={styles.barValue}>
5173
{ingressThroughput
52-
? `${ingressThroughput.value} ${ingressThroughput.unit}`
53-
: "-- Mbit"}
74+
? `${ingressThroughput.value} ${ingressThroughput.unit}/s`
75+
: "-- Mbit/s"}
5476
</Text>
5577
<Bars
5678
value={ingress.total_throughput ?? 0}
@@ -62,8 +84,8 @@ export default function Gossip() {
6284
<Text className={styles.barTitle}>Egress</Text>
6385
<Text className={styles.barValue}>
6486
{egressThroughput
65-
? `${egressThroughput.value} ${egressThroughput.unit}`
66-
: "-- Mbit"}
87+
? `${egressThroughput.value} ${egressThroughput.unit}/s`
88+
: "-- Mbit/s"}
6789
</Text>
6890
<Bars
6991
value={egress.total_throughput ?? 0}

0 commit comments

Comments
 (0)