65 lines · 2.2 KB
import { cloudRunnerSize } from "../ci/runner-sizes";
import type { RunnerBinding } from "../ci/runner-sizes";
import { all } from "../db";
import type { MeterId } from "~/lib/meters";
import { recordUsage } from "./record";

const METER_FOR_BINDING: Record<RunnerBinding, MeterId> = {
  RUNNER: "compute.runner",
  RUNNER_LARGE: "compute.runner_large",
  RUNNER_XLARGE: "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;
  sourceRepoId: string;
}): Promise<void> {
  const seconds = jobSeconds(input.startedAt, input.completedAt);
  const steps = await all<{ count: number }>(