Skip to content

Commit

Permalink
feat: lobby stats (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
jog1t committed Sep 6, 2024
1 parent ca0fca8 commit 88bb7be
Show file tree
Hide file tree
Showing 12 changed files with 791 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { Suspense } from "react";
import { gameNamespaceLobbyQueryOptions } from "../../queries";
import { LobbyLifecycle } from "./lobby-lifecycle";
import { LobbyLogs } from "./lobby-logs";
import { LobbyMetrics } from "./lobby-metrics";
import { LobbyStats } from "./lobby-stats";
import { LobbySummary } from "./lobby-summary";

interface GameMatchmakerLobbyDetailsPanelProps {
Expand All @@ -38,7 +40,8 @@ export function GameMatchmakerLobbyDetailsPanel({
}

const {
data: { lobby },
data: { lobby, metrics },
dataUpdatedAt,
} = useSuspenseQuery(
gameNamespaceLobbyQueryOptions(
{ gameId, namespaceId, lobbyId },
Expand Down Expand Up @@ -68,15 +71,25 @@ export function GameMatchmakerLobbyDetailsPanel({
readyTs={lobby.readyTs || lobby.startTs}
stopTs={lobby.status.stopped?.stopTs}
/>

{isLive && metrics ? (
<LobbyMetrics lobbyId={lobbyId} {...metrics} />
) : null}
</>
}
/>
</Suspense>

<Tabs defaultValue="logs" className="flex-1 min-h-0 flex flex-col mt-4">
<Tabs
defaultValue="logs"
className="flex-1 min-h-0 flex flex-col mt-4 @container"
>
<TabsList className="overflow-auto">
<TabsTrigger value="logs">Output</TabsTrigger>
<TabsTrigger value="errors">Error</TabsTrigger>
{isLive && metrics ? (
<TabsTrigger value="stats">Stats</TabsTrigger>
) : null}
</TabsList>
<TabsContent value="logs" className="min-h-0 flex-1 mt-0 p-4">
<Suspense fallback={<LobbyLogs.Skeleton />}>
Expand All @@ -98,6 +111,17 @@ export function GameMatchmakerLobbyDetailsPanel({
/>
</Suspense>
</TabsContent>
{isLive && metrics ? (
<TabsContent value="stats" className="min-h-0 flex-1 mt-0">
<Suspense fallback={<LobbyLogs.Skeleton />}>
<LobbyStats
lobbyId={lobbyId}
metricsAt={dataUpdatedAt}
{...metrics}
/>
</Suspense>
</TabsContent>
) : null}
</Tabs>
</ErrorBoundary>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { Rivet } from "@rivet-gg/api";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
timing,
} from "@rivet-gg/components";
import { format } from "date-fns";
import { useId } from "react";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";

interface LobbyCPUStatsProps extends Pick<Rivet.cloud.SvcMetrics, "cpu"> {
metricsAt: number;
syncId?: string;
}

const chartConfig = {
value: {
color: "hsl(var(--chart-1))",
label: "CPU Usage",
},
} satisfies ChartConfig;

export function LobbyCPUStats({ cpu, metricsAt, syncId }: LobbyCPUStatsProps) {
const data = cpu.map((value, i) => ({
x: `${(cpu.length - i) * -15}`,
value: value / 100,
config: {
label: new Date(metricsAt - (cpu.length - i) * timing.seconds(15)),
},
}));

const id = useId();

const fillId = `fill-${id}`;
return (
<ChartContainer config={chartConfig}>
<AreaChart accessibilityLayer data={data} syncId={syncId}>
<CartesianGrid vertical={true} />
<XAxis
interval="preserveStartEnd"
dataKey="x"
hide
axisLine={false}
domain={[0, 60]}
tickCount={60}
/>
<YAxis
dataKey="value"
axisLine={false}
padding={{ top: 10 }}
domain={([, dataMax]) => {
return [0, Math.ceil(dataMax * 100) / 100];
}}
tickFormatter={(value) => `${value * 100}%`}
/>
<ChartTooltip
content={
<ChartTooltipContent
hideIndicator
labelKey="label"
labelFormatter={(label) => {
return format(label, "HH:mm:ss");
}}
valueFormatter={(value) => {
if (typeof value !== "number") {
return "n/a";
}
return `${(value * 100).toFixed(2)}%`;
}}
/>
}
/>
<defs>
<linearGradient id={fillId} x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-value)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="var(--color-value)"
stopOpacity={0.1}
/>
</linearGradient>
</defs>
<Area
isAnimationActive={false}
dataKey="value"
type="linear"
fill={`url(#${fillId})`}
fillOpacity={0.4}
stroke="var(--color-value)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { Rivet } from "@rivet-gg/api";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
timing,
} from "@rivet-gg/components";
import { format } from "date-fns";
import { filesize } from "filesize";
import { useId } from "react";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";

interface LobbyMemoryStatsProps
extends Pick<Rivet.cloud.SvcMetrics, "memory" | "allocatedMemory"> {
metricsAt: number;
syncId?: string;
}

const chartConfig = {
value: {
color: "hsl(var(--chart-1))",
label: "Memory Usage",
},
} satisfies ChartConfig;

export function LobbyMemoryStats({
memory,
allocatedMemory,
metricsAt,
syncId,
}: LobbyMemoryStatsProps) {
const data = memory.map((value, i) => ({
x: `${(memory.length - i) * -15}`,
value,
config: {
label: new Date(metricsAt - (memory.length - i) * timing.seconds(15)),
},
}));

const id = useId();

const fillId = `fill-${id}`;
return (
<ChartContainer config={chartConfig}>
<AreaChart accessibilityLayer data={data} syncId={syncId}>
<CartesianGrid vertical={true} />
<XAxis
interval="preserveStartEnd"
dataKey="x"
hide
axisLine={false}
domain={[0, 60]}
tickCount={60}
includeHidden
/>
<YAxis
dataKey="value"
axisLine={false}
padding={{ top: 10 }}
domain={([, dataMax]) => {
return [0, allocatedMemory || dataMax];
}}
tickFormatter={(value) => `${filesize(value)}`}
/>
<ChartTooltip
content={
<ChartTooltipContent
hideIndicator
labelKey="label"
labelFormatter={(label) => {
return format(label, "HH:mm:ss");
}}
valueFormatter={(value) => {
if (typeof value !== "number") {
return "n/a";
}
return filesize(value);
}}
/>
}
/>
<defs>
<linearGradient id={fillId} x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor="var(--color-value)"
stopOpacity={0.8}
/>
<stop
offset="95%"
stopColor="var(--color-value)"
stopOpacity={0.1}
/>
</linearGradient>
</defs>
<Area
isAnimationActive={false}
dataKey="value"
type="linear"
fill={`url(#${fillId})`}
fillOpacity={0.4}
stroke="var(--color-value)"
stackId="a"
/>
</AreaChart>
</ChartContainer>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Rivet } from "@rivet-gg/api";
import { Flex, Progress, SmallText, WithTooltip } from "@rivet-gg/components";
import { filesize } from "filesize";

interface LobbyMetricsProps extends Rivet.cloud.SvcMetrics {
lobbyId: string;
}

export function LobbyMetrics({
allocatedMemory,
memory,
cpu,
}: LobbyMetricsProps) {
const currentMemory = memory[memory.length - 1];
const memoryPercentage = (currentMemory / (allocatedMemory || 1)) * 100;

const cpuPercentage = cpu[cpu.length - 1] * 100;
return (
<Flex gap="2">
<WithTooltip
trigger={
<Flex direction="col" gap="2" className="min-w-20">
<SmallText>Memory</SmallText>
<Progress className="h-2" value={memoryPercentage} />
</Flex>
}
content={
<>
{filesize(currentMemory)} / {filesize(allocatedMemory || 1)} (
{memoryPercentage.toFixed(2)}%)
</>
}
/>

<WithTooltip
trigger={
<Flex direction="col" gap="2" className="min-w-20">
<SmallText>CPU</SmallText>
<Progress className="h-2" value={cpu[cpu.length - 1] * 100} />
</Flex>
}
content={<>{cpuPercentage.toFixed(2)}%</>}
/>
</Flex>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Rivet } from "@rivet-gg/api";
import { Flex, Progress, ScrollArea, Strong } from "@rivet-gg/components";
import { filesize } from "filesize";
import { LobbyCPUStats } from "./lobby-cpu-stats";
import { LobbyMemoryStats } from "./lobby-memory-stats";

interface LobbyStatsProps extends Rivet.cloud.SvcMetrics {
lobbyId: string;
metricsAt: number;
}

export function LobbyStats({
cpu,
memory,
metricsAt,
allocatedMemory,
lobbyId,
}: LobbyStatsProps) {
const syncId = `stats-${lobbyId}`;

const memoryPercentage =
(memory[memory.length - 1] / (allocatedMemory || 1)) * 100;

const cpuPercentage = cpu[cpu.length - 1];

return (
<ScrollArea className=" h-full w-full overflow-auto p-4">
<div className="grid grid-cols-1 gap-4 @3xl:grid-cols-2 gap-y-8 ">
<div>
<p className="mb-4 font-bold">Memory Usage</p>
<LobbyMemoryStats
memory={memory}
metricsAt={metricsAt}
allocatedMemory={allocatedMemory}
syncId={syncId}
/>
<Flex items="center" justify="center" py="4" gap="4">
<Progress value={memoryPercentage} />
<Flex direction="col" gap="2">
<Strong className="tabular-nums">
{memoryPercentage.toFixed(2)}%
</Strong>
</Flex>
</Flex>
<p>
<Strong className="tabular-nums">
{filesize(memory[memory.length - 1])}
</Strong>{" "}
/{" "}
<span className="tabular-nums">
{filesize(allocatedMemory || 1)}
</span>{" "}
Total
</p>
</div>
<div>
<p className="mb-4 font-bold">CPU Usage</p>
<LobbyCPUStats cpu={cpu} metricsAt={metricsAt} syncId={syncId} />

<Flex items="center" justify="center" py="4" gap="4">
<Progress value={cpuPercentage} />
<Flex direction="col" gap="2">
<Strong className="tabular-nums">
{cpuPercentage.toFixed(2)}%
</Strong>
</Flex>
</Flex>
</div>
</div>
</ScrollArea>
);
}
Loading

0 comments on commit 88bb7be

Please sign in to comment.