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
37 changes: 34 additions & 3 deletions src/lib/components/Chart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,41 @@

const fmtY = $derived(unit === 'bytes' ? formatBytes : formatNumber)

// Every pod of one deployment shares a long, identical name prefix — the k8s
// resource name and project id (`web-123-…`, or `0d456-123-…` for id-named
// deployments). That prefix is the same on every line, so in the legend it's
// pure noise that pushes the part that actually differs (the pod hash) off
// the edge. Strip the longest prefix common to all lines, trimmed back to a
// '-' boundary so whole name segments are removed (never a hash mid-token).
// This also hides the otherwise-opaque `0d<id>` resource name.
const podPrefixLen = $derived.by(() => {
/** @type {string[]} */
const names = []
for (const s of series ?? []) {
for (const l of (s.lines ?? [])) names.push(l.name ?? '')
}
if (names.length < 2) return 0
let p = names[0]
for (let i = 1; i < names.length && p; i++) {
const n = names[i]
let j = 0
while (j < p.length && j < n.length && p[j] === n[j]) j++
p = p.slice(0, j)
}
const cut = p.lastIndexOf('-')
return cut >= 0 ? cut + 1 : 0
})

/** @param {string} name */
function podLabel (name) {
return name.slice(podPrefixLen) || name
}

// Flatten {prefix, lines[]} into one drawable line each. A single-line series
// keeps the bare prefix ("Usage"); multi-line series disambiguate with the
// line's own name ("Usage · web"). The first solid line gets the area fill so
// the panel reads as one primary trend with reference lines layered over it.
// pod's distinguishing suffix ("Usage · x2k9p"). The first solid line gets the
// area fill so the panel reads as one primary trend with reference lines
// layered over it.
const flat = $derived.by(() => {
let areaUsed = false
/** @type {import('$lib/charts/util').LineSeries[]} */
Expand All @@ -63,7 +94,7 @@
const area = !dashed && !areaUsed
if (area) areaUsed = true
out.push({
name: lines.length > 1 ? `${s.prefix} · ${l.name}` : s.prefix,
name: lines.length > 1 ? `${s.prefix} · ${podLabel(l.name)}` : s.prefix,
color: resolveColor(s.color),
dashed,
area,
Expand Down
35 changes: 35 additions & 0 deletions src/lib/deployment/podName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Pod names are `<kubeName>-<projectID>-<replicaSetHash>-<podHash>`. The
// `<kubeName>-<projectID>` part is the deployment's k8s service name — exactly
// what the API returns as `internalAddress` — and is identical on every pod, so
// repeating it (in the logs pod chip, or inside event messages) is pure noise
// that buries the part identifying the individual pod. For id-named deployments
// it's also the opaque `d<id>` resource name the user never chose. These
// helpers strip that prefix wherever it appears, leaving just the pod-specific
// suffix (`<replicaSetHash>-<podHash>`).

/** @param {string} s */
function escapeRegExp (s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

/**
* Build a function that strips the deployment's `<kubeName>-<projectID>-` prefix
* from any text — a bare pod name (logs) or pod references embedded in free-text
* event messages.
*
* @param {{ internalAddress?: string }} [deployment]
* @returns {(text?: string) => string}
*/
export function podPrefixStripper (deployment) {
const addr = (deployment?.internalAddress ?? '').trim()
const alts = []
// Primary: the exact service name (`<kubeName>-<projectID>`). Guard against
// the field ever being an IP or empty by requiring the `<name>-<digits>`
// shape so we never build a prefix that matches arbitrary text.
if (/^[a-z0-9-]+-\d+$/i.test(addr)) alts.push(escapeRegExp(addr))
// Fallback: any id-based prefix (`d<id>-<projectID>`), so the opaque
// resource name is still hidden even when internalAddress is unavailable.
alts.push('d\\d+-\\d+')
const re = new RegExp('(?:' + alts.join('|') + ')-', 'g')
return (text) => (text ?? '').replace(re, '')
}
29 changes: 21 additions & 8 deletions src/lib/server/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ function metricLine (name, base) {
return [{ name, points }]
}

// Two pods of one id-named deployment (`d<id>-<projectID>-<rsHash>-<podHash>`),
// matching how the live (non-aggregate) `deployment.metrics` lines are keyed by
// full pod name. Lets the metrics page exercise the legend's shared-prefix
// stripping (only the trailing pod hash should show).
const MOCK_PODS = ['d128-77-7d8f9b6c5-x2k9p', 'd128-77-7d8f9b6c5-q8m2t']

/** @param {number} base */
function metricPodLines (base) {
return MOCK_PODS.map((name, i) => metricLine(name, base * (i === 0 ? 1 : 0.72))[0])
}

/**
* dailyMetricLine builds a single daily series — one [unixSeconds, value] point
* per day at midnight for the trailing 30 days — for the daily usage charts.
Expand Down Expand Up @@ -163,7 +174,9 @@ function deployment (project = 'acme') {
podsUrl: '',
statusUrl: HEALTHY_STATUS_URL,
address: '203.0.113.10',
internalAddress: '10.0.0.10',
// `<kubeName>-<projectID>` (the in-cluster service name); id-named here so
// the logs/events pages exercise pod-name prefix stripping.
internalAddress: 'd128-77',
status: 'success',
action: 'deploy',
allocatedPrice: 120.5,
Expand Down Expand Up @@ -870,13 +883,13 @@ const handlers = {
}
})),
'deployment.metrics': () => ok({
cpuUsage: metricLine('web', 0.3),
cpuLimit: metricLine('limit', 0.5),
memoryUsage: metricLine('web', 268435456),
memory: metricLine('allocated', 402653184),
memoryLimit: metricLine('limit', 536870912),
requests: metricLine('web', 120),
egress: metricLine('web', 1048576)
cpuUsage: metricPodLines(0.3),
cpuLimit: metricPodLines(0.5),
memoryUsage: metricPodLines(268435456),
memory: metricPodLines(402653184),
memoryLimit: metricPodLines(536870912),
requests: metricPodLines(120),
egress: metricPodLines(1048576)
}),

'disk.list': () => list(disks),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
<script>
import { onMount } from 'svelte'
import { podPrefixStripper } from '$lib/deployment/podName'

const { data } = $props()

const deployment = $derived(data.deployment)

// Pod names show up inside event reasons/messages; strip the shared
// `<kubeName>-<projectID>-` prefix so only the pod-distinguishing suffix
// remains (the full text stays available on hover).
const stripPods = $derived(podPrefixStripper(deployment))

const POLL_INTERVAL_MS = 5000

/** @type {Array<{type: string, reason: string, message: string, lastSeen: string}>} */
Expand Down Expand Up @@ -451,8 +457,8 @@
</span>
<span class="event-row__mark" aria-hidden="true"></span>
<span class="event-row__type">{ev.type}</span>
<span class="event-row__reason" title={ev.reason}>{ev.reason}</span>
<span class="event-row__msg">{ev.message}</span>
<span class="event-row__reason" title={ev.reason}>{stripPods(ev.reason)}</span>
<span class="event-row__msg" title={ev.message}>{stripPods(ev.message)}</span>
</li>
{/each}
</ol>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<script>
import { onMount } from 'svelte'
import { SvelteSet } from 'svelte/reactivity'
import { podPrefixStripper } from '$lib/deployment/podName'

const { data } = $props()

const deployment = $derived(data.deployment)

// Drop the `<kubeName>-<projectID>-` prefix shared by every pod of this
// deployment so the chip shows only the pod-distinguishing suffix.
const podLabel = $derived(podPrefixStripper(deployment))

// Cap how many lines we keep in memory and render. Without this the
// previous implementation accumulated every line into a single string and
// re-prepended each event (O(n) string copy), which froze the page on
Expand Down Expand Up @@ -776,7 +781,7 @@
<span class="log-row__mark" aria-hidden="true"></span>
<span class="log-row__pod" style="--pod-h: {podHue(line.pod)}"
title={line.pod}>
{line.pod}
{podLabel(line.pod)}
</span>
<span class="log-row__msg">{line.log}</span>
</li>
Expand Down
10 changes: 6 additions & 4 deletions src/routes/api/mock-events/+server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,21 @@

import { env } from '$env/dynamic/private'

// Pod references use full id-named pod names (`d128-77-<rsHash>-<podHash>`) so
// the events page exercises pod-name prefix stripping in free-text messages.
const NORMAL = [
{ reason: 'Scheduled', message: 'Successfully assigned acme/web-7d4f-x1 to gke-node-rcf2-pool-1-3a8f' },
{ reason: 'Scheduled', message: 'Successfully assigned acme/d128-77-7d8f9b6c5-x2k9p to gke-node-rcf2-pool-1-3a8f' },
{ reason: 'Pulling', message: 'Pulling image "registry.deploys.app/acme/web:latest"' },
{ reason: 'Pulled', message: 'Successfully pulled image "registry.deploys.app/acme/web:latest" in 2.317s' },
{ reason: 'Created', message: 'Created container web' },
{ reason: 'Started', message: 'Started container web' },
{ reason: 'SuccessfulCreate', message: 'Created pod: web-7d4f-x2' },
{ reason: 'SuccessfulDelete', message: 'Deleted pod: web-6c1e-old' },
{ reason: 'SuccessfulCreate', message: 'Created pod: d128-77-7d8f9b6c5-q8m2t' },
{ reason: 'SuccessfulDelete', message: 'Deleted pod: d128-77-5b9c4d2a1-h3n7v' },
{ reason: 'Killing', message: 'Stopping container web' }
]

const WARNINGS = [
{ reason: 'BackOff', message: 'Back-off restarting failed container web in pod web-7d4f-x1' },
{ reason: 'BackOff', message: 'Back-off restarting failed container web in pod d128-77-7d8f9b6c5-x2k9p' },
{ reason: 'Unhealthy', message: 'Readiness probe failed: HTTP probe failed with statuscode: 503' },
{ reason: 'FailedScheduling', message: '0/5 nodes are available: 5 Insufficient memory.' },
{ reason: 'FailedMount', message: 'Unable to attach or mount volumes: unmounted volumes=[data]' }
Expand Down
9 changes: 8 additions & 1 deletion src/routes/api/mock-logs/+server.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@

import { env } from '$env/dynamic/private'

const PODS = ['web-7d4f-x1', 'web-7d4f-x2', 'worker-6f9-a1']
// Full pod names (`<kubeName>-<projectID>-<rsHash>-<podHash>`) for an id-named
// deployment, so the logs page exercises pod-name prefix stripping — only the
// `<rsHash>-<podHash>` suffix should show in the chip.
const PODS = [
'd128-77-7d8f9b6c5-x2k9p',
'd128-77-7d8f9b6c5-q8m2t',
'd128-77-5b9c4d2a1-h3n7v'
]

const TEMPLATES = [
() => `GET /api/users?page=${(Math.random() * 5 | 0) + 1} 200 ${(Math.random() * 200 | 0) + 20}ms`,
Expand Down