1288 lines · 48.1 KB · TypeScript
RawDownload
import { RefreshIcon } from "~/components/ui/refresh-icon";import {  ActivityIcon,  AlertTriangleIcon,  BatteryIcon,  ChevronDownIcon,  ChevronRightIcon,  CpuIcon,  DatabaseIcon,  GaugeIcon,  HardDriveIcon,  MemoryStickIcon,} from "lucide-react";import type {  BackgroundBooleanState,  ResourceAttributionEntry,  ResourceTelemetryAggregate,  ResourceTelemetryHistoryBucket,  ResourceTelemetryIoSemantics,  ResourceTelemetryProcess,  ResourceTelemetryProcessCategory,  ResourceTelemetryProcessSummary,  ResourceTelemetrySourceHealth,  ResourceTelemetrySourceStatus,  ServerProcessSignal,} from "@t3tools/contracts";import * as DateTime from "effect/DateTime";import * as Option from "effect/Option";import { useCallback, useMemo, useRef, useState, type ReactNode } from "react";import {  isAtomCommandInterrupted,  squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime"; import {  useResourceTelemetry,  useResourceTelemetryHistory,} from "../../lib/resourceTelemetryState";import { cn } from "../../lib/utils";import { ensureLocalApi } from "../../localApi";import { usePrimaryEnvironment } from "../../state/environments";import { serverEnvironment } from "../../state/server";import { useAtomCommand } from "../../state/use-atom-command";import { formatRelativeTime } from "../../timestampFormat";import { Button } from "../ui/button";import { ScrollArea } from "../ui/scroll-area";import { Toggle, ToggleGroup } from "../ui/toggle-group";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";import { toastManager } from "../ui/toast";import {  resourceHistoryBarHeight,  resourceHistoryCpuScaleMax,  shouldShowResourceMonitorRetry,  visibleResourceTelemetryProcesses,} from "./ResourceTelemetryDiagnostics.logic";import { SettingsSection, useRelativeTimeTick } from "./settingsLayout"; const HISTORY_WINDOWS = [  { label: "5m", windowMs: 5 * 60_000, bucketMs: 15_000 },  { label: "15m", windowMs: 15 * 60_000, bucketMs: 30_000 },  { label: "30m", windowMs: 30 * 60_000, bucketMs: 60_000 },  { label: "1h", windowMs: 60 * 60_000, bucketMs: 2 * 60_000 },] as const; function formatBytes(value: number): string {  if (value < 1_024) return `${Math.round(value)} B`;  const units = ["KB", "MB", "GB", "TB"] as const;  let next = value;  let unitIndex = -1;  do {    next /= 1_024;    unitIndex += 1;  } while (next >= 1_024 && unitIndex < units.length - 1);  return `${next.toFixed(next >= 100 ? 0 : next >= 10 ? 1 : 2)} ${units[unitIndex]}`;} function formatRate(value: number): string {  return `${formatBytes(value)}/s`;} function formatCpuTime(valueMs: number): string {  const seconds = valueMs / 1_000;  if (seconds < 60) return `${seconds.toFixed(seconds >= 10 ? 1 : 2)}s`;  const minutes = seconds / 60;  if (minutes < 60) return `${minutes.toFixed(minutes >= 10 ? 1 : 2)}m`;  return `${(minutes / 60).toFixed(2)}h`;} function formatDurationMicros(value: number): string {  if (value < 1_000) return `${Math.round(value)} µs`;  if (value < 1_000_000) return `${(value / 1_000).toFixed(2)} ms`;  return `${(value / 1_000_000).toFixed(2)} s`;} function formatSampleInterval(valueMs: number): string {  if (valueMs < 1_000) return `${Math.max(0, Math.round(valueMs))} ms`;  const seconds = valueMs / 1_000;  return `${seconds.toLocaleString(undefined, { maximumFractionDigits: 1 })} ${    seconds === 1 ? "second" : "seconds"  }`;} function processIdentityKey(process: ResourceTelemetryProcess): string {  return `${process.identity.pid}:${process.identity.startTimeMs}`;} function processSummaryIdentityKey(process: ResourceTelemetryProcessSummary): string {  return `${process.identity.pid}:${process.identity.startTimeMs}`;} function formatProcessName(process: Pick<ResourceTelemetryProcess, "command" | "name">): string {  if (process.name.trim()) return process.name;  const firstToken = process.command.trim().split(/\s+/)[0] ?? process.command;  const normalized = firstToken.replace(/^['"]|['"]$/g, "");  return normalized.split(/[\\/]/).findLast((segment) => segment.length > 0) ?? normalized;} function categoryLabel(category: ResourceTelemetryProcessCategory): string {  switch (category) {    case "server":      return "Server";    case "server-child":      return "Backend child";    case "provider-root":      return "Provider";    case "terminal-root":      return "Terminal";    case "electron-main":      return "Electron main";    case "electron-renderer":      return "Renderer";    case "electron-gpu":      return "GPU";    case "electron-utility":      return "Electron utility";    case "resource-monitor":      return "Monitor";    case "unknown-t3":      return "T3 process";  }} function categoryDotClass(category: ResourceTelemetryProcessCategory): string {  if (category === "resource-monitor") return "bg-amber-500";  if (category.startsWith("electron-")) return "bg-sky-500";  if (category === "server") return "bg-violet-500";  return "bg-emerald-500";} function ioSemanticsLabel(semantics: ResourceTelemetryIoSemantics): string {  switch (semantics) {    case "storage":      return "Storage bytes";    case "logical":      return "Logical bytes";    case "all-io":      return "All I/O bytes";    case "unavailable":      return "Unavailable";  }} function booleanStateLabel(  value: BackgroundBooleanState,  labels: { readonly true: string; readonly false: string },): string {  if (value === "true") return labels.true;  if (value === "false") return labels.false;  return "Unknown";} function sourceStatusTone(status: ResourceTelemetrySourceStatus): "default" | "warning" | "danger" {  if (status === "healthy") return "default";  if (status === "starting" || status === "degraded") return "warning";  return "danger";} function SourceStatusBadge({  label,  status,  presentation,}: {  label: string;  status: ResourceTelemetrySourceStatus;  presentation?:    | {        readonly label: string;        readonly tone: "neutral";      }    | undefined;}) {  const tone = presentation?.tone ?? sourceStatusTone(status);  return (    <span      className={cn(        "inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.08em]",        tone === "neutral" && "border-border/70 bg-muted/45 text-muted-foreground",        tone === "default" &&          "border-emerald-500/25 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",        tone === "warning" &&          "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300",        tone === "danger" && "border-destructive/30 bg-destructive/10 text-destructive",      )}    >      <span        className={cn(          "size-1.5 rounded-full",          tone === "neutral" && "bg-muted-foreground/55",          tone === "default" && "bg-emerald-500",          tone === "warning" && "bg-amber-500",          tone === "danger" && "bg-destructive",        )}      />      {label} {presentation?.label ?? status}    </span>  );} function LastSampleLabel({ sampledAt }: { sampledAt: DateTime.Utc | null }) {  useRelativeTimeTick();  if (!sampledAt) {    return <span className="text-[11px] text-muted-foreground/55">Waiting for sample</span>;  }  const relative = formatRelativeTime(DateTime.formatIso(sampledAt));  if (!relative) {    return <span className="text-[11px] text-muted-foreground/55">Waiting for sample</span>;  }  return (    <span className="text-[11px] text-muted-foreground/60">      Updated <span className="font-mono tabular-nums">{relative.value}</span>      {relative.suffix ? ` ${relative.suffix}` : ""}    </span>  );} function IconStat({  icon,  label,  value,  detail,  tone = "default",}: {  icon: ReactNode;  label: string;  value: string;  detail?: string | undefined;  tone?: "default" | "warning" | "danger";}) {  return (    <div className="group min-w-0 px-4 py-4 sm:px-5">      <div className="flex items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.11em] text-muted-foreground/70">        <span className="text-muted-foreground/55 transition-colors group-hover:text-foreground/65">          {icon}        </span>        <span className="truncate">{label}</span>      </div>      <div        className={cn(          "mt-2.5 truncate font-mono text-2xl font-semibold tracking-[-0.05em] tabular-nums text-foreground",          tone === "warning" && "text-amber-600 dark:text-amber-300",          tone === "danger" && "text-destructive",        )}      >        {value}      </div>      {detail ? (        <div className="mt-1.5 truncate text-[10px] text-muted-foreground/60">{detail}</div>      ) : null}    </div>  );} function AggregateCard({  label,  accentClass,  aggregate,}: {  label: string;  accentClass: string;  aggregate: ResourceTelemetryAggregate;}) {  return (    <div className="relative overflow-hidden border-t border-border/60 px-4 py-4 first:border-t-0 md:border-t-0 md:border-l md:first:border-l-0 sm:px-5">      <span className={cn("absolute inset-x-5 top-0 h-0.5 rounded-full opacity-75", accentClass)} />      <div className="flex items-center justify-between gap-3">        <div className="text-[10px] font-semibold uppercase tracking-[0.11em] text-muted-foreground/75">          {label}        </div>        <div className="rounded-md bg-muted/55 px-1.5 py-0.5 font-mono text-[9px] tabular-nums text-muted-foreground/70">          {aggregate.processCount} {aggregate.processCount === 1 ? "process" : "processes"}        </div>      </div>      <div className="mt-3.5 grid grid-cols-2 gap-x-4 gap-y-2.5">        <MetricPair label="CPU" value={`${aggregate.currentCpuPercent.toFixed(1)}%`} />        <MetricPair label="Memory" value={formatBytes(aggregate.currentRssBytes)} />        <MetricPair label="Read" value={formatRate(aggregate.ioReadBytesPerSecond)} />        <MetricPair label="Write" value={formatRate(aggregate.ioWriteBytesPerSecond)} />      </div>    </div>  );} function MetricPair({ label, value }: { label: string; value: string }) {  return (    <div className="min-w-0">      <div className="text-[9px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/45">        {label}      </div>      <div className="truncate font-mono text-xs font-medium tabular-nums text-foreground/90">        {value}      </div>    </div>  );} function HealthSource({ label, health }: { label: string; health: ResourceTelemetrySourceHealth }) {  const expectedInBrowser =    health.status === "unavailable" &&    Option.exists(health.lastError, (error) => error.includes("'web' mode"));  return (    <div className="flex items-start justify-between gap-4 border-t border-border/50 py-3 first:border-t-0">      <div className="min-w-0">        <div className="text-[13px] font-medium text-foreground">{label}</div>        <div className="mt-1 text-[11px] leading-relaxed text-muted-foreground/65">          {expectedInBrowser            ? "Available when this page runs inside the desktop app."            : Option.match(health.lastError, {                onNone: () => "No reported errors",                onSome: (error) => error,              })}        </div>      </div>      <SourceStatusBadge        label=""        status={health.status}        presentation={          expectedInBrowser            ? {                label: "Desktop only",                tone: "neutral",              }            : undefined        }      />    </div>  );} function DetailRow({  label,  value,  valueClassName,}: {  label: string;  value: ReactNode;  valueClassName?: string | undefined;}) {  return (    <div className="flex items-center justify-between gap-4 border-t border-border/50 py-2.5 first:border-t-0">      <span className="text-[11px] text-muted-foreground/75">{label}</span>      <span        className={cn(          "min-w-0 truncate text-right font-mono text-[11px] tabular-nums text-foreground/85",          valueClassName,        )}      >        {value}      </span>    </div>  );} function HistoryWindowSelector({  selectedWindowMs,  onSelect,}: {  selectedWindowMs: number;  onSelect: (windowMs: number) => void;}) {  return (    <ToggleGroup      aria-label="Resource history period"      variant="segmented"      value={[String(selectedWindowMs)]}      onValueChange={(next) => {        const selected = HISTORY_WINDOWS.find((option) => String(option.windowMs) === next[0]);        if (selected) onSelect(selected.windowMs);      }}    >      {HISTORY_WINDOWS.map((option) => (        <Toggle key={option.windowMs} value={String(option.windowMs)}>          {option.label}        </Toggle>      ))}    </ToggleGroup>  );} function ResourceHistoryChart({  buckets,}: {  buckets: ReadonlyArray<ResourceTelemetryHistoryBucket>;}) {  const maxCpu = resourceHistoryCpuScaleMax(buckets);  const maxIo = Math.max(1, ...buckets.map((bucket) => bucket.ioReadBytes + bucket.ioWriteBytes));   return (    <div className="border-t border-border/60 px-4 py-4 sm:px-5">      <div className="mb-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-[10px] text-muted-foreground/65">        <span className="inline-flex items-center gap-1.5">          <span className="h-1.5 w-3 rounded-full bg-foreground/70" /> CPU average        </span>        <span className="inline-flex items-center gap-1.5">          <span className="h-1.5 w-3 rounded-full bg-sky-500/70" /> I/O reads        </span>        <span className="inline-flex items-center gap-1.5">          <span className="h-1.5 w-3 rounded-full bg-amber-500/80" /> I/O writes        </span>      </div>      <div className="flex h-32 items-end gap-1 overflow-hidden rounded-lg border border-border/40 bg-muted/8 px-2 pt-3 pb-2">        {buckets.map((bucket) => {          const cpuHeight = resourceHistoryBarHeight({            value: bucket.avgCpuPercent,            max: maxCpu,            minimumVisiblePercent: 2,          });          const readHeight = resourceHistoryBarHeight({            value: bucket.ioReadBytes,            max: maxIo,            minimumVisiblePercent: 1,          });          const writeHeight = resourceHistoryBarHeight({            value: bucket.ioWriteBytes,            max: maxIo,            minimumVisiblePercent: 1,          });          return (            <Tooltip key={DateTime.formatIso(bucket.startedAt)}>              <TooltipTrigger                render={                  <div className="grid h-full min-w-1 flex-1 grid-cols-3 items-end gap-px">                    <span                      className="block rounded-t-sm bg-foreground/65"                      style={{ height: `${cpuHeight}%` }}                    />                    <span                      className="block rounded-t-sm bg-sky-500/70"                      style={{ height: `${readHeight}%` }}                    />                    <span                      className="block rounded-t-sm bg-amber-500/80"                      style={{ height: `${writeHeight}%` }}                    />                  </div>                }              />              <TooltipPopup side="top" className="space-y-0.5 text-left">                <div>CPU avg {bucket.avgCpuPercent.toFixed(1)}%</div>                <div>CPU peak {bucket.maxCpuPercent.toFixed(1)}%</div>                <div>Read {formatBytes(bucket.ioReadBytes)}</div>                <div>Write {formatBytes(bucket.ioWriteBytes)}</div>              </TooltipPopup>            </Tooltip>          );        })}      </div>    </div>  );} function ProcessTreeName({  process,  collapsed,  onToggle,}: {  process: ResourceTelemetryProcess;  collapsed: boolean;  onToggle: (process: ResourceTelemetryProcess) => void;}) {  const name = formatProcessName(process);  const hasChildren = process.childPids.length > 0;  const ChevronIcon = collapsed ? ChevronRightIcon : ChevronDownIcon;  return (    <div      className="grid min-w-0 grid-cols-[1.25rem_0.375rem_minmax(0,1fr)] items-center gap-2"      style={{ paddingLeft: `${Math.min(process.depth, 7) * 10}px` }}    >      {hasChildren ? (        <Button          size="icon-micro"          variant="ghost-muted"          onClick={() => onToggle(process)}          aria-label={collapsed ? `Expand ${name}` : `Collapse ${name}`}        >          <ChevronIcon className="size-3.5" />        </Button>      ) : (        <span className="size-5" aria-hidden />      )}      <span className={cn("size-1.5 rounded-full", categoryDotClass(process.category))} />

Lines 1–500 of 1288.