69 lines · 2.4 KB
import { cloudRunnerSize } from "../ci/runner-sizes";
import type { RunnerBinding } from "../ci/runner-sizes";
import { repoDb } from "../scoped-db";
import type { MeterId } from "~/lib/meters";
import { recordUsage } from "./record";

/** Docker-capable twins bill under their base size's meter: same instance type, so no extra charge. */
const METER_FOR_BINDING: Record<RunnerBinding, MeterId> = {
  RUNNER: "compute.runner",
  RUNNER_LARGE: "compute.runner_large",
  RUNNER_XLARGE: "compute.runner_xlarge",
  RUNNER_DIND: "compute.runner",
  RUNNER_LARGE_DIND: "compute.runner_large",
  RUNNER_XLARGE_DIND: "compute.runner_xlarge",
};

export function runnerMeter(runsOn: string[]): MeterId {
  const size = cloudRunnerSize(runsOn);
  return size ? METER_FOR_BINDING[size.binding] : "compute.runner_self_hosted";
}

/** Exact runner seconds, never rounded up; a job that never started costs nothing. */
export function jobSeconds(startedAt: number | null, completedAt: number): number {
  if (startedAt === null || completedAt <= startedAt) return 0;
  return (completedAt - startedAt) / 1000;
}

/**
 * Bills a completed job's runner time to the account owning the repository the run executes in. Fork pull
 * request runs execute in the base repository, so the base owner pays. Keyed by job id: rerun attempts are
 * new job rows, while a duplicate completion of the same job is ignored.
 */
export async function meterCompletedJob(input: {
  jobId: string;
  runId: string;
  accountId: string;
  repoId: string;
  runsOn: string[];
  startedAt: number | null;
  completedAt: number;