TraceOfPage
is an async component
One row of the traces page as a page of its own: a definition's spans of a kind (each version of it alike), or a
page's, a view's or a statement's, since long ago (1h, 24h, 7d). Its calls, errors, p50 and p95; what runs worse
than its version before and the change that did it; each version side by side; where its calls came from in the
browser; its slowest and latest traces, each a link to its waterfall.
import { shownMs, sinceMs } from "#codebase/tcmts/cli/main"; import { traceOf } from "#codebase/tcmts/cli/traced"; import { shortHash } from "#codebase/tcmts/core/hash"; import { ago, at, codeHref, shareNow } from "#codebase/tcmts/share"; import { StudioFrame, studioShare, traceOfHref, tracesFrom } from "#codebase/tcmts/studio"; export async function TraceOfPage({ name, kind, since, from, }: { name: string; kind: string; since: string; from: string; }) { studioShare(); const s = shareNow(); if (!s.signedIn) return <StudioFrame tab="traces" title="Sign in">{null}</StudioFrame>; const ago_ = ["1h", "24h", "7d"].includes(since) ? since : "24h"; const t = tracesFrom(s.cb, from)((db) => traceOf(db, s.cb, name, kind, Date.now() - sinceMs(ago_))); const q = from === "production" ? "?from=production" : ""; const kindSaid = kind === "client" ? "from the browser" : kind; const row = (x: { trace: string; start: number; dur: number; error: string | null; hash: string | null }) => ( <a className="lrow tracerow" key={`${x.trace} ${x.start}`} href={at(`/trace/${x.trace}${q}`)}> <div> <span className="mono">trace {x.trace.slice(0, 8)}</span>{" "} <span className="muted"> {ago(x.start)} {t && t.versions.length > 1 && x.hash ? ` · ${shortHash(x.hash)}` : ""} </span> {x.error && <> <code className="bad">{x.error.split("\n")[0]}</code></>} </div> <span>{shownMs(x.dur)}</span> </a> ); return ( <StudioFrame tab="traces" title={`${name} · traces`}> <div className="crumbs"> <a href={at(`/traces${ago_ === "24h" ? q : `?since=${ago_}${q.replace("?", "&")}`}`)}> {from === "production" ? "Production's traces" : "Traces"} </a> <span className="muted">/</span> </div> <div className="pagehead"> <h2 className="title mono" style={{ fontSize: 20 }}>{name}</h2> <span className="muted">{kindSaid}, in the last {ago_}</span> <span className="right actions"> {t?.definition && <a className="btn small" href={codeHref(name)}>Its code</a>} <span className="seg"> {["1h", "24h", "7d"].map((x) => ( <a key={x} className="btn small" href={`${traceOfHref(name, kind, from)}${x === "24h" ? "" : `&since=${x}`}`} {...(x === ago_ ? { "data-selected": "" } : {})} > {x} </a> ))} </span> </span> </div> {!t ? ( <div className="box"><div className="empty">Nothing recorded for it in the last {ago_}.</div></div> ) : ( <> {t.change?.verdict?.worse && ( <div className="notice" style={{ borderColor: "var(--warn)", background: "var(--warnbg)" }}> <b className="warn">Worse than its version before: {t.change.verdict.said}.</b>{" "} {t.change.changed.length ? <>What changed: {t.change.changed.join(", ")}. </> : null} {t.versions[0]?.since && <a href={at(`/history/${t.versions[0].since.causal}`)}>See the change</a>} </div> )} <div className="box"> <div className="boxbody"> <div className="stats"> <div className="stat"><b>{t.n}</b>{kind === "render" ? "renders" : kind === "sql" ? "statements" : "calls"}</div> <div className="stat"><b className={t.errors ? "bad" : ""}>{t.errors}</b>errors</div> <div className="stat"><b>{shownMs(t.p50)}</b>p50</div> <div className="stat"><b>{shownMs(t.p95)}</b>p95</div> <div className="stat"><b>{shownMs(t.max)}</b>the longest</div> </div> </div> </div> <div className="cols-2" style={{ display: "grid", gap: 24 }}> <div> <div className="box"> <div className="boxhead"> <b>Latest</b> <span className="muted">each trace, to its waterfall</span> </div> {t.latest.map(row)} </div> </div> <aside className="side"> <div className="sect"> <h4>Slowest</h4> {t.slowest.map((x) => ( <div key={`${x.trace} ${x.start}`}> <a className="mono" href={at(`/trace/${x.trace}${q}`)}>{x.trace.slice(0, 8)}</a>{" "} <span className="muted">{shownMs(x.dur)} · {ago(x.start)}</span> </div> ))} </div> {t.versions.length > 1 && ( <div className="sect"> <h4>Versions</h4> {t.versions.map((v) => ( <div key={v.hash} style={{ marginBottom: 6 }}> <span className="mono">{shortHash(v.hash)}</span> {v.now && <span className="muted"> (now)</span>}{" "} <span className="muted"> {v.n} · p95 {shownMs(v.p95)}{v.errors ? ` · ${v.errors} failed` : ""} </span> {v.since && ( <a href={at(`/history/${v.since.causal}`)} style={{ display: "block", fontSize: 12.5 }}> {v.since.title} </a> )} </div> ))} </div> )} {t.callers.length > 0 && ( <div className="sect"> <h4>Called from</h4> {t.callers.map((c) => ( <div key={c.caller?.hash ?? "page"}> {c.caller && (!c.caller.gone || c.caller.earlier) ? ( <a className="mono" href={codeHref(c.caller.label)}>{c.caller.label}</a> ) : ( <span className="mono">{c.caller?.label ?? "the page"}</span> )}{" "} <span className="muted">{c.n} · waited p95 {shownMs(c.p95)}</span> </div> ))} </div> )} </aside> </div> </> )} </StudioFrame> ); }
uses
- tcmts.cli.main.shownMs
- tcmts.cli.main.sinceMs
- tcmts.cli.traced.traceOf
- tcmts.core.hash.shortHash
- tcmts.share.ago
- tcmts.share.at
- tcmts.share.codeHref
- tcmts.share.shareNow
- tcmts.studio.StudioFrame
- tcmts.studio.studioShare
- tcmts.studio.traceOfHref
- tcmts.studio.tracesFrom