TracePage
is an async component
One trace (an id, or its start), as tcm traces --trace shows it, drawn as a waterfall on one clock: each span by
where it ran (the browser, the server, the database) under the one it's inside, its definition linked to its page,
the definition that made a call from the browser, an error under the span that threw it, and the jobs it enqueued.
import { shownMs } from "#codebase/tcmts/cli/main"; import { traceTree } from "#codebase/tcmts/cli/traced"; import { shortHash } from "#codebase/tcmts/core/hash"; import { at, codeHref, shareNow } from "#codebase/tcmts/share"; import { StudioFrame, studioShare, tracesFrom } from "#codebase/tcmts/studio"; export async function TracePage({ id, from }: { id: string; from: string }) { studioShare(); const s = shareNow(); if (!s.signedIn) return ( <StudioFrame tab="traces" title="Sign in"> {null} </StudioFrame> ); const t = tracesFrom(s.cb, from)((db) => traceTree(db, s.cb, id)); const q = from === "production" ? "?from=production" : ""; if (!t || t === "none" || t === "many") return ( <StudioFrame tab="traces" title="No such trace"> <div className="empty"> {t === "many" ? ( <> More than one trace starts <code>{id}</code>. </> ) : ( <> No trace <code>{id}</code> here. </> )} </div> </StudioFrame> ); const end = Math.max(...t.spans.map((x) => x.start + x.dur)); const span = Math.max(end - t.start, 0.001); const where = (kind: string) => ["client", "view", "navigate", "error"].includes(kind) ? "brw" : ["sql", "transaction"].includes(kind) ? "any" : "srv"; return ( <StudioFrame tab="traces" title={`Trace ${t.trace.slice(0, 8)}`}> <div className="pagehead"> <h2 className="title">Trace {t.trace.slice(0, 8)}</h2> <span className="muted"> {new Date(t.start).toISOString().replace("T", " ").slice(0, 19)} {t.build ? ` · build ${shortHash(t.build)}` : ""} · {shownMs(span)} · the browser's spans on the server's clock </span> <a className="right" href={at(`/traces${q}`)}> All traces </a> </div> <div className="box"> <div className="boxhead"> <span className="legend"> <span className="li"> <span className="dot brw" /> browser </span> <span className="li"> <span className="dot srv" /> server </span> <span className="li"> <span className="dot any" /> database </span> </span> </div> <div className="wf"> {t.spans.map((x) => ( <div className="r" key={x.id}> <span className="lbl" style={{ paddingLeft: x.depth * 12 }} title={x.label}> <span className="muted">{x.kind}</span>{" "} {x.name === x.kind ? null : x.hash && (!x.gone || x.earlier) ? ( <a href={codeHref(x.label)}>{x.label}</a> ) : ( x.label )} {x.earlier && <span className="muted"> (an earlier version)</span>} {x.from && <span className="muted"> from {x.from.label}</span>} {x.attrs["db.rows"] !== undefined && ( <span className="muted"> {String(x.attrs["db.rows"])} row(s)</span> )} {x.attrs["url.path"] !== undefined && ( <span className="muted"> {String(x.attrs["url.path"])}</span> )} </span> <span className="track"> <span className={`b ${x.error ? "bad" : where(x.kind)}`} style={{ left: `${((x.start - t.start) / span) * 100}%`, width: `${(x.dur / span) * 100}%`, }} /> </span> <span className="muted" style={{ textAlign: "right" }}> {shownMs(x.dur)} </span> {x.error && ( <span className="err" style={{ paddingLeft: 14 + x.depth * 12 }}> {x.error.split("\n")[0]} </span> )} </div> ))} </div> </div> {t.caused.length > 0 && ( <div className="box"> <div className="boxhead"> <b>Jobs it enqueued</b> </div> {t.caused.map((c) => ( <div className="lrow" key={c.trace}> <div> <span className="mono">{c.label}</span> </div> <a className="btn small" href={at(`/trace/${c.trace}${q}`)}> trace {c.trace.slice(0, 8)} </a> </div> ))} </div> )} </StudioFrame> ); }
uses
- tcmts.cli.main.shownMs
- tcmts.cli.traced.traceTree
- tcmts.core.hash.shortHash
- tcmts.share.at
- tcmts.share.codeHref
- tcmts.share.shareNow
- tcmts.studio.StudioFrame
- tcmts.studio.studioShare
- tcmts.studio.tracesFrom