traceTotals

tcmts.cli.traced.traceTotals · #67su2ncr · term · runs anywhere

What the app spent its time on since since, by definition (and kind) as named now, the most time first: calls, errors, p50, p95, the longest and the total. A client row is a call as the page waited for it. Each version (hash) is a row of its own, but byNameNow: then a definition's earlier versions (Of.earlier) are in its row, which says how many versions it holds (and is named as the definition is, the version named now leading, if it ran).

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

export function traceTotals(
  db: DatabaseSync,
  cb: Codebase,
  since: number,
  limit: number,
  opts: { byNameNow?: boolean } = {},
): (Of & {
  kind: string;
  name: string;
  n: number;
  errors: number;
  p50: number;
  p95: number;
  max: number;
  total: number;
  versions: number;
})[] {
  const groups = (
    db
      .prepare(
        `SELECT hash, kind, name, count(*) AS n, sum(dur) AS total, max(dur) AS max, sum(error IS NOT NULL) AS errors
         FROM spans WHERE start >= ? GROUP BY coalesce(hash || ' ' || kind, kind || ' ' || name) ORDER BY total DESC LIMIT ?`,
      )
      .all(since, opts.byNameNow ? limit * 5 : limit) as {
      hash: string | null;
      kind: string;
      name: string;
      n: number;
      total: number;
      max: number;
      errors: number;
    }[]
  ).map((g) => ({ ...g, of: spanOf(cb, g), hashes: g.hash ? [g.hash] : [] }));
  let rows = groups;
  if (opts.byNameNow) {
    const byKey = new Map<string, (typeof groups)[number]>();
    for (const g of groups) {
      const key =
        g.hash && (!g.of.gone || g.of.earlier)
          ? `${g.of.label} ${g.kind}`
          : `${g.hash ?? g.name} ${g.kind}`;
      const was = byKey.get(key);
      if (!was) {
        byKey.set(key, g);
        continue;
      }
      // (the version named now leads: its hash, its name)
      const lead = was.of.gone && !g.of.gone ? g : was;
      byKey.set(key, {
        ...lead,
        n: was.n + g.n,
        total: was.total + g.total,
        max: Math.max(was.max, g.max),
        errors: was.errors + g.errors,
        hashes: [...was.hashes, ...g.hashes],
      });
    }
    rows = [...byKey.values()].sort((a, b) => b.total - a.total).slice(0, limit);
  }
  return rows.map((g) => {
    const [where, args]: [string, (string | number)[]] = g.hashes.length
      ? [
          `hash IN (${g.hashes.map(() => "?").join(", ")}) AND kind = ? AND start >= ?`,
          [...g.hashes, g.kind, since],
        ]
      : ["hash IS NULL AND kind = ? AND name = ? AND start >= ?", [g.kind, g.name, since]];
    return {
      ...g.of,
      kind: g.kind,
      name: g.name,
      n: g.n,
      errors: g.errors,
      p50: percentileOf(db, where, args, g.n, 0.5),
      p95: percentileOf(db, where, args, g.n, 0.95),
      max: g.max,
      total: g.total,
      versions: Math.max(1, g.hashes.length),
    };
  });
}

uses

used by