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
72 changes: 47 additions & 25 deletions apps/local-ui/src/views/service-detail-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@ import { useMemo } from "react"
import { ArrowLeftIcon } from "@maple/ui/components/icons"
import { Badge } from "@maple/ui/components/ui/badge"
import { Button } from "@maple/ui/components/ui/button"
import { LatencyValue } from "@maple/ui/components/latency-value"
import { latencyToneClass } from "@maple/ui/lib/latency-tone"
import { ServiceDot } from "@maple/ui/components/service-dot"
import { Spinner } from "@maple/ui/components/ui/spinner"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@maple/ui/components/ui/table"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@maple/ui/components/ui/table"
import { QueryBuilderLineChart } from "@maple/ui/components/charts/line/query-builder-line-chart"
import { formatDuration, formatNumber } from "@maple/ui/format"
import { cn } from "@maple/ui/utils"
Expand Down Expand Up @@ -95,11 +90,7 @@ export function ServiceDetailView({ serviceName, onBack }: ServiceDetailViewProp
<Spinner />
</div>
) : overview.isError ? (
<ErrorState
label="service"
error={overview.error}
onRetry={() => overview.refetch()}
/>
<ErrorState label="service" error={overview.error} onRetry={() => overview.refetch()} />
) : !stats ? (
<EmptyState
title="No spans in this range"
Expand All @@ -119,9 +110,21 @@ export function ServiceDetailView({ serviceName, onBack }: ServiceDetailViewProp
value={`${(stats.errorRate * 100).toFixed(1)}%`}
danger={stats.errorRate > 0.05}
/>
<StatCard label="p50" value={formatDuration(stats.p50LatencyMs)} />
<StatCard label="p95" value={formatDuration(stats.p95LatencyMs)} />
<StatCard label="p99" value={formatDuration(stats.p99LatencyMs)} />
<StatCard
label="p50"
value={formatDuration(stats.p50LatencyMs)}
valueClassName={latencyToneClass(stats.p50LatencyMs, "p50")}
/>
<StatCard
label="p95"
value={formatDuration(stats.p95LatencyMs)}
valueClassName={latencyToneClass(stats.p95LatencyMs, "p95")}
/>
<StatCard
label="p99"
value={formatDuration(stats.p99LatencyMs)}
valueClassName={latencyToneClass(stats.p99LatencyMs, "p99")}
/>
</section>

<section className="space-y-2">
Expand Down Expand Up @@ -195,19 +198,21 @@ export function ServiceDetailView({ serviceName, onBack }: ServiceDetailViewProp
op.errorCount > 0 && "text-destructive",
)}
>
{formatNumber(op.estimatedErrorCount || op.errorCount)}
{formatNumber(
op.estimatedErrorCount || op.errorCount,
)}
</TableCell>
<TableCell className="text-right tabular-nums">
{(op.errorRate * 100).toFixed(1)}%
</TableCell>
<TableCell className="text-right tabular-nums">
{formatDuration(op.avgDurationMs)}
<TableCell className="text-right">
<LatencyValue ms={op.avgDurationMs} scale="avg" />
</TableCell>
<TableCell className="text-right tabular-nums">
{formatDuration(op.p50DurationMs)}
<TableCell className="text-right">
<LatencyValue ms={op.p50DurationMs} scale="p50" />
</TableCell>
<TableCell className="text-right tabular-nums">
{formatDuration(op.p95DurationMs)}
<TableCell className="text-right">
<LatencyValue ms={op.p95DurationMs} scale="p95" />
</TableCell>
</TableRow>
))}
Expand All @@ -223,13 +228,30 @@ export function ServiceDetailView({ serviceName, onBack }: ServiceDetailViewProp
)
}

function StatCard({ label, value, danger }: { label: string; value: string; danger?: boolean }) {
function StatCard({
label,
value,
danger,
valueClassName,
}: {
label: string
value: string
danger?: boolean
/** Applied after `danger`, so it wins — carries the latency magnitude ramp. */
valueClassName?: string
}) {
return (
<div className="rounded-md border bg-card px-3 py-2">
<div className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
{label}
</div>
<div className={cn("text-lg font-semibold tabular-nums", danger && "text-destructive")}>
<div
className={cn(
"text-lg font-semibold tabular-nums",
danger && "text-destructive",
valueClassName,
)}
>
{value}
</div>
</div>
Expand Down
24 changes: 10 additions & 14 deletions apps/local-ui/src/views/services-list-view.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { DatabaseIcon } from "@maple/ui/components/icons"
import { LatencyValue } from "@maple/ui/components/latency-value"
import { ServiceDot } from "@maple/ui/components/service-dot"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@maple/ui/components/ui/table"
import { formatDuration, formatNumber } from "@maple/ui/format"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@maple/ui/components/ui/table"
import { formatNumber } from "@maple/ui/format"
import { cn } from "@maple/ui/utils"
import { SearchableFilterSection } from "@maple/ui/components/filters/filter-section"
import {
Expand Down Expand Up @@ -148,18 +142,20 @@ function ServiceRow({ entry, onSelect }: { entry: ServiceCatalogEntry; onSelect:
</span>
</TableCell>
<TableCell className="text-right tabular-nums">{formatNumber(entry.spanCount)}</TableCell>
<TableCell
className={cn("text-right tabular-nums", entry.errorCount > 0 && "text-destructive")}
>
<TableCell className={cn("text-right tabular-nums", entry.errorCount > 0 && "text-destructive")}>
{formatNumber(entry.errorCount)}
</TableCell>
<TableCell
className={cn("text-right tabular-nums", entry.errorRate > 0.05 && "text-destructive")}
>
{(entry.errorRate * 100).toFixed(1)}%
</TableCell>
<TableCell className="text-right tabular-nums">{formatDuration(entry.p50LatencyMs)}</TableCell>
<TableCell className="text-right tabular-nums">{formatDuration(entry.p95LatencyMs)}</TableCell>
<TableCell className="text-right">
<LatencyValue ms={entry.p50LatencyMs} scale="p50" />
</TableCell>
<TableCell className="text-right">
<LatencyValue ms={entry.p95LatencyMs} scale="p95" />
</TableCell>
<TableCell className="text-right tabular-nums">{formatNumber(entry.logCount)}</TableCell>
</TableRow>
)
Expand Down
5 changes: 3 additions & 2 deletions apps/web/src/components/ai-elements/inline/inline-service.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from "@tanstack/react-router"
import { cn } from "@maple/ui/utils"
import { formatDuration, formatErrorRate, formatNumber } from "@/lib/format"
import { LatencyValue } from "@maple/ui/components/latency-value"
import { formatErrorRate, formatNumber } from "@/lib/format"
import type { InlineServiceData } from "./types"

export function InlineService({ data }: { data: InlineServiceData }) {
Expand Down Expand Up @@ -30,7 +31,7 @@ export function InlineService({ data }: { data: InlineServiceData }) {
)}
{data.p99Ms != null && (
<span className="ml-auto text-[10px] text-muted-foreground">
P99: <span className="font-mono">{formatDuration(data.p99Ms)}</span>
P99: <LatencyValue ms={data.p99Ms} scale="p99" />
</span>
)}
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { BaseComponentProps } from "@json-render/react"
import { cn } from "@maple/ui/utils"
import { formatDuration, formatErrorRate, formatNumber } from "@/lib/format"
import { LatencyValue } from "@maple/ui/components/latency-value"
import { formatErrorRate, formatNumber } from "@/lib/format"

interface ServiceTableProps {
services: Array<{
Expand Down Expand Up @@ -56,14 +57,14 @@ export function ServiceTable({ props }: BaseComponentProps<ServiceTableProps>) {
>
{formatErrorRate(svc.errorRate)}
</td>
<td className="py-1 pr-2 text-right font-mono text-muted-foreground">
{formatDuration(svc.p50Ms)}
<td className="py-1 pr-2 text-right">
<LatencyValue ms={svc.p50Ms} scale="p50" />
</td>
<td className="py-1 pr-2 text-right font-mono text-muted-foreground">
{formatDuration(svc.p95Ms)}
<td className="py-1 pr-2 text-right">
<LatencyValue ms={svc.p95Ms} scale="p95" />
</td>
<td className="py-1 text-right font-mono text-muted-foreground">
{formatDuration(svc.p99Ms)}
<td className="py-1 text-right">
<LatencyValue ms={svc.p99Ms} scale="p99" />
</td>
</tr>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BaseComponentProps } from "@json-render/react"
import { cn } from "@maple/ui/utils"
import { latencyToneClass } from "@maple/ui/lib/latency-tone"
import { formatDuration, formatErrorRate, formatNumber } from "@/lib/format"

interface SystemHealthCardProps {
Expand Down Expand Up @@ -49,8 +50,16 @@ export function SystemHealthCard({ props }: BaseComponentProps<SystemHealthCardP
: "text-severity-info"
}
/>
<StatCell label="P50 Latency" value={formatDuration(latency.p50Ms)} />
<StatCell label="P95 Latency" value={formatDuration(latency.p95Ms)} />
<StatCell
label="P50 Latency"
value={formatDuration(latency.p50Ms)}
color={latencyToneClass(latency.p50Ms, "p50")}
/>
<StatCell
label="P95 Latency"
value={formatDuration(latency.p95Ms)}
color={latencyToneClass(latency.p95Ms, "p95")}
/>
</div>
{topErrors.length > 0 && (
<div className="space-y-1">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { BaseComponentProps } from "@json-render/react"
import { cn } from "@maple/ui/utils"
import { LatencyValue } from "@maple/ui/components/latency-value"
import { formatDuration } from "@/lib/format"
import { HttpSpanLabel } from "@maple/ui/components/traces/http-span-label"

Expand Down Expand Up @@ -29,8 +30,12 @@ export function TraceList({ props }: BaseComponentProps<TraceListProps>) {
<div className="space-y-1.5">
{stats && (
<div className="flex gap-3 text-[10px] text-muted-foreground">
<span>P50: {formatDuration(stats.p50Ms)}</span>
<span>P95: {formatDuration(stats.p95Ms)}</span>
<span>
P50: <LatencyValue ms={stats.p50Ms} scale="p50" className="text-[10px]" />
</span>
<span>
P95: <LatencyValue ms={stats.p95Ms} scale="p95" className="text-[10px]" />
</span>
<span>Min: {formatDuration(stats.minMs)}</span>
<span>Max: {formatDuration(stats.maxMs)}</span>
</div>
Expand Down
14 changes: 13 additions & 1 deletion apps/web/src/components/dashboard/service-health-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Card } from "@maple/ui/components/ui/card"
import { Badge } from "@maple/ui/components/ui/badge"
import { Skeleton } from "@maple/ui/components/ui/skeleton"
import { formatErrorRate, formatLatency } from "@maple/ui/lib/format"
import { latencyToneClass } from "@maple/ui/lib/latency-tone"
import { cn } from "@maple/ui/utils"

import {
Expand Down Expand Up @@ -419,6 +420,14 @@ function ServiceHealthRow({
label="p95"
value={formatLatency(service.p95LatencyMs)}
tone={metricTone(latencyCause)}
// An open incident/anomaly is a stronger signal than raw
// magnitude, so it keeps the tone. Without one, fall back to
// the shared magnitude ramp.
valueClassName={
latencyCause === undefined
? latencyToneClass(service.p95LatencyMs, "p95")
: undefined
}
/>
<Metric
label="rps"
Expand All @@ -435,10 +444,13 @@ function Metric({
label,
value,
tone = "ok",
valueClassName,
}: {
label: string
value: string
tone?: "ok" | "warn" | "crit"
/** Applied after `tone`, so it wins — used to fall back to a magnitude ramp. */
valueClassName?: string
}) {
const toneClass =
tone === "crit"
Expand All @@ -448,7 +460,7 @@ function Metric({
: "text-foreground"
return (
<div className="flex w-16 flex-col items-end gap-0.5">
<span className={cn("leading-none", toneClass)}>{value}</span>
<span className={cn("leading-none", toneClass, valueClassName)}>{value}</span>
<span className="text-[10px] uppercase tracking-wider text-muted-foreground/60">{label}</span>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Skeleton } from "@maple/ui/components/ui/skeleton"
import { LatencyValue } from "@maple/ui/components/latency-value"

import type { CloudflareWorkerRow } from "@/api/warehouse/cloudflare-infra"
import { formatLatency, formatNumber } from "@/lib/format"
import { formatNumber } from "@/lib/format"
import { ColumnHead, TableShell, TableSkeleton, useTableSort } from "../primitives/data-table"
import { formatPercent } from "../format"
import { errorRateClass } from "./constants"
Expand Down Expand Up @@ -152,11 +153,13 @@ export function CloudflareWorkerTable({ workers, waiting }: CloudflareWorkerTabl
<div className="hidden w-[100px] text-right font-mono text-[12px] tabular-nums text-foreground/80 lg:block">
{formatNumber(worker.subrequests)}
</div>
<div className="hidden w-[90px] text-right font-mono text-[12px] tabular-nums text-foreground/80 md:block">
{formatLatency(worker.cpuP99Ms)}
<div className="hidden w-[90px] text-right text-[12px] md:block">
{/* Worker CPU time runs ~20x tighter than wall-clock latency,
hence the dedicated "cpu" budget. */}
<LatencyValue ms={worker.cpuP99Ms} scale="cpu" className="text-[12px]" />
</div>
<div className="w-[100px] text-right font-mono text-[12px] tabular-nums text-foreground/80">
{formatLatency(worker.durationP99Ms)}
<div className="w-[100px] text-right text-[12px]">
<LatencyValue ms={worker.durationP99Ms} scale="p99" className="text-[12px]" />
</div>
</div>
))}
Expand Down
28 changes: 23 additions & 5 deletions apps/web/src/components/infra/cloudflare/cloudflare-zone-table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link } from "@tanstack/react-router"

import { Skeleton } from "@maple/ui/components/ui/skeleton"
import { LatencyValue } from "@maple/ui/components/latency-value"

import type { CloudflareZoneRow } from "@/api/warehouse/cloudflare-infra"
import { formatLatency, formatNumber } from "@/lib/format"
Expand Down Expand Up @@ -193,12 +194,29 @@ export function CloudflareZoneTable({ zones, waiting }: CloudflareZoneTableProps
<div className="hidden w-[90px] text-right font-mono text-[12px] tabular-nums text-foreground/80 lg:block">
{formatNumber(zone.visits)}
</div>
<div className="hidden w-[90px] text-right font-mono text-[12px] tabular-nums text-foreground/80 lg:block">
{formatOptionalLatency(zone.ttfbP50Ms)}
<div className="hidden w-[90px] text-right text-[12px] lg:block">
<LatencyValue
ms={zone.ttfbP50Ms}
scale="p50"
format={formatOptionalLatency}
className="text-[12px]"
/>
</div>
{numCell(formatOptionalLatency(zone.ttfbP99Ms))}
<div className="hidden w-[90px] text-right font-mono text-[12px] tabular-nums text-foreground/80 lg:block">
{formatOptionalLatency(zone.originP99Ms)}
<div className="w-[90px] text-right text-[12px]">
<LatencyValue
ms={zone.ttfbP99Ms}
scale="p99"
format={formatOptionalLatency}
className="text-[12px]"
/>
</div>
<div className="hidden w-[90px] text-right text-[12px] lg:block">
<LatencyValue
ms={zone.originP99Ms}
scale="p99"
format={formatOptionalLatency}
className="text-[12px]"
/>
</div>
</Link>
))}
Expand Down
Loading
Loading