Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion apps/dashboard/src/@/api/usage/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,67 @@ export const fetchRPCUsage = unstable_cache(
data: resData.data as RPCUsageDataItem[],
};
},
["nebula-analytics"],
["rpc-usage"],
{
revalidate: 60 * 60, // 1 hour
},
);

type Last24HoursRPCUsageApiResponse = {
peakRate: {
date: string;
peakRPS: number;
};
averageRate: {
date: string;
averageRate: number;
includedCount: number;
rateLimitedCount: number;
overageCount: number;
}[];
totalCounts: {
includedCount: number;
rateLimitedCount: number;
overageCount: number;
};
};

export const getLast24HoursRPCUsage = unstable_cache(
async (params: {
teamId: string;
projectId?: string;
authToken: string;
}) => {
const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string;
const url = new URL(`${analyticsEndpoint}/v2/rpc/24-hours`);
url.searchParams.set("teamId", params.teamId);
if (params.projectId) {
url.searchParams.set("projectId", params.projectId);
}

const res = await fetch(url, {
headers: {
Authorization: `Bearer ${params.authToken}`,
},
});

if (!res.ok) {
const error = await res.text();
return {
ok: false as const,
error: error,
};
}

const resData = await res.json();

return {
ok: true as const,
data: resData.data as Last24HoursRPCUsageApiResponse,
};
},
["rpc-usage-last-24-hours"],
{
revalidate: 60, // 1 minute
},
);
44 changes: 40 additions & 4 deletions apps/dashboard/src/@/components/blocks/charts/area-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
Expand All @@ -17,7 +18,7 @@ import {
} from "@/components/ui/chart";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import {
EmptyChartState,
LoadingChartState,
Expand All @@ -30,11 +31,17 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
description?: string;
titleClassName?: string;
};
footer?: React.ReactNode;
customHeader?: React.ReactNode;
// chart config
config: TConfig;
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
showLegend?: boolean;
maxLimit?: number;
yAxis?: boolean;
xAxis?: {
sameDay?: boolean;
};

// chart className
chartClassName?: string;
Expand Down Expand Up @@ -70,17 +77,33 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
) : props.data.length === 0 ? (
<EmptyChartState />
) : (
<AreaChart accessibilityLayer data={props.data}>
<AreaChart
accessibilityLayer
data={
props.maxLimit
? props.data.map((d) => ({
...d,
maxLimit: props.maxLimit,
}))
: props.data
}
>
<CartesianGrid vertical={false} />
{props.yAxis && <YAxis tickLine={false} axisLine={false} />}
<XAxis
dataKey="time"
tickLine={false}
axisLine={false}
tickMargin={20}
tickFormatter={(value) => formatDate(new Date(value), "MMM dd")}
tickFormatter={(value) =>
formatDate(
new Date(value),
props.xAxis?.sameDay ? "MMM dd, HH:mm" : "MMM dd",
)
}
/>
<ChartTooltip
cursor={false}
cursor={true}
content={
<ChartTooltipContent
hideLabel={
Expand Down Expand Up @@ -124,6 +147,16 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
stackId="a"
/>
))}
{props.maxLimit && (
<Area
type="monotone"
dataKey="maxLimit"
stroke="#ef4444"
strokeWidth={2}
strokeDasharray="5 5"
fill="none"
/>
)}

{props.showLegend && (
<ChartLegend
Expand All @@ -134,6 +167,9 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
)}
</ChartContainer>
</CardContent>
{props.footer && (
<CardFooter className="w-full">{props.footer}</CardFooter>
)}
</Card>
);
}
4 changes: 3 additions & 1 deletion apps/dashboard/src/@/components/ui/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ const ChartTooltipContent = React.forwardRef<
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
{item.name === "maxLimit"
? "Upper Limit"
: itemConfig?.label || item.name}
</span>
</div>
{item.value !== undefined && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export default async function Layout(props: {
exactMatch: true,
label: "Overview",
},
{
href: `/team/${params.team_slug}/~/usage/rpc`,
exactMatch: true,
label: "RPC",
},
{
href: `/team/${params.team_slug}/~/usage/storage`,
exactMatch: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";

import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";
import { formatDate } from "date-fns";

export function CountGraph(props: {
peakPercentage: number;
currentRateLimit: number;
data: {
date: string;
includedCount: number;
overageCount: number;
rateLimitedCount: number;
}[];
}) {
return (
<ThirdwebAreaChart
chartClassName="aspect-[1.5] lg:aspect-[4]"
header={{
title: "Requests Over Time",
description: "Requests over the last 24 hours. All times in UTC.",
}}
config={{
includedCount: {
label: "Successful Requests",
color: "hsl(var(--chart-1))",
},
rateLimitedCount: {
label: "Rate Limited Requests",
color: "hsl(var(--chart-4))",
},
}}
showLegend
yAxis
xAxis={{
sameDay: true,
}}
hideLabel={false}
toolTipLabelFormatter={(label) => {
return formatDate(new Date(label), "MMM dd, HH:mm");
}}
data={props.data
.slice(1, -1)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
.map((v) => ({
time: v.date,
includedCount: v.includedCount + v.overageCount,
rateLimitedCount: v.rateLimitedCount,
}))}
isPending={false}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";
import { formatDate } from "date-fns";
import { InfoIcon } from "lucide-react";

export function RateGraph(props: {
peakPercentage: number;
currentRateLimit: number;
data: { date: string; averageRate: number }[];
}) {
return (
<ThirdwebAreaChart
chartClassName="aspect-[1.5] lg:aspect-[4]"
header={{
title: "Request Rate Over Time",
description: "Request rate over the last 24 hours. All times in UTC.",
}}
// only show the footer if the peak usage is greater than 80%
footer={
props.peakPercentage > 80 ? (
<div className="flex items-center justify-center gap-2">
<InfoIcon className="h-4 w-4 text-muted-foreground" />
<p className="text-muted-foreground text-xs">
The red dashed line represents your current plan rate limit (
{props.currentRateLimit} RPS)
</p>
</div>
) : undefined
}
config={{
averageRate: {
label: "Average RPS",
color: "hsl(var(--chart-1))",
},
}}
data={props.data
.slice(1, -1)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
.map((v) => ({
time: v.date,
averageRate: Number(v.averageRate.toFixed(2)),
}))}
yAxis
xAxis={{
sameDay: true,
}}
hideLabel={false}
toolTipLabelFormatter={(label) => {
return formatDate(new Date(label), "MMM dd, HH:mm");
}}
// only show the upper limit if the peak usage is greater than 80%
maxLimit={props.peakPercentage > 80 ? props.currentRateLimit : undefined}
isPending={false}
/>
);
}
Loading
Loading