feat(dashboard): LLM Inference card with live llm-api metrics
- /api/dashboard now queries llm-api Prometheus metrics: req/s, tok/s, gen tok/s, uptime, sparklines - new LLM Inference card shows gen throughput + uptime badge + traffic sparkline - fmtUptime helper (human-readable duration)
This commit is contained in:
@@ -137,6 +137,14 @@ interface DashboardData {
|
|||||||
errors: number[];
|
errors: number[];
|
||||||
traceVolume: number[];
|
traceVolume: number[];
|
||||||
links: { url: string; label: string }[];
|
links: { url: string; label: string }[];
|
||||||
|
llm: {
|
||||||
|
reqRate: number | null;
|
||||||
|
tokRate: number | null;
|
||||||
|
tokSpeed: number | null;
|
||||||
|
uptime: number | null;
|
||||||
|
reqSpark: number[];
|
||||||
|
tokSpark: number[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
@@ -176,6 +184,33 @@ export async function GET() {
|
|||||||
promQuery("count(up)"),
|
promQuery("count(up)"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// ── LLM inference metrics (llm-api /metrics) ──
|
||||||
|
const [
|
||||||
|
llmReqRate,
|
||||||
|
llmTokRate,
|
||||||
|
llmTokSpeed,
|
||||||
|
llmUptime,
|
||||||
|
llmReqSpark,
|
||||||
|
llmTokSpark,
|
||||||
|
] = await Promise.all([
|
||||||
|
promQuery(
|
||||||
|
`sum(rate(llm_api_requests_total{service="llm-api"}[5m]))`,
|
||||||
|
),
|
||||||
|
promQuery(
|
||||||
|
`sum(rate(llm_api_completion_tokens_total{service="llm-api"}[5m]))`,
|
||||||
|
),
|
||||||
|
promQuery(
|
||||||
|
`avg(llm_api_tokens_per_second{service="llm-api"})`,
|
||||||
|
),
|
||||||
|
promQuery(`llm_api_uptime_seconds{service="llm-api"}`),
|
||||||
|
promRange(
|
||||||
|
`sum(rate(llm_api_requests_total{service="llm-api"}[1m]))`,
|
||||||
|
),
|
||||||
|
promRange(
|
||||||
|
`sum(rate(llm_api_completion_tokens_total{service="llm-api"}[1m]))`,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
const links: { url: string; label: string }[] = [
|
const links: { url: string; label: string }[] = [
|
||||||
{ url: "https://github.com/asepharyana/asepharyana-hub", label: "GitHub" },
|
{ url: "https://github.com/asepharyana/asepharyana-hub", label: "GitHub" },
|
||||||
];
|
];
|
||||||
@@ -195,6 +230,14 @@ export async function GET() {
|
|||||||
errors,
|
errors,
|
||||||
traceVolume: traceVolume ? [traceVolume] : [],
|
traceVolume: traceVolume ? [traceVolume] : [],
|
||||||
links,
|
links,
|
||||||
|
llm: {
|
||||||
|
reqRate: llmReqRate,
|
||||||
|
tokRate: llmTokRate,
|
||||||
|
tokSpeed: llmTokSpeed,
|
||||||
|
uptime: llmUptime,
|
||||||
|
reqSpark: llmReqSpark,
|
||||||
|
tokSpark: llmTokSpark,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return NextResponse.json(data);
|
return NextResponse.json(data);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import { AnimatedNumber } from "@/components/ui/motion-primitives";
|
import { AnimatedNumber } from "@/components/ui/motion-primitives";
|
||||||
import {
|
import {
|
||||||
type DashboardData,
|
type DashboardData,
|
||||||
|
fmtUptime,
|
||||||
gaugeColor,
|
gaugeColor,
|
||||||
safeDur,
|
safeDur,
|
||||||
serviceIndicator,
|
serviceIndicator,
|
||||||
@@ -230,6 +231,73 @@ export default function Dashboard() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* LLM Inference */}
|
||||||
|
<motion.div variants={cardVariants}>
|
||||||
|
<Card className="transition-shadow duration-300 hover:shadow-md hover:shadow-border/20">
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
|
<CardTitle>LLM Inference</CardTitle>
|
||||||
|
<Badge variant="secondary" className="font-mono text-[10px] font-normal">
|
||||||
|
{data?.llm.uptime != null
|
||||||
|
? `${fmtUptime(data.llm.uptime)} up`
|
||||||
|
: "no data"}
|
||||||
|
</Badge>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-2 gap-1.5">
|
||||||
|
<StatBox
|
||||||
|
value={
|
||||||
|
data?.llm.reqRate != null
|
||||||
|
? data.llm.reqRate.toFixed(1)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
label="Req/s (5m)"
|
||||||
|
color="text-blue-400"
|
||||||
|
/>
|
||||||
|
<StatBox
|
||||||
|
value={
|
||||||
|
data?.llm.tokRate != null
|
||||||
|
? `${data.llm.tokRate.toFixed(0)}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
label="Tokens/s (5m)"
|
||||||
|
color="text-green-400"
|
||||||
|
/>
|
||||||
|
<StatBox
|
||||||
|
value={
|
||||||
|
data?.llm.tokSpeed != null
|
||||||
|
? `${data.llm.tokSpeed.toFixed(0)}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
label="Gen tok/s"
|
||||||
|
color="text-purple-400"
|
||||||
|
/>
|
||||||
|
<StatBox
|
||||||
|
value={data?.llm.reqSpark?.length ? "live" : undefined}
|
||||||
|
label="Sparkline"
|
||||||
|
color="text-yellow-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{data?.llm.reqSpark?.length ? (
|
||||||
|
<div className="mt-2">
|
||||||
|
<Sparkline data={data.llm.reqSpark} color="#58a6ff" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="mt-2 text-center text-[10px] text-muted-foreground">
|
||||||
|
No traffic yet — send a prompt on{" "}
|
||||||
|
<a
|
||||||
|
className="text-blue-400 underline"
|
||||||
|
href="https://ai.asepharyana.my.id"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
ai.asepharyana.my.id
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
{/* Request Rate */}
|
{/* Request Rate */}
|
||||||
{hasTraffik && (
|
{hasTraffik && (
|
||||||
<motion.div variants={cardVariants}>
|
<motion.div variants={cardVariants}>
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ export interface DashboardData {
|
|||||||
errors: number[];
|
errors: number[];
|
||||||
traceVolume: number[];
|
traceVolume: number[];
|
||||||
links: { url: string; label: string }[];
|
links: { url: string; label: string }[];
|
||||||
|
llm: {
|
||||||
|
reqRate: number | null;
|
||||||
|
tokRate: number | null;
|
||||||
|
tokSpeed: number | null;
|
||||||
|
uptime: number | null;
|
||||||
|
reqSpark: number[];
|
||||||
|
tokSpark: number[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function safeDur(us: number): string {
|
export function safeDur(us: number): string {
|
||||||
@@ -55,3 +63,14 @@ export function serviceIndicator(state: string): string {
|
|||||||
return "bg-red-400";
|
return "bg-red-400";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fmtUptime(seconds: number): string {
|
||||||
|
const s = Math.max(0, Math.floor(seconds));
|
||||||
|
if (s < 60) return `${s}s`;
|
||||||
|
const m = Math.floor(s / 60);
|
||||||
|
if (m < 60) return `${m}m ${s % 60}s`;
|
||||||
|
const h = Math.floor(m / 60);
|
||||||
|
if (h < 24) return `${h}h ${m % 60}m`;
|
||||||
|
const d = Math.floor(h / 24);
|
||||||
|
return `${d}d ${h % 24}h`;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user