88 lines · 3.9 KB · TypeScript
RawDownload
import type { UsageStats } from "@local-studio/contracts/usage";import { observeControllerFunction } from "../../core/function-observability";import type { RouteRegistrar } from "../../http/route-registrar";import type { AppContext } from "../../app-context";import { getUsageFromPiSessions } from "./usage/pi-sessions";import { emptyResponse } from "./usage/usage-utilities"; // Enrich the model filter set with the currently-running model. This used to// scan the full process table via `ps` (findInferenceProcess) on every /usage// request — an event-loop-blocking spawn just to learn the active model name.// The metrics collector already refreshes `model_id`/`model_path`/// `served_model_name` into the event manager's in-memory latest-metrics snapshot// every few seconds, so read that instead (zero syscalls).const collectKnownModels = (context: AppContext): Set<string> => {  const knownModels = new Set<string>();  for (const recipe of context.stores.recipeStore.list()) {    if (recipe.served_model_name) knownModels.add(recipe.served_model_name);    knownModels.add(recipe.id);    if (recipe.name) knownModels.add(recipe.name);  }  const latest = context.eventManager.getLatestMetrics();  const servedModelName = latest["served_model_name"];  if (typeof servedModelName === "string" && servedModelName) knownModels.add(servedModelName);  const modelId = latest["model_id"];  if (typeof modelId === "string" && modelId) knownModels.add(modelId);  const modelPath = latest["model_path"];  if (typeof modelPath === "string" && modelPath) {    knownModels.add(modelPath);    knownModels.add(modelPath.split("/").pop() ?? modelPath);  }  return knownModels;}; // Analytics endpoints are not real-time; a short TTL collapses bursty// dashboard polling (and repeated aggregation passes) into one computation.const USAGE_CACHE_TTL_MS = 15_000; const withControllerUsage = (  context: AppContext,  body: UsageStats,  includeController: boolean,): UsageStats =>  includeController    ? { ...body, controller: context.stores.controllerRequestStore.aggregate() }    : body; export const registerUsageRoutes: RouteRegistrar = (app, context) => {  let usageCache: { at: number; body: UsageStats } | null = null;   app.get("/usage", async (ctx) => {    const includeController = ctx.req.query("include_controller") === "true";    try {      if (usageCache && Date.now() - usageCache.at < USAGE_CACHE_TTL_MS) {        return ctx.json(withControllerUsage(context, usageCache.body, includeController));      }      const knownModels = await observeControllerFunction(context, "usage.collectKnownModels", () =>        collectKnownModels(context),      );      const usage = await observeControllerFunction(        context,        "usage.aggregateInferenceRequests",        () => context.stores.inferenceRequestStore.aggregate(knownModels),      );      const body: UsageStats = usage ?? emptyResponse();      usageCache = { at: Date.now(), body };      return ctx.json(withControllerUsage(context, body, includeController));    } catch (error) {      context.logger.error(`[Usage] Error fetching usage stats: ${(error as Error).message}`);      return ctx.json(withControllerUsage(context, emptyResponse(), includeController));    }  });   app.get("/usage/pi-sessions", async (ctx) => {    try {      // pi-sessions tab shows ALL pi coding-agent activity, regardless of      // whether the model is one of our recipes (so users can see their      // external model usage too).      const usage = await observeControllerFunction(context, "usage.aggregatePiSessions", () =>        getUsageFromPiSessions(),      );      const body: UsageStats = usage ?? emptyResponse();      return ctx.json(body);    } catch (error) {      context.logger.error(`[Usage] Error fetching pi-sessions usage: ${(error as Error).message}`);      return ctx.json(emptyResponse());    }  });};