TracesPage
is an async component
What the app did on this machine (tcm dev and tcm start record it), or from production (its server's traces,
fetched: fetchProduction, set up here, ProductionForm), as tcm traces says it: what runs worse
than its version before and the change that did it, then what it spent its time on by definition as named now
(show "errors": the errors instead, grouped by the definition that threw them), since long ago (1h, 24h, 7d).
import { shownMs, sinceMs } from "#codebase/tcmts/cli/main"; import { traceErrors, traceTotals, traceWorse } from "#codebase/tcmts/cli/traced"; import type { Of } from "#codebase/tcmts/cli/traced"; import { shortHash } from "#codebase/tcmts/core/hash"; import { ago, at, codeHref, Fold, NavTabs, servedCodebase, shareNow } from "#codebase/tcmts/share"; import { FetchProductionButton, production, ProductionForm, StudioFrame, studioShare, traceOfHref, tracesFrom, } from "#codebase/tcmts/studio"; export async function TracesPage({ since, show, from: source, }: { since: string; show: 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 from = Date.now() - sinceMs(ago_); const errorsShown = show === "errors"; const prod = source === "production"; const known = prod ? production(servedCodebase().cb) : undefined; const toTrace = (id: string) => at(`/trace/${id}${prod ? "?from=production" : ""}`); const t = tracesFrom(s.cb, source)((db) => { const rows = traceTotals(db, s.cb, from, 40, { byNameNow: true }); return { rows, worse: traceWorse(db, s.cb, from, rows), errors: traceErrors(db, s.cb, from, 40), }; }); const of = (o: Of & { versions?: number }) => ( <span className="lbl"> {o.label} {o.hash && o.gone && o.earlier ? ( <span className="muted"> ({(o.versions ?? 1) > 1 ? `${o.versions} earlier versions` : "an earlier version"})</span> ) : o.hash && (o.versions ?? 1) > 1 ? ( <span className="muted"> ({o.versions} versions)</span> ) : o.hash && o.gone ? ( <span className="muted"> ({shortHash(o.hash)}, no longer named)</span> ) : null} </span> ); const link = (p: { since?: string; show?: string }) => { const q = new URLSearchParams(); const sn = p.since ?? ago_; const sh = p.show ?? show; if (sn !== "24h") q.set("since", sn); if (sh) q.set("show", sh); if (prod) q.set("from", "production"); return at(`/traces${q.size ? `?${q}` : ""}`); }; const most = Math.max(1, ...(t?.rows ?? []).map((r) => r.total)); const failed = (t?.errors ?? []).reduce((n, e) => n + e.times, 0); return ( <StudioFrame tab="traces" title="Traces"> <div className="pagehead"> <h2 className="title">Traces</h2> <span className="muted"> {prod ? "what the app did in production" : "what the app did on this machine"}, by definition as named now </span> <span className="right seg"> <a className="btn small" href={at("/traces")} {...(prod ? {} : { "data-selected": "" })}> This machine </a> <a className="btn small" href={at("/traces?from=production")} {...(prod ? { "data-selected": "" } : {})}> Production </a> </span> <span className="seg"> {["1h", "24h", "7d"].map((x) => ( <a key={x} className="btn small" href={link({ since: x })} {...(x === ago_ ? { "data-selected": "" } : {})}> {x} </a> ))} </span> </div> <div className="subtabs" style={{ marginBottom: 16, ...(prod && !known ? { display: "none" } : {}) }}> <NavTabs label="Traces" selected={errorsShown ? "errors" : "summary"} tabs={[ { id: "summary", label: "Where the time goes", href: link({ show: "" }) }, { id: "errors", label: "Errors", href: link({ show: "errors" }), ...(failed ? { count: failed } : {}) }, ]} /> </div> {prod && known && ( <div className="notice" style={{ marginBottom: 16 }}> {`From ${known.url}, ${known.fetched ? `fetched ${ago(known.fetched)}` : "not fetched yet"}. `} <FetchProductionButton label={known.fetched ? "Fetch again" : "Fetch"} /> </div> )} {prod && !known ? ( <div className="box"> <div className="boxhead"> <b>Production</b> </div> <div className="boxbody"> <p className="muted" style={{ marginTop: 0 }}> The server <code>tcm start</code> runs the app on hands its traces to whoever holds its traces token: set{" "} <code>TCM_TRACES_TOKEN</code> there to a secret of your own, and give it here with where it runs. Its traces are fetched into the studio, which reads them as it reads this machine's. </p> <ProductionForm url="" /> </div> </div> ) : !t ? ( <div className="box"> <div className="empty"> {prod ? ( "No traces from production yet." ) : ( <> No traces yet: <code>tcm dev</code> and <code>tcm start</code> record them as the app is used. </> )} </div> </div> ) : ( <> {t.worse.map((w) => ( <div className="notice" key={`${w.name} ${w.kind}`} style={{ borderColor: "var(--warn)", background: "var(--warnbg)" }}> <b className="warn"> <a href={traceOfHref(w.name, w.kind, source)} style={{ color: "inherit" }}>{w.name}</a> ({w.kind}) runs worse than its version before: {w.said}. </b>{" "} {w.changed.length ? <>What changed: {w.changed.join(", ")}. </> : null} {w.since && <a href={at(`/history/${w.since.causal}`)}>“{w.since.title}”</a>} </div> ))} {errorsShown ? ( t.errors.length ? ( <> <p className="muted" style={{ margin: "0 0 12px" }}> By the definition that threw them, as named now: alike ones together, once a trace. </p> {t.errors.map((e) => ( <div className="box" key={`${e.name ?? ""} ${e.error}`}> <div className="boxhead"> {e.name ? ( <a className="mono" href={codeHref(e.name)}>{e.name}</a> ) : ( <b>Not thrown in the codebase's code</b> )} {e.inBrowser && <span className="muted">in the browser</span>} <span className="right muted"> {e.times === 1 ? "once" : `${e.times} times`}, last {ago(e.last)} </span> </div> <div className="boxbody errbody"> <pre className="code errmsg">{e.error}</pre> {(e.began || e.through.length > 0) && ( <div className="muted errwhere"> {e.began && ( <> Began with {shortHash(e.versions[0]!)}, in{" "} <a href={at(`/history/${e.began.causal}`)}>{e.began.title}</a> {e.through.length > 0 ? ". " : ""} </> )} {e.through.length > 0 && ( <> Went up through{" "} {e.through .slice(0, 4) .map((o, i) => (o.label === e.throughKinds[i] ? o.label : `${o.label} (${e.throughKinds[i]})`)) .join(", ")} . </> )} </div> )} </div> {(() => { const kinds = new Set(e.traces.map((y) => y.kind)); const row = (x: (typeof e.traces)[number]) => { return ( <a className="lrow tracerow" key={x.trace} href={toTrace(x.trace)}> <div> <span className="mono">trace {x.trace.slice(0, 8)}</span> {kinds.size > 1 && <span className="muted"> · in a {x.kind === "error" ? "page" : x.kind}</span>} {e.versions.length > 1 && x.thrown && ( <span className="muted"> · thrown by {shortHash(x.thrown)}</span> )} </div> <span className="muted">{ago(x.start)}</span> </a> ); }; const rest = e.times - 5; return ( <> {e.traces.slice(0, 5).map(row)} {rest > 0 && ( <div className="tracerest"> <Fold head={<span className="muted">{rest} earlier</span>}> {e.traces.slice(5).map(row)} {e.times > e.traces.length && ( <div className="lrow muted" style={{ fontSize: 12.5 }}> <div>and {e.times - e.traces.length} before those, not listed</div> </div> )} </Fold> </div> )} </> ); })()} </div> ))} </> ) : ( <div className="box"> <div className="empty">No errors in the last {ago_}.</div> </div> ) ) : ( <div className="box"> {t.rows.length ? ( <div className="trows"> <div className="tr th"> <span>definition</span> <span>kind</span> <span className="num">calls</span> <span className="num">errors</span> <span className="num">p50</span> <span className="num">p95</span> <span>time, all told</span> </div> {t.rows.map((r) => ( <a className="tr" key={`${r.hash ?? r.name} ${r.kind}`} href={traceOfHref(r.label, r.kind, source)}> {of(r)} <span className="muted">{r.kind === "client" ? "from the browser" : r.kind}</span> <span className="num">{r.n}</span> <span className={`num${r.errors ? " bad" : ""}`}>{r.errors}</span> <span className="num">{shownMs(r.p50)}</span> <span className="num">{shownMs(r.p95)}</span> <span title={shownMs(r.total)}> <span className="sr-only">{shownMs(r.total)}</span> <span className="meter"> <span className="fill" style={{ width: `${Math.max(2, (r.total / most) * 100)}%`, background: ["client", "view", "navigate", "error"].includes(r.kind) ? "var(--brw)" : ["sql", "transaction"].includes(r.kind) ? "var(--any)" : "var(--srv)", }} /> </span> </span> </a> ))} </div> ) : ( <div className="empty">Nothing recorded in the last {ago_}.</div> )} </div> )} </> )} </StudioFrame> ); }
uses
- tcmts.cli.main.shownMs
- tcmts.cli.main.sinceMs
- tcmts.cli.traced.Of
- tcmts.cli.traced.traceErrors
- tcmts.cli.traced.traceTotals
- tcmts.cli.traced.traceWorse
- tcmts.core.hash.shortHash
- tcmts.share.Fold
- tcmts.share.NavTabs
- tcmts.share.ago
- tcmts.share.at
- tcmts.share.codeHref
- tcmts.share.servedCodebase
- tcmts.share.shareNow
- tcmts.studio.FetchProductionButton
- tcmts.studio.ProductionForm
- tcmts.studio.StudioFrame
- tcmts.studio.production
- tcmts.studio.studioShare
- tcmts.studio.traceOfHref
- tcmts.studio.tracesFrom