Skip to content

Commit 7d18591

Browse files
Adding overview CU Progression graph
1 parent 0a1549c commit 7d18591

34 files changed

+1841
-206
lines changed

Diff for: package-lock.json

+214-37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"@ag-grid-community/react": "^32.2.0",
1717
"@ag-grid-community/styles": "^32.2.0",
1818
"@fontsource/inter-tight": "^5.1.0",
19-
"@nivo/core": "^0.87.0",
20-
"@nivo/pie": "^0.87.0",
19+
"@nivo/core": "^0.88.0",
20+
"@nivo/pie": "^0.88.0",
2121
"@radix-ui/react-icons": "^1.3.0",
2222
"@radix-ui/themes": "^3.1.3",
2323
"@react-spring/web": "^9.7.4",
@@ -43,6 +43,8 @@
4343
"react-intersection-observer": "^9.13.1",
4444
"react-use": "^17.5.1",
4545
"react-virtualized-auto-sizer": "^1.0.24",
46+
"recharts": "^2.15.1",
47+
"simplify-js": "^1.2.4",
4648
"use-debounce": "^10.0.3",
4749
"zod": "^3.23.8"
4850
},

Diff for: src/api/entities.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,15 @@ export const startupProgressSchema = z.object({
206206
waiting_for_supermajority_stake_percent: z.number().nullable(),
207207
});
208208

209+
export const computeUnitsSchema = z.object({
210+
max_compute_units: z.number(),
211+
start_timestamp_nanos: z.coerce.bigint(),
212+
target_end_timestamp_nanos: z.coerce.bigint(),
213+
compute_unit_timestamps_nanos: z.coerce.bigint().array(),
214+
compute_units_deltas: z.number().array(),
215+
active_bank_count: z.number().array(),
216+
});
217+
209218
export const slotLevelSchema = z.enum([
210219
"incomplete",
211220
"completed",
@@ -415,9 +424,10 @@ const tsTileTimersSchema = z.object({
415424

416425
export const slotResponseSchema = z.object({
417426
publish: slotPublishSchema,
418-
waterfall: txnWaterfallSchema.nullable(),
419-
tile_primary_metric: tilePrimaryMetricSchema.nullable(),
427+
waterfall: txnWaterfallSchema.nullable().optional(),
428+
tile_primary_metric: tilePrimaryMetricSchema.nullable().optional(),
420429
tile_timers: tsTileTimersSchema.array().nullable().optional(),
430+
compute_units: computeUnitsSchema.nullable().optional(),
421431
});
422432

423433
export const slotSkippedHistorySchema = z.number().array();

Diff for: src/api/types.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
versionSchema,
3737
voteDistanceSchema,
3838
voteStateSchema,
39+
computeUnitsSchema,
3940
} from "./entities";
4041

4142
export type Version = z.infer<typeof versionSchema>;
@@ -110,9 +111,11 @@ export interface Peer extends z.infer<typeof peerUpdateSchema> {
110111

111112
export type PeerRemove = z.infer<typeof peerRemoveSchema>;
112113

114+
export type ComputeUnits = z.infer<typeof computeUnitsSchema>;
115+
113116
export type SlotPublish = z.infer<typeof slotPublishSchema>;
114117

115-
export type SlotReponse = z.infer<typeof slotResponseSchema>;
118+
export type SlotResponse = z.infer<typeof slotResponseSchema>;
116119

117120
export type SkippedSlots = z.infer<typeof slotSkippedHistorySchema>;
118121

Diff for: src/api/useSetAtomWsData.ts

+25-24
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
EstimatedTps,
4545
LiveTilePrimaryMetric,
4646
LiveTxnWaterfall,
47+
SlotResponse,
4748
} from "./types";
4849
import { useThrottledCallback } from "use-debounce";
4950
import { useInterval } from "react-use";
@@ -128,6 +129,29 @@ export function useSetAtomWsData() {
128129

129130
const setBlockEngine = useSetAtom(blockEngineAtom);
130131

132+
const handleSlotUpdate = (value: SlotResponse) => {
133+
setSlotStatus(value.publish.slot, value.publish.level);
134+
135+
if (value.publish.mine) {
136+
if (value.publish.skipped) {
137+
setSkippedSlots((prev) =>
138+
[
139+
...(prev ?? []).filter((slot) => slot !== value.publish.slot),
140+
value.publish.slot,
141+
].sort()
142+
);
143+
} else {
144+
setSkippedSlots((prev) => {
145+
if (prev?.some((slot) => slot === value.publish.slot)) {
146+
return prev?.filter((slot) => slot !== value.publish.slot);
147+
} else {
148+
return prev;
149+
}
150+
});
151+
}
152+
}
153+
};
154+
131155
useServerMessages((msg) => {
132156
try {
133157
const { topic } = topicSchema.parse(msg);
@@ -231,31 +255,8 @@ export function useSetAtomWsData() {
231255
case "update":
232256
case "query": {
233257
if (value) {
234-
setSlotStatus(value.publish.slot, value.publish.level);
235258
setSlotResponse(value);
236-
237-
if (value.publish.mine) {
238-
if (value.publish.skipped) {
239-
setSkippedSlots((prev) =>
240-
[
241-
...(prev ?? []).filter(
242-
(slot) => slot !== value.publish.slot
243-
),
244-
value.publish.slot,
245-
].sort()
246-
);
247-
} else {
248-
setSkippedSlots((prev) => {
249-
if (prev?.some((slot) => slot === value.publish.slot)) {
250-
return prev?.filter(
251-
(slot) => slot !== value.publish.slot
252-
);
253-
} else {
254-
return prev;
255-
}
256-
});
257-
}
258-
}
259+
handleSlotUpdate(value);
259260
}
260261
break;
261262
}

Diff for: src/app.css

+1-11
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,7 @@
1515
/* headder height + epoch bar height + 10 padding top/bottom */
1616
--header-height: 121px;
1717

18-
/* --display-p3: blue;
19-
--gray-12: blue; */
20-
/* color(display-p3 0.93 0.933 0.94) */
21-
22-
/* --color-background: var(--gray-4); */
23-
/* background: blue; */
24-
/* background: #070B14; */
25-
/* --color-overlay: var(--black-a6);
26-
--color-panel-solid: white;
27-
--color-panel-translucent: rgba(255, 255, 255, 0.7);
28-
--color-surface: rgba(255, 255, 255, 0.85); */
18+
font-variant-numeric: tabular-nums;
2919
}
3020

3121
.rt-TooltipContent {

Diff for: src/atoms.ts

+15-23
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
PeerRemove,
1414
SkipRate,
1515
SlotLevel,
16-
SlotReponse,
16+
SlotResponse,
1717
} from "./api/types";
1818
import { merge } from "lodash";
1919
import { getLeaderSlots, getStake } from "./utils";
@@ -153,34 +153,26 @@ export const deleteSlotStatusBoundsAtom = atom(null, (get, set) => {
153153
}
154154
});
155155

156-
const hasQueryedAtom = atomWithImmer<Record<number, boolean>>({});
156+
const slotResponseAtom = atomWithImmer<Record<number, SlotResponse>>({});
157157

158-
export const getHasQueryedAtom = (slot?: number) =>
159-
atom((get) => (slot !== undefined ? !!get(hasQueryedAtom)[slot] : false));
160-
161-
export const setHasQueryedAtom = atom(null, (_, set, slot: number) => {
162-
set(hasQueryedAtom, (draft) => {
163-
draft[slot] = true;
164-
});
165-
});
166-
167-
export const deleteHasQueryedAtom = atom(null, (get, set, slot: number) => {
168-
set(hasQueryedAtom, (draft) => {
169-
delete draft[slot];
170-
});
171-
});
172-
173-
const slotResponseAtom = atomWithImmer<Record<number, SlotReponse>>({});
158+
export const getSlotPublishAtom = (slot?: number) =>
159+
atom((get) =>
160+
slot !== undefined ? get(slotResponseAtom)[slot]?.publish : undefined
161+
);
174162

175163
export const getSlotResponseAtom = (slot?: number) =>
176164
atom((get) => (slot !== undefined ? get(slotResponseAtom)[slot] : undefined));
177165

178166
export const setSlotResponseAtom = atom(
179167
null,
180-
(_, set, response: SlotReponse) => {
168+
(_, set, response: SlotResponse) => {
181169
const slot = response.publish.slot;
182170
set(slotResponseAtom, (draft) => {
171+
response.compute_units ??= draft[slot]?.compute_units;
172+
response.tile_primary_metric ??= draft[slot]?.tile_primary_metric;
183173
response.tile_timers ??= draft[slot]?.tile_timers;
174+
response.waterfall ??= draft[slot]?.waterfall;
175+
184176
draft[slot] = response;
185177
});
186178
}
@@ -215,7 +207,7 @@ export const deleteSlotResponseBoundsAtom = atom(null, (get, set) => {
215207
(numberVal < cacheSlotMin || numberVal > cacheSlotMax)
216208
) {
217209
delete draft[numberVal];
218-
set(deleteHasQueryedAtom, numberVal);
210+
// set(deleteHasQueryedAtom, numberVal);
219211
}
220212
}
221213
});
@@ -224,10 +216,10 @@ export const deleteSlotResponseBoundsAtom = atom(null, (get, set) => {
224216

225217
export const firstProcessedSlotAtom = atom((get) => {
226218
const startupProgress = get(startupProgressAtom);
227-
if(!(startupProgress?.downloading_incremental_snapshot_slot)) return;
219+
if (!startupProgress?.downloading_incremental_snapshot_slot) return;
228220

229-
return startupProgress.downloading_incremental_snapshot_slot + 1;
230-
})
221+
return startupProgress.downloading_incremental_snapshot_slot + 1;
222+
});
231223

232224
const _currentSlotAtom = atom<number | undefined>(undefined);
233225
export const currentSlotAtom = atom(

Diff for: src/features/EpochBar/EpochSlider.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
epochAtom,
2222
firstProcessedSlotAtom,
2323
} from "../../atoms";
24-
import { startupProgressAtom } from "../../api/atoms";
2524
import { useInterval, useMeasure } from "react-use";
2625
import { Epoch } from "../../api/types";
2726

Diff for: src/features/LeaderSchedule/Slots/CardValidatorSummary.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ import { useAtomValue } from "jotai";
1414
import PeerIcon from "../../../components/PeerIcon";
1515
import { identityKeyAtom } from "../../../api/atoms";
1616
import styles from "./cardValidatorSummary.module.css";
17-
import useSlotQuery from "../../../hooks/useSlotQuery";
1817
import { useHarmonicIntervalFn, useMedia, useUpdate } from "react-use";
1918
import { Peer } from "../../../api/types";
2019
import { peerStatsAtom } from "../../../atoms";
2120
import { formatNumber } from "../../../numUtils";
2221
import clsx from "clsx";
2322
import ArrowDropdown from "../../../components/ArrowDropdown";
23+
import { useSlotQueryPublish } from "../../../hooks/useSlotQuery";
2424

2525
interface CardValidatorSummaryProps {
2626
slot: number;
@@ -191,18 +191,18 @@ function ValidatorInfo({ peer }: ValidatorInfoProps) {
191191
}
192192

193193
function TimeAgo({ slot, showTime }: CardValidatorSummaryProps) {
194-
const query = useSlotQuery(slot);
194+
const query = useSlotQueryPublish(slot);
195195
const update = useUpdate();
196196

197197
useHarmonicIntervalFn(update, 1_000);
198198

199199
const slotDateTime = useMemo(() => {
200-
if (!query.slotResponse?.publish.completed_time_nanos) return;
200+
if (!query.publish?.completed_time_nanos) return;
201201

202202
return DateTime.fromMillis(
203-
Math.trunc(query.slotResponse.publish.completed_time_nanos / 1_000_000)
203+
Math.trunc(query.publish?.completed_time_nanos / 1_000_000)
204204
);
205-
}, [query.slotResponse]);
205+
}, [query.publish]);
206206

207207
const getDiffDuration = () => {
208208
if (!showTime || !slotDateTime) return;

Diff for: src/features/LeaderSchedule/Slots/PastSlotCard.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import SlotCardGrid from "./SlotCardGrid";
55
import CardValidatorSummary, {
66
CardValidatorSummaryMobile,
77
} from "./CardValidatorSummary";
8-
import useSlotQuery from "../../../hooks/useSlotQuery";
98
import { identityKeyAtom } from "../../../api/atoms";
109
import { useAtomValue } from "jotai";
1110
import { usePubKey } from "../../../hooks/usePubKey";
1211
import { useMedia } from "react-use";
1312
import clsx from "clsx";
13+
import { useSlotQueryPublish } from "../../../hooks/useSlotQuery";
1414

1515
interface PastSlotCardProps {
1616
slot: number;
@@ -21,15 +21,15 @@ export function PastSlotCard({ slot }: PastSlotCardProps) {
2121
const pubkey = usePubKey(slot);
2222
const isLeader = myPubkey === pubkey;
2323

24-
const response = useSlotQuery(slot);
25-
const response1 = useSlotQuery(slot + 1);
26-
const response2 = useSlotQuery(slot + 3);
27-
const response3 = useSlotQuery(slot + 3);
24+
const query = useSlotQueryPublish(slot);
25+
const query1 = useSlotQueryPublish(slot + 1);
26+
const query2 = useSlotQueryPublish(slot + 2);
27+
const query3 = useSlotQueryPublish(slot + 3);
2828
const isSkipped =
29-
response.slotResponse?.publish.skipped ||
30-
response1.slotResponse?.publish.skipped ||
31-
response2.slotResponse?.publish.skipped ||
32-
response3.slotResponse?.publish.skipped;
29+
query.publish?.skipped ||
30+
query1.publish?.skipped ||
31+
query2.publish?.skipped ||
32+
query3.publish?.skipped;
3333

3434
const isWideScreen = useMedia("(min-width: 900px)");
3535

Diff for: src/features/LeaderSchedule/Slots/SlotCardGrid.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "../../../atoms";
1010
import { useEffect, useMemo, useRef, useState } from "react";
1111
import "react-circular-progressbar/dist/styles.css";
12-
import useSlotQuery from "../../../hooks/useSlotQuery";
12+
import { useSlotQueryPublish } from "../../../hooks/useSlotQuery";
1313
import { SlotPublish } from "../../../api/types";
1414
import processedIcon from "../../../assets/checkOutline.svg";
1515
import optimisticalyConfirmedIcon from "../../../assets/checkFill.svg";
@@ -132,7 +132,7 @@ function SlotText({
132132
isCurrent,
133133
isWideScreen,
134134
}: SlotTextProps & { isWideScreen: boolean }) {
135-
const response = useSlotQuery(slot);
135+
const queryPublish = useSlotQueryPublish(slot);
136136

137137
return (
138138
<Flex
@@ -149,7 +149,7 @@ function SlotText({
149149
<Text>&nbsp;</Text>
150150
)}
151151
<StatusIcon slot={slot} isCurrent={isCurrent} />
152-
{response.slotResponse?.publish.skipped ? (
152+
{queryPublish.publish?.skipped ? (
153153
<Tooltip content="Slot was skipped">
154154
<img src={skippedIcon} alt="skipped" className={styles.icon} />
155155
</Tooltip>
@@ -181,7 +181,7 @@ interface SlotCardRowProps {
181181
function SlotCardRow({ slot, active }: SlotCardRowProps) {
182182
const firstProcessedSlot = useAtomValue(firstProcessedSlotAtom);
183183
const currentSlot = useAtomValue(currentSlotAtom);
184-
const response = useSlotQuery(slot);
184+
const queryPublish = useSlotQueryPublish(slot);
185185

186186
const [values, setValues] = useState<RowValues | undefined>();
187187

@@ -239,18 +239,18 @@ function SlotCardRow({ slot, active }: SlotCardRowProps) {
239239
};
240240
};
241241

242-
if (response.slotResponse?.publish) {
243-
setValues(getValues(response.slotResponse.publish));
242+
if (queryPublish.publish) {
243+
setValues(getValues(queryPublish.publish));
244244
}
245-
}, [response.slotResponse?.publish, slot]);
245+
}, [queryPublish.publish, slot]);
246246

247247
const isFuture = slot > (currentSlot ?? Infinity);
248248
const isCurrent = slot === currentSlot;
249249
const isSnapshot = slot < (firstProcessedSlot ?? 0);
250250

251251
const getText = (text?: string | number, suffix?: string) => {
252252
if (isFuture || isCurrent || isSnapshot) return "-";
253-
if (!values && !response.hasWaitedForData) return "Loading...";
253+
if (!values && !queryPublish.hasWaitedForData) return "Loading...";
254254
if (!values) return "-";
255255

256256
if (typeof text === "number") text = Math.round(text);

Diff for: src/features/LeaderSchedule/Slots/index.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export default function Slots() {
7070
const hammer = new Hammer(ref.current);
7171

7272
hammer.get("pan").set({ direction: Hammer.DIRECTION_VERTICAL });
73-
hammer.get("swipe").set({ direction: Hammer.DIRECTION_VERTICAL });
7473

7574
hammer.on("panup pandown", (e) => {
7675
if (!e.pointerType.includes("touch")) return;

0 commit comments

Comments
 (0)