From 665f4b7d1635375ced00f8746e6ec9161392707e Mon Sep 17 00:00:00 2001 From: Amy Liu Date: Tue, 25 Nov 2025 18:00:36 -0600 Subject: [PATCH] chore: slot detail stats density and layout --- src/colors.ts | 1 + src/components/Card.tsx | 9 +- src/components/card.module.css | 7 +- src/consts.ts | 2 +- .../Overview/SlotPerformance/TileCard.tsx | 16 ++- .../SlotPerformance/TileSparkLine.tsx | 6 +- .../SlotPerformance/tileCard.module.css | 9 ++ .../ComputeSection/BusyAccounts.tsx | 8 +- .../ComputeSection/ComputeUnitStats.tsx | 30 ++--- .../CumulativeExecutionTimeStats.tsx | 41 ++++--- .../ComputeSection/ExecutionTime.tsx | 26 ++--- .../ComputeSection/ProtocolLimitStats.tsx | 7 +- .../ComputeSection/index.tsx | 15 +-- .../FeeSection/FeeBreakdownStats.tsx | 30 ++--- .../FeeSection/IncomeDistributionCharts.tsx | 5 +- .../IncomeScatterChart.tsx | 6 +- .../FeeSection/IncomeScatterCharts/index.tsx | 3 +- .../FeeSection/distributionBars.module.css | 3 +- .../DetailedSlotStats/FeeSection/index.tsx | 11 +- .../DetailedSlotStats/PctBarRow.tsx | 30 ++--- .../PerformanceSection/CpuSparklines.tsx | 39 ++++--- .../PackBufferChartTooltip.tsx | 8 +- .../PackBufferChart/index.tsx | 14 ++- .../PerformanceSection/ScheduleOutcomes.tsx | 110 ------------------ .../PerformanceSection/ScheduleStats.tsx | 93 +++++++++++++++ .../PerformanceSection/SlotDurationStats.tsx | 14 +-- .../TimeSinceLastLeaderStats.tsx | 37 +++--- .../TxnExecutionDurationCharts/index.tsx | 40 +++++-- .../PerformanceSection/index.tsx | 24 ++-- .../scheduleOutcomes.module.css | 9 -- .../DetailedSlotStats/SlotDetailsHeader.tsx | 40 ++----- .../DetailedSlotStats/SlotDetailsSection.tsx | 20 +++- .../SlotDetailsSubSection.tsx | 3 +- .../SlotDetails/DetailedSlotStats/consts.ts | 5 + .../detailedSlotStats.module.css | 59 ++++++++++ .../SlotDetails/DetailedSlotStats/index.tsx | 3 +- .../SlotDetails/slotNavigation.module.css | 4 +- 37 files changed, 437 insertions(+), 350 deletions(-) delete mode 100644 src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleOutcomes.tsx create mode 100644 src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleStats.tsx delete mode 100644 src/features/SlotDetails/DetailedSlotStats/PerformanceSection/scheduleOutcomes.module.css create mode 100644 src/features/SlotDetails/DetailedSlotStats/consts.ts create mode 100644 src/features/SlotDetails/DetailedSlotStats/detailedSlotStats.module.css diff --git a/src/colors.ts b/src/colors.ts index 1fd4a138..c0d813f4 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -262,6 +262,7 @@ export const slotDetailsSelectedColor = "#e3efff"; export const slotDetailsDisabledSlotBorderColor = "#484D53B2"; export const slotDetailsStatsPrimary = "var(--gray-11)"; export const slotDetailsStatsSecondary = "var(--gray-10)"; +export const slotDetailsStatsTertiary = "var(--gray-12)"; // slots list export const slotsListSlotBackgroundColor = "#070A13"; diff --git a/src/components/Card.tsx b/src/components/Card.tsx index d40e1838..b5fa4809 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -4,17 +4,20 @@ import type { PropsWithChildren, HTMLAttributes } from "react"; interface CardProps { hideChildren?: boolean; - includeBg?: boolean; + isNarrow?: boolean; } export default function Card({ children, hideChildren, - includeBg = true, + isNarrow = false, ...props }: PropsWithChildren>) { return ( -
+
{!hideChildren && children}
); diff --git a/src/components/card.module.css b/src/components/card.module.css index 21a3dcac..ca169732 100644 --- a/src/components/card.module.css +++ b/src/components/card.module.css @@ -1,9 +1,10 @@ .card { + padding: 10px; border-radius: 8px; border: 1px solid var(--container-border-color); - padding: 8px 16px; + background: var(--container-background-color); - &.bg { - background: var(--container-background-color); + &.narrow { + padding: 4px; } } diff --git a/src/consts.ts b/src/consts.ts index d8534d38..221d3459 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -62,7 +62,7 @@ export const txnErrorCodeMap: Record = { export const nonBreakingSpace = "\u00A0"; export const clusterIndicatorHeight = 5; -export const slotNavHeight = 29; +export const slotNavHeight = 30; export const headerHeight = 48; export const headerSpacing = 13; diff --git a/src/features/Overview/SlotPerformance/TileCard.tsx b/src/features/Overview/SlotPerformance/TileCard.tsx index d4d95751..ff2165c6 100644 --- a/src/features/Overview/SlotPerformance/TileCard.tsx +++ b/src/features/Overview/SlotPerformance/TileCard.tsx @@ -11,6 +11,7 @@ import TileSparkLineExpandedContainer from "./TileSparkLineExpandedContainer"; import { useMeasure } from "react-use"; import type React from "react"; import { useLastDefinedValue, useTileSparkline } from "./useTileSparkline"; +import clsx from "clsx"; interface TileCardProps { header: string; @@ -23,7 +24,8 @@ interface TileCardProps { sparklineHeight?: number; isExpanded?: boolean; setIsExpanded?: (isExpanded: boolean) => void; - includeBg?: boolean; + isDark?: boolean; + isNarrow?: boolean; } export default function TileCard({ @@ -37,7 +39,8 @@ export default function TileCard({ sparklineHeight, isExpanded = false, setIsExpanded = () => {}, - includeBg = true, + isDark = false, + isNarrow, }: TileCardProps) { const [ref, { width }] = useMeasure(); @@ -60,7 +63,10 @@ export default function TileCard({ return ( - + ); diff --git a/src/features/Overview/SlotPerformance/TileSparkLine.tsx b/src/features/Overview/SlotPerformance/TileSparkLine.tsx index c64b2765..20466801 100644 --- a/src/features/Overview/SlotPerformance/TileSparkLine.tsx +++ b/src/features/Overview/SlotPerformance/TileSparkLine.tsx @@ -23,14 +23,14 @@ interface TileParkLineProps { value?: number; queryBusy?: number[]; height?: number; - includeBg?: boolean; + background?: string; } export default function TileSparkLine({ value, queryBusy, height = 24, - includeBg = true, + background, }: TileParkLineProps) { const [svgRef, { width }] = useMeasure(); @@ -50,7 +50,7 @@ export default function TileSparkLine({ scaledDataPoints={scaledDataPoints} range={range} height={height} - background={includeBg ? undefined : "unset"} + background={background} pxPerTick={pxPerTick} tickMs={chartTickMs} isLive={isLive} diff --git a/src/features/Overview/SlotPerformance/tileCard.module.css b/src/features/Overview/SlotPerformance/tileCard.module.css index e8899e09..c3e426a0 100644 --- a/src/features/Overview/SlotPerformance/tileCard.module.css +++ b/src/features/Overview/SlotPerformance/tileCard.module.css @@ -2,6 +2,15 @@ color: var(--dropdown-button-text-color); } +.full-width { + width: 100%; +} + +.dark { + background: #171b24; + border-color: transparent; +} + .subHeader { color: var(--tile-sub-header-color); font-size: 12px; diff --git a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/BusyAccounts.tsx b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/BusyAccounts.tsx index 6afb802e..feaf9090 100644 --- a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/BusyAccounts.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/BusyAccounts.tsx @@ -4,6 +4,7 @@ import { useSlotQueryResponseDetailed } from "../../../../hooks/useSlotQuery"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; import PctBarRow from "../PctBarRow"; import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; +import { gridGapX, gridGapY } from "../consts"; export default function BusyAccounts() { const selectedSlot = useAtomValue(selectedSlotAtom); @@ -14,7 +15,11 @@ export default function BusyAccounts() { return ( - + {limits.used_account_write_costs.map(({ account, cost }) => ( ))} diff --git a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ComputeUnitStats.tsx b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ComputeUnitStats.tsx index 06dd40cf..239032e8 100644 --- a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ComputeUnitStats.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ComputeUnitStats.tsx @@ -7,11 +7,11 @@ import { computeUnitsColor, headerColor, nonVoteColor, - slotDetailsStatsPrimary, - slotDetailsStatsSecondary, votesColor, } from "../../../../colors"; import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; +import styles from "../detailedSlotStats.module.css"; +import { gridGapX, gridGapY } from "../consts"; const bundleColor = headerColor; @@ -53,7 +53,11 @@ export default function ComputeUnitStats() { return ( - + - - {label} - - + {label} + {cus.toLocaleString()} - - / - - - {totalCus.toLocaleString()} - + / + {totalCus.toLocaleString()} + + {cuPctFromTotal}% - +
- + Success+Landed - + Failed+Landed - + Unlanded - + {label} - - {`${landedSuccessUnits.value} ${landedSuccessUnits.unit}`} + + {landedSuccessUnits.value} + {landedSuccessUnits.unit} - - {`${landedFailedUnits.value} ${landedFailedUnits.unit}`} + + {landedFailedUnits.value} + {landedFailedUnits.unit} - - {`${unlandedUnits.value} ${unlandedUnits.unit}`} + + {unlandedUnits.value} + {unlandedUnits.unit} diff --git a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ExecutionTime.tsx b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ExecutionTime.tsx index 6f1be30a..9c3cb370 100644 --- a/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ExecutionTime.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/ComputeSection/ExecutionTime.tsx @@ -3,14 +3,12 @@ import { useAtomValue } from "jotai"; import { useMemo } from "react"; import { useSlotQueryResponseTransactions } from "../../../../hooks/useSlotQuery"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; -import { - nonVoteColor, - slotDetailsStatsSecondary, - votesColor, -} from "../../../../colors"; +import { nonVoteColor, votesColor } from "../../../../colors"; import { getDurationWithUnits } from "../../../Overview/SlotPerformance/TransactionBarsCard/chartUtils"; import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; import MonoText from "../../../../components/MonoText"; +import styles from "../detailedSlotStats.module.css"; +import { gridGapX, gridGapY } from "../consts"; export default function ExecutionTime() { const selectedSlot = useAtomValue(selectedSlotAtom); @@ -113,7 +111,7 @@ export default function ExecutionTime() { return ( - + - - {label} - - + {label} + {minFormatted.value} {minFormatted.unit} - / - + / + {formatted.value} {formatted.unit} - / - + / + {maxFormatted.value} {maxFormatted.unit} - + - - - - - - - + + + + + + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/FeeSection/FeeBreakdownStats.tsx b/src/features/SlotDetails/DetailedSlotStats/FeeSection/FeeBreakdownStats.tsx index 02293ee6..2fab8ef3 100644 --- a/src/features/SlotDetails/DetailedSlotStats/FeeSection/FeeBreakdownStats.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/FeeSection/FeeBreakdownStats.tsx @@ -5,7 +5,6 @@ import { tipsColor, feesColor, incomePerCuToggleControlColor, - slotDetailsStatsPrimary, } from "../../../../colors"; import { useSlotQueryResponseTransactions } from "../../../../hooks/useSlotQuery"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; @@ -13,6 +12,8 @@ import { getPaidTxnFees, getPaidTxnTips } from "../../../../utils"; import { formatNumberLamports } from "../../../Overview/ValidatorsCard/formatAmt"; import { solDecimals } from "../../../../consts"; import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; +import styles from "../detailedSlotStats.module.css"; +import { gridGapX, gridGapY } from "../consts"; export const defaultMaxValue = 100_000_000; @@ -53,7 +54,7 @@ export default function FeeBreakdownStats() { return ( - + @@ -76,10 +77,8 @@ function TipsRow({ label, value, total, color }: TipsRowProps) { return ( <> - - {label} - - + {label} + {`${formatNumberLamports(value ?? 0n, solDecimals, { decimals: solDecimals, trailingZeroes: true, @@ -107,13 +106,10 @@ function TipsRow({ label, value, total, color }: TipsRowProps) { fill="#FFC53D" /> - + Commission  - + - {`${formatNumberLamports(commission ?? 0n, solDecimals, { decimals: solDecimals, @@ -137,10 +133,8 @@ function Row({ label, value, total, color }: RowProps) { return ( <> - - {label} - - + {label} + {`${formatNumberLamports(value ?? 0n, solDecimals, { decimals: solDecimals, trailingZeroes: true, @@ -173,11 +167,9 @@ function TotalIncomeRow({ tips, fees }: TotalIncomeRowProps) { return ( <> - - Income - + Income diff --git a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeDistributionCharts.tsx b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeDistributionCharts.tsx index 7113bbd8..c8144573 100644 --- a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeDistributionCharts.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeDistributionCharts.tsx @@ -1,4 +1,3 @@ -import { Flex } from "@radix-ui/themes"; import { useSlotQueryResponseTransactions } from "../../../../hooks/useSlotQuery"; import { useAtomValue } from "jotai"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; @@ -15,11 +14,11 @@ export default function IncomeDistributionCharts() { if (!transactions) return; return ( - + <> - + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/IncomeScatterChart.tsx b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/IncomeScatterChart.tsx index 97340b8f..037c3640 100644 --- a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/IncomeScatterChart.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/IncomeScatterChart.tsx @@ -35,7 +35,7 @@ export default function IncomeScatterChart({ return { width: 0, height: 0, - padding: [30, 30, 0, 30], + padding: [10, 15, 0, 15], scales: { [xScaleKey]: { time: false, @@ -73,7 +73,9 @@ export default function IncomeScatterChart({ : rawValue; }); }, - size: 35, + size: 20, + gap: 0, + font: "8px Inter Tight", }, { scale: lamportsScaleKey, diff --git a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/index.tsx b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/index.tsx index 1029f2da..4e8feea5 100644 --- a/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/index.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/FeeSection/IncomeScatterCharts/index.tsx @@ -8,6 +8,7 @@ import { getTxnIncome } from "../../../../../utils.ts"; import { useMemo } from "react"; import IncomeScatterChart from "./IncomeScatterChart.tsx"; import type uPlot from "uplot"; +import { subsectionGapX } from "../../consts.ts"; function getCuChartData(transactions?: SlotTransactions | null) { return transactions?.txn_compute_units_consumed @@ -102,7 +103,7 @@ export default function IncomeScatterCharts() { return; return ( - + - - - - - + + + + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/PctBarRow.tsx b/src/features/SlotDetails/DetailedSlotStats/PctBarRow.tsx index de895856..a7a9af16 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PctBarRow.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PctBarRow.tsx @@ -1,9 +1,6 @@ import { Text } from "@radix-ui/themes"; import PctBar from "./PctBar"; -import { - slotDetailsStatsPrimary, - slotDetailsStatsSecondary, -} from "../../../colors"; +import styles from "./detailedSlotStats.module.css"; interface PctBarRowProps { label: string; @@ -11,6 +8,7 @@ interface PctBarRowProps { total: number; valueColor: string; numeratorColor?: boolean; + pctColor?: boolean; } export default function PctBarRow({ @@ -19,39 +17,31 @@ export default function PctBarRow({ total, valueColor, numeratorColor = true, + pctColor = false, }: PctBarRowProps) { const pct = Math.round(total ? (value / total) * 100 : 0); return ( <> - - {label} - + {label} {value.toLocaleString()} - - / - - + / + {total.toLocaleString()} {pct}% - - - + + + + + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/PackBufferChartTooltip.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/PackBufferChartTooltip.tsx index 95ba9c60..f87c4eaf 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/PackBufferChartTooltip.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/PackBufferChartTooltip.tsx @@ -3,11 +3,11 @@ import UplotTooltip from "../../../../../uplotReact/UplotTooltip"; import type { SchedulerCounts } from "../../../../../api/types"; import { nonVoteColor, - slotDetailsStatsSecondary, slotStatusRed, tipsColor, votesColor, } from "../../../../../colors"; +import styles from "../../detailedSlotStats.module.css"; interface PackBufferChartProps { data?: SchedulerCounts; @@ -41,8 +41,10 @@ interface RowProps { function Row({ label, value, color }: RowProps) { return ( <> - {label} - {value.toLocaleString()} + {label} + + {value.toLocaleString()} + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/index.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/index.tsx index 9e64ca7f..231f601b 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/index.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/PackBufferChart/index.tsx @@ -54,6 +54,7 @@ export default function PackBufferChart() { return { width: 0, height: 0, + padding: [0, 0, 0, 0], drawOrder: ["axes", "series"] as uPlot.DrawOrderKey[], cursor: {}, scales: { @@ -78,11 +79,12 @@ export default function PackBufferChart() { stroke: chartAxisColor, size: 5, }, - size: 30, values: (self, ticks) => { return ticks.map((rawValue) => rawValue / 1_000_000 + "ms"); }, space: 100, + size: 20, + font: "8px Inter Tight", }, { scale: yScaleKey, @@ -101,6 +103,7 @@ export default function PackBufferChart() { size: 5, }, space: 50, + font: "8px Inter Tight", size(self, values, axisIdx, cycleNum) { const axis = self.axes[axisIdx]; // bail out, force convergence @@ -173,8 +176,13 @@ export default function PackBufferChart() { : undefined; return ( - - + + {({ height, width }) => { options.width = width; diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleOutcomes.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleOutcomes.tsx deleted file mode 100644 index f06874d0..00000000 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleOutcomes.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import styles from "./scheduleOutcomes.module.css"; -import { Flex, Table } from "@radix-ui/themes"; -import { useAtomValue } from "jotai"; -import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; -import { useSlotQueryResponseDetailed } from "../../../../hooks/useSlotQuery"; -import byteSize from "byte-size"; -import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; - -const scheduleOutcomes = [ - "success", - "fail_taken", - "fail_fast_path", - "fail_byte_limit", - "fail_write_cost", - "fail_slow_path", - "fail_defer_skip", -]; - -export default function SchedulerStats() { - const selectedSlot = useAtomValue(selectedSlotAtom); - const schedulerStats = - useSlotQueryResponseDetailed(selectedSlot).response?.scheduler_stats; - if (!schedulerStats) return; - - const { - slot_schedule_counts, - end_slot_schedule_counts, - pending_smallest_bytes, - pending_smallest_cost, - pending_vote_smallest_bytes, - pending_vote_smallest_cost, - } = schedulerStats; - - return ( - - - - - - Outcome - - Txn Count - - - Txn Count (end) - - - - - - {slot_schedule_counts.map((scheduleCount, i) => ( - - {scheduleOutcomes[i]} - - {scheduleCount.toLocaleString()} - - - {end_slot_schedule_counts[i].toLocaleString()} - - - ))} - - - - - - - - Smallest pending txn - - - Cu Cost - - - Size - - - - - - Non-vote - - {pending_smallest_cost?.toLocaleString() ?? 0} - - - {pending_smallest_bytes != null - ? byteSize(pending_smallest_bytes)?.toString() - : 0} - - - - Vote - - {pending_vote_smallest_cost?.toLocaleString() ?? 0} - - - {pending_vote_smallest_bytes != null - ? byteSize(pending_vote_smallest_bytes)?.toString() - : 0} - - - - - - - ); -} diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleStats.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleStats.tsx new file mode 100644 index 00000000..3c9e7ad0 --- /dev/null +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/ScheduleStats.tsx @@ -0,0 +1,93 @@ +import { Grid, Text } from "@radix-ui/themes"; +import { useAtomValue } from "jotai"; +import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; +import { useSlotQueryResponseDetailed } from "../../../../hooks/useSlotQuery"; +import byteSize from "byte-size"; +import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; +import { gridGapX, gridGapY } from "../consts"; +import styles from "../detailedSlotStats.module.css"; + +const scheduleOutcomes = [ + "success", + "fail_taken", + "fail_fast_path", + "fail_byte_limit", + "fail_write_cost", + "fail_slow_path", + "fail_defer_skip", +]; + +export default function SchedulerStats() { + const selectedSlot = useAtomValue(selectedSlotAtom); + const schedulerStats = + useSlotQueryResponseDetailed(selectedSlot).response?.scheduler_stats; + if (!schedulerStats) return; + + const { + slot_schedule_counts, + end_slot_schedule_counts, + pending_smallest_bytes, + pending_smallest_cost, + pending_vote_smallest_bytes, + pending_vote_smallest_cost, + } = schedulerStats; + + return ( + <> + + + Outcome + + Txn Count + + + Txn Count (End) + + + {slot_schedule_counts.map((scheduleCount, i) => ( + <> + {scheduleOutcomes[i]} + + {scheduleCount.toLocaleString()} + + + {end_slot_schedule_counts[i].toLocaleString()} + + + ))} + + + + +
+ + CU Cost + + + Size + + + Non-vote + + {pending_smallest_cost?.toLocaleString() ?? 0} + + + {pending_smallest_bytes != null + ? byteSize(pending_smallest_bytes)?.toString() + : 0} + + + Vote + + {pending_vote_smallest_cost?.toLocaleString() ?? 0} + + + {pending_vote_smallest_bytes != null + ? byteSize(pending_vote_smallest_bytes)?.toString() + : 0} + + + + + ); +} diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/SlotDurationStats.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/SlotDurationStats.tsx index 7e165929..a7dc3f09 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/SlotDurationStats.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/SlotDurationStats.tsx @@ -3,10 +3,8 @@ import { useAtomValue } from "jotai"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; import { useSlotQueryPublish } from "../../../../hooks/useSlotQuery"; import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; -import { - slotDetailsStatsPrimary, - slotDetailsStatsSecondary, -} from "../../../../colors"; +import styles from "../detailedSlotStats.module.css"; +import { gridGapX } from "../consts"; export function SlotDurationStats() { const selectedSlot = useAtomValue(selectedSlotAtom); @@ -15,11 +13,11 @@ export function SlotDurationStats() { return ( - - Actual + + Actual {durationNanos != null && ( - - {`${(durationNanos / 1_000_000).toFixed(2)} ms`} + + {`${(durationNanos / 1_000_000).toFixed(2)}ms`} )} diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TimeSinceLastLeaderStats.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TimeSinceLastLeaderStats.tsx index 9f89166a..33d29a82 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TimeSinceLastLeaderStats.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TimeSinceLastLeaderStats.tsx @@ -7,10 +7,9 @@ import { getDurationText, getSlotGroupLeader } from "../../../../utils"; import { selectedSlotAtom } from "../../../Overview/SlotPerformance/atoms"; import { slotsPerLeader } from "../../../../consts"; import { useSlotQueryResponseDetailed } from "../../../../hooks/useSlotQuery"; -import { - slotDetailsStatsPrimary, - slotDetailsStatsSecondary, -} from "../../../../colors"; +import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; +import styles from "../detailedSlotStats.module.css"; +import { gridGapX, gridGapY } from "../consts"; export function TimeSinceLastLeaderStats() { const slotDuration = useAtomValue(slotDurationAtom); @@ -39,23 +38,19 @@ export function TimeSinceLastLeaderStats() { : undefined; return ( - - - - Time Since Last Leader Group - - - {getDurationText(timeTill)} - + + + + Time Since Last Leader Group + {getDurationText(timeTill)} + + + End slot reason + + {schedulerStats?.end_slot_reason} + + - - - End slot reason - - - {schedulerStats?.end_slot_reason} - - - + ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TxnExecutionDurationCharts/index.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TxnExecutionDurationCharts/index.tsx index d605d1bb..9bfd688c 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TxnExecutionDurationCharts/index.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/TxnExecutionDurationCharts/index.tsx @@ -1,4 +1,4 @@ -import { Box, Flex } from "@radix-ui/themes"; +import { Box } from "@radix-ui/themes"; import { useMemo } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import uPlot, { type AlignedData, type Options } from "uplot"; @@ -19,14 +19,24 @@ export default function TxnExecutionDurationCharts() { if (!transactions) return; return ( - - - - - + <> + - + + + + ); } @@ -69,17 +79,25 @@ function Chart({ data, log, id }: ChartProps) { axes: [ { scale: "duration", - splits: [0, data[0].length], + splits: [0, data[0].length - 1], values: xValues, label: "Txn Execution Duration", - stroke: "gray", + stroke: "#B4B4B4", grid: { show: false }, + size: 10, + gap: 0, + font: "8px Inter Tight", + labelFont: "8px Inter Tight", + labelGap: 0, + labelSize: 10, }, { - stroke: "gray", + stroke: "#B4B4B4", values: (u, splits) => { return log ? splits.map((v) => Math.trunc(Math.exp(v))) : splits; }, + gap: 0, + font: "8px Inter Tight", size(self, values, axisIdx, cycleNum) { const axis = self.axes[axisIdx]; // bail out, force convergence @@ -94,7 +112,7 @@ function Chart({ data, log, id }: ChartProps) { "", ); if (longestVal !== "") { - self.ctx.font = axis.font?.[0] ?? "Inter Tight"; + self.ctx.font = axis.font?.[0] ?? "8px Inter Tight"; axisSize += self.ctx.measureText(longestVal).width / devicePixelRatio; } diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/index.tsx b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/index.tsx index ec9658e8..c551aa41 100644 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/index.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/index.tsx @@ -5,19 +5,27 @@ import { TimeSinceLastLeaderStats } from "./TimeSinceLastLeaderStats"; import CpuSparklines from "./CpuSparklines"; import PackBufferChart from "./PackBufferChart"; import { SlotDurationStats } from "./SlotDurationStats"; -import { SlotDetailsSubSection } from "../SlotDetailsSubSection"; -import ScheduleOutcomes from "./ScheduleOutcomes"; +import ScheduleStats from "./ScheduleStats"; +import { sectionGapX, sectionGapY } from "../consts"; +import { useMedia } from "react-use"; export default function PerformanceSection() { + const isNarrowScreen = useMedia("(max-width: 500px)"); + return ( - - - + + + - + - - + + diff --git a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/scheduleOutcomes.module.css b/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/scheduleOutcomes.module.css deleted file mode 100644 index bc96d7cd..00000000 --- a/src/features/SlotDetails/DetailedSlotStats/PerformanceSection/scheduleOutcomes.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.table-container { - th { - color: var(--slot-details-stats-secondary); - } - - td { - color: var(--slot-details-stats-primary); - } -} diff --git a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsHeader.tsx b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsHeader.tsx index 4d336c31..7e0ec7d7 100644 --- a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsHeader.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsHeader.tsx @@ -11,16 +11,13 @@ import { import { epochAtom } from "../../../atoms"; import { useMemo } from "react"; import { DateTime } from "luxon"; -import { - slotDetailsStatsPrimary, - slotDetailsStatsSecondary, -} from "../../../colors"; + +import styles from "./detailedSlotStats.module.css"; export default function SlotDetailsHeader() { const slot = useAtomValue(selectedSlotAtom); - const { peer, isLeader, name, countryCode, countryFlag } = useSlotInfo( - slot ?? 0, - ); + const { peer, isLeader, name, pubkey, countryCode, countryFlag } = + useSlotInfo(slot ?? 0); const epoch = useAtomValue(epochAtom); const slotPublish = useSlotQueryPublish(slot).publish; const schedulerStats = @@ -42,20 +39,11 @@ export default function SlotDetailsHeader() { if (slot === undefined) return; return ( - // TODO: Fix to a better layout - - + + - - {name} - + {name} + {pubkey} {countryCode && ( @@ -103,14 +91,10 @@ function HorizontalLabelValue({ icon, }: HorizontalLabelValueProps) { return ( - - - {label} - - - {value} - - {icon && {icon}} + + {label} + {value} + {icon && {icon}} ); } diff --git a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSection.tsx b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSection.tsx index dbef13bc..1a799dee 100644 --- a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSection.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSection.tsx @@ -1,6 +1,8 @@ -import { Flex, Text, type FlexProps } from "@radix-ui/themes"; +import { Box, Flex, Text, type FlexProps } from "@radix-ui/themes"; import type { PropsWithChildren } from "react"; import RowSeparator from "../../../components/RowSeparator"; +import styles from "./detailedSlotStats.module.css"; +import { sectionGapY } from "./consts"; interface SlotDetailsSectionProps { title: string; @@ -12,11 +14,17 @@ export function SlotDetailsSection({ ...props }: PropsWithChildren) { return ( - - - {title} - - + + + {title} + + {children} ); diff --git a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSubSection.tsx b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSubSection.tsx index d05eb898..78d3951c 100644 --- a/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSubSection.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/SlotDetailsSubSection.tsx @@ -1,5 +1,6 @@ import { Flex, Text, type FlexProps } from "@radix-ui/themes"; import type { PropsWithChildren } from "react"; +import styles from "./detailedSlotStats.module.css"; interface SlotDetailsSubSectionProps { title: string; @@ -12,7 +13,7 @@ export function SlotDetailsSubSection({ }: PropsWithChildren & FlexProps) { return ( - + {title} {children} diff --git a/src/features/SlotDetails/DetailedSlotStats/consts.ts b/src/features/SlotDetails/DetailedSlotStats/consts.ts new file mode 100644 index 00000000..dab44765 --- /dev/null +++ b/src/features/SlotDetails/DetailedSlotStats/consts.ts @@ -0,0 +1,5 @@ +export const sectionGapX = "20px"; +export const sectionGapY = "15px"; +export const subsectionGapX = "15px"; +export const gridGapX = "5px"; +export const gridGapY = "1"; diff --git a/src/features/SlotDetails/DetailedSlotStats/detailedSlotStats.module.css b/src/features/SlotDetails/DetailedSlotStats/detailedSlotStats.module.css new file mode 100644 index 00000000..37cffdc0 --- /dev/null +++ b/src/features/SlotDetails/DetailedSlotStats/detailedSlotStats.module.css @@ -0,0 +1,59 @@ +.header { + font-size: 14px; + font-weight: 600; + color: var(--slot-details-stats-tertiary); +} + +.subheader { + font-size: 12px; + color: var(--slot-details-stats-tertiary); +} + +.label { + font-size: 10px; + color: var(--slot-details-stats-secondary); + text-wrap: nowrap; +} + +.value { + font-size: 10px; + color: var(--slot-details-stats-primary); + text-wrap: nowrap; +} + +.table-header { + font-size: 10px; + color: var(--slot-details-stats-primary); + text-wrap: nowrap; +} + +.table-row-label { + font-size: 10px; + color: var(--slot-details-stats-primary); + text-wrap: nowrap; + + &.total { + color: var(--slot-details-stats-tertiary); + } +} + +.table-cell-value { + font-size: 10px; + color: var(--slot-details-stats-secondary); + text-wrap: nowrap; + + &.total { + color: var(--slot-details-stats-primary); + } +} + +.name { + font-size: 12px; + font-weight: 600; + color: #ccc; +} + +.pubkey { + font-size: 8px; + color: var(--gray-11); +} diff --git a/src/features/SlotDetails/DetailedSlotStats/index.tsx b/src/features/SlotDetails/DetailedSlotStats/index.tsx index 4daae32b..3f8f81e6 100644 --- a/src/features/SlotDetails/DetailedSlotStats/index.tsx +++ b/src/features/SlotDetails/DetailedSlotStats/index.tsx @@ -4,13 +4,14 @@ import ComputeSection from "./ComputeSection"; import FeeSection from "./FeeSection"; import PerformanceSection from "./PerformanceSection"; import SlotDetailsHeader from "./SlotDetailsHeader"; +import { sectionGapX } from "./consts"; export default function DetailedSlotStats() { return ( - + diff --git a/src/features/SlotDetails/slotNavigation.module.css b/src/features/SlotDetails/slotNavigation.module.css index f0f00ef6..9c80b6e8 100644 --- a/src/features/SlotDetails/slotNavigation.module.css +++ b/src/features/SlotDetails/slotNavigation.module.css @@ -2,15 +2,17 @@ gap: 4px; padding: 2px; border-radius: 5px; - border-top-width: 3px; border: 1px solid var(--slot-details-my-slots-not-selected-color); + border-top-width: 3px; &.disabled { border: 1px solid var(--slot-details-disabled-slot-border-color); + border-top-width: 3px; } &.is-selected { border: 1px solid var(--slots-list-my-slots-selected-border-color); + border-top-width: 3px; } }