traceTree

tcmts.cli.traced.traceTree · #y6zkpnm3 · term · runs anywhere

One trace (an id, or the start of one), as a tree: its spans in the order they started, each with its depth (a span whose parent isn't kept here, a caller's, is a root of what this server did) and what it's of; the build that answered; and the jobs it enqueued, each a trace of its own. "none" or "many" if the id names no trace or more than one.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { spanOf } from "#codebase/tcmts/cli/traced";
import type { Of } from "#codebase/tcmts/cli/traced";
import { shortHash } from "#codebase/tcmts/core/hash";
import type { DatabaseSync } from "node:sqlite";

export function traceTree(
  db: DatabaseSync,
  cb: Codebase,
  id: string,
):
  | {
      trace: string;
      start: number;
      build?: string;
      spans: (Of & {
        id: string;
        kind: string;
        name: string;
        start: number;
        dur: number;
        error: string | null;
        attrs: Record<string, unknown>;
        link: string | null;
        depth: number;
        from?: Of;
      })[];
      caused: (Of & { trace: string })[];
    }
  | "none"
  | "many" {
  const ids = db
    .prepare("SELECT DISTINCT trace FROM spans WHERE trace LIKE ? LIMIT 2")
    .all(`${id.toLowerCase()}%`) as { trace: string }[];
  if (ids.length !== 1) return ids.length ? "many" : "none";
  const trace = ids[0]!.trace;
  const spans = db
    .prepare("SELECT * FROM spans WHERE trace = ? ORDER BY start")
    .all(trace) as unknown as {
    id: string;
    parent: string | null;
    kind: string;
    name: string;
    hash: string | null;
    build: string | null;
    start: number;
    dur: number;
    error: string | null;
    attrs: string;
    link: string | null;
  }[];
  const here = new Set(spans.map((s) => s.id));
  const children = new Map<string | null, typeof spans>();
  for (const s of spans) {
    const up = s.parent && here.has(s.parent) ? s.parent : null;
    children.set(up, [...(children.get(up) ?? []), s]);
  }
  const ordered: ReturnType<typeof shown>[] = [];
  const shown = (s: (typeof spans)[number], depth: number) => {
    const attrs = JSON.parse(s.attrs) as Record<string, unknown>;
    const from = attrs["tcm.from"];
    return {
      ...spanOf(cb, s),
      id: s.id,
      kind: s.kind,
      name: s.name,
      start: s.start,
      dur: s.dur,
      error: s.error,
      attrs,
      link: s.link,
      depth,
      ...(typeof from === "string"
        ? { from: spanOf(cb, { hash: from, name: shortHash(from) }) }
        : {}),
    };
  };
  const walk = (s: (typeof spans)[number], depth: number) => {
    ordered.push(shown(s, depth));
    for (const c of children.get(s.id) ?? []) walk(c, depth + 1);
  };
  for (const s of children.get(null) ?? []) walk(s, 0);
  const build = spans.find((s) => s.build)?.build;
  const caused = db
    .prepare("SELECT DISTINCT trace, name, hash FROM spans WHERE link LIKE ?")
    .all(`00-${trace}-%`) as { trace: string; name: string; hash: string | null }[];
  return {
    trace,
    start: spans[0]!.start,
    ...(build ? { build } : {}),
    spans: ordered,
    caused: caused.map((c) => ({ ...spanOf(cb, c), trace: c.trace })),
  };
}

uses

used by