# Service latency

> p50 and p95 service latency against an explicit p95 budget.

A shadcn/ui block from the [@neumorphism-ui](https://neumorphism-ui.andongmin.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/neumorphism-ui/chart-service-latency).

- Page, with a live preview: https://shadcn.exchange/r/neumorphism-ui/chart-service-latency
- Screenshot: https://shadcn.exchange/shot/neumorphism-ui/chart-service-latency/light.png
- Registry item: https://neumorphism-ui.andongmin.com/r/chart-service-latency.json
- Categories: chart
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/neumorphism-ui/chart-service-latency.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@neumorphism-ui/chart`

## Files

### `src/components/blocks/service-latency-chart.tsx`

```tsx
"use client";

import { CartesianGrid, Line, LineChart, ReferenceLine, XAxis, YAxis } from "recharts";

import { ChartContainer, ChartTooltip, ChartLegend } from "@/components/ui/chart";

const copy = {
  "en": {
    "title": "Service latency",
    "description": "Daily p50 and p95 response time against a p95 budget.",
    "worst": "Worst p95",
    "overBudget": "periods over budget",
    "empty": "No latency samples.",
    "table": "View latency data",
    "caption": "Service latency data",
    "period": "Period",
    "budget": "Budget",
    "over": "Over",
    "within": "Within",
    "days": [
      "Mon",
      "Tue",
      "Wed",
      "Thu",
      "Fri",
      "Sat",
      "Sun"
    ]
  },
  "ko": {
    "title": "서비스 지연 시간",
    "description": "일별 p50·p95 응답 시간과 p95 목표값을 비교합니다.",
    "worst": "최대 p95",
    "overBudget": "기간에서 목표 초과",
    "empty": "지연 시간 데이터가 없습니다.",
    "table": "지연 시간 데이터 보기",
    "caption": "서비스 지연 시간 데이터",
    "period": "기간",
    "budget": "목표",
    "over": "초과",
    "within": "이내",
    "days": [
      "월",
      "화",
      "수",
      "목",
      "금",
      "토",
      "일"
    ]
  },
  "ja": {
    "title": "サービスの応答時間",
    "description": "日別のp50・p95応答時間をp95の目標値と比較します。",
    "worst": "最大p95",
    "overBudget": "期間で目標超過",
    "empty": "応答時間データはありません。",
    "table": "応答時間データを表示",
    "caption": "サービス応答時間データ",
    "period": "期間",
    "budget": "目標",
    "over": "超過",
    "within": "以内",
    "days": [
      "月",
      "火",
      "水",
      "木",
      "金",
      "土",
      "日"
    ]
  },
  "zh": {
    "title": "服务延迟",
    "description": "比较每日p50、p95响应时间与p95目标值。",
    "worst": "最高p95",
    "overBudget": "个时段超出目标",
    "empty": "暂无延迟数据。",
    "table": "查看延迟数据",
    "caption": "服务延迟数据",
    "period": "时段",
    "budget": "目标",
    "over": "超出",
    "within": "范围内",
    "days": [
      "周一",
      "周二",
      "周三",
      "周四",
      "周五",
      "周六",
      "周日"
    ]
  }
} as const;

export type ServiceLatencyPoint = {
  label: string;
  p50Ms: number;
  p95Ms: number;
};

export function ServiceLatencyChart({
  data,
  budgetMs = 300,
  locale = "en",
}: {
  data: readonly ServiceLatencyPoint[];
  budgetMs?: number;
  locale?: keyof typeof copy;
}) {
  const t = copy[locale];
  const number = new Intl.NumberFormat(locale);
  const worst = data.length ? Math.max(...data.map((row) => row.p95Ms)) : 0;
  const breaches = data.filter((row) => row.p95Ms > budgetMs);

  return (
    <ChartContainer
      data-chart="service-latency"
      title={t.title}
      description={t.description}
      summary={data.length ? `${t.worst} ${number.format(worst)} ms · ${breaches.length}/${data.length} ${t.overBudget}` : t.empty}
      tableLabel={t.table}
      emptyLabel={t.empty}
      empty={!data.length}
      table={
        <table>
          <caption className="sr-only">{t.caption}</caption>
          <thead><tr><th>{t.period}</th><th>p50 (ms)</th><th>p95 (ms)</th><th>{t.budget}</th></tr></thead>
          <tbody>{data.map((row) => <tr key={row.label}><th scope="row">{row.label}</th><td>{row.p50Ms}</td><td>{row.p95Ms}</td><td>{row.p95Ms > budgetMs ? t.over : t.within}</td></tr>)}</tbody>
        </table>
      }
    >
      <LineChart data={data} accessibilityLayer margin={{ top: 12, right: 12, bottom: 4, left: 0 }}>
        <CartesianGrid vertical={false} stroke="var(--border)" strokeDasharray="3 4" />
        <XAxis dataKey="label" tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <YAxis domain={[0, "auto"]} width={52} tickFormatter={(value: number) => `${value}ms`} tick={{ fill: "var(--muted-foreground)" }} tickLine={false} axisLine={false} />
        <ChartLegend reference={{ value: `${t.budget} ${number.format(budgetMs)} ms`, color: "var(--destructive)", dashArray: "4 4" }} />
        <ReferenceLine ifOverflow="extendDomain" y={budgetMs} stroke="var(--destructive)" strokeDasharray="4 4" />
        <ChartTooltip formatter={(value) => `${Number(value)} ms`} />
        <Line dataKey="p50Ms" name="p50" legendType="plainline" stroke="var(--muted-foreground)" strokeWidth={2} strokeDasharray="7 4" dot={false} isAnimationActive={false} />
        <Line dataKey="p95Ms" name="p95" legendType="plainline" stroke="var(--primary)" strokeWidth={2.5} dot={{ r: 3 }} isAnimationActive={false} />
      </LineChart>
    </ChartContainer>
  );
}

const exampleLatency: ServiceLatencyPoint[] = [
  { label: "Mon", p50Ms: 80, p95Ms: 210 },
  { label: "Tue", p50Ms: 76, p95Ms: 195 },
  { label: "Wed", p50Ms: 95, p95Ms: 340 },
  { label: "Thu", p50Ms: 130, p95Ms: 470 },
  { label: "Fri", p50Ms: 100, p95Ms: 330 },
  { label: "Sat", p50Ms: 82, p95Ms: 240 },
  { label: "Sun", p50Ms: 79, p95Ms: 215 },
];

export function ServiceLatencyExample({ locale = "en" }: { locale?: keyof typeof copy }) {
  const data = exampleLatency.map((point, index) => ({ ...point, label: copy[locale].days[index] }));
  return <ServiceLatencyChart data={data} locale={locale} />;
}
```
