Merge branch 'worktree-agent-a56293a6f836cd343'
38 files+235−16022 files not counted0/38 viewed
| 220 | 220 | `g1t cache usage` says what the repository's entries hold against its | |
| 221 | 221 | 10 GiB, and `g1t cache list --sort size-in-bytes` which take the most. | |
| 222 | 222 | ||
| 223 | + | ## Adding a machine size | |
| 224 | + | ||
| 225 | + | g1t's hosted machines are one catalogue: `INSTANCE_TYPES` in | |
| 226 | + | `crates/contracts/src/actions.rs`, mirrored row for row in | |
| 227 | + | `packages/contracts/src/instances.ts`. Each row is a `runs-on` label, the | |
| 228 | + | machine it bills as (`machine`, such as `2-core`), the runner's Durable | |
| 229 | + | Object class that runs it (`class` / `sandboxClass`), the Cloudflare | |
| 230 | + | Containers instance type behind that class (`container`), its vCPUs, | |
| 231 | + | memory and disk, and its price a minute (`micros_per_minute`). The runner | |
| 232 | + | starts a job in its label's class, billing charges its minutes at its | |
| 233 | + | rate and reports them by machine, and the docs' tables follow it. | |
| 234 | + | ||
| 235 | + | - **A new label for an existing machine** (another name for the standard | |
| 236 | + | one, say): a row with the new label and class and the machine's | |
| 237 | + | `machine`, size and rate; the class and its binding below. | |
| 238 | + | - **A new size**: a row with a new `machine` key and its rate. The rate is | |
| 239 | + | what a minute costs at Cloudflare's list prices with half the vCPUs | |
| 240 | + | busy, plus 20%, rounded up to a tenth of a cent, and never under its | |
| 241 | + | cost with every vCPU busy; `rates_cover_the_machine_at_full_cpu_and_its_usual_cost_plus_the_margin` | |
| 242 | + | checks it. | |
| 243 | + | - **Pointing a label at another instance type**: change its `container` | |
| 244 | + | and size (and `machine`, if it is a different machine now), and the | |
| 245 | + | class's `instance_type` in `services/runner/wrangler.jsonc`. A custom | |
| 246 | + | instance type works the same way, once the account may use them: add its | |
| 247 | + | size to the runner test's table of instance types. | |
| 248 | + | - **Renaming a label**: change `label`; keep `machine`, so its minutes stay | |
| 249 | + | on the same line of everyone's usage. | |
| 250 | + | ||
| 251 | + | Never reuse a `machine` key for a different machine: past usage is | |
| 252 | + | reported under it. For a new class, add it to `services/runner/wrangler.jsonc` | |
| 253 | + | (`containers`, a `durable_objects` binding and a migration tag with | |
| 254 | + | `new_sqlite_classes`), export it from `services/runner/src/index.ts` | |
| 255 | + | (`export class SandboxXCore extends AttemptSandbox {}`), and give it a | |
| 256 | + | binding in `SANDBOX_BINDINGS` (`services/runner/src/egress.ts`). The tests | |
| 257 | + | that hold all of this together are `cargo test -p g1t-contracts instance` | |
| 258 | + | (the table, its rates and its TypeScript mirror) and the runner's | |
| 259 | + | `npm test` (every class in wrangler.jsonc, its instance type, size and | |
| 260 | + | binding against the catalogue). Then update the machine tables in | |
| 261 | + | `guides/actions.md`, `guides/usage-and-billing.md` and | |
| 262 | + | `architecture/compute.mdx`. | |
| 263 | + | ||
| 223 | 264 | ## Speed | |
| 224 | 265 | ||
| 225 | 266 | Pages are a few rounds of service calls, and each round costs a trip to |
| 6400 | 6400 | "from": "2026-10-01", | |
| 6401 | 6401 | "until": "2026-10-07", | |
| 6402 | 6402 | "totals": { | |
| 6403 | − | "price_micros": 48210000, | |
| 6403 | + | "price_micros": 48212000, | |
| 6404 | 6404 | "discount_micros": 0, | |
| 6405 | 6405 | "included_micros": 20000000, | |
| 6406 | 6406 | "credits_micros": 18000000, | |
| 6407 | − | "charged_micros": 10210000, | |
| 6407 | + | "charged_micros": 10212000, | |
| 6408 | 6408 | "pending_micros": 1340000, | |
| 6409 | 6409 | "pending_charged_micros": 1340000, | |
| 6410 | 6410 | "cost_micros": 40100000 | |
| ⋯ | |||
| 6430 | 6430 | { | |
| 6431 | 6431 | "key": "sandboxes", | |
| 6432 | 6432 | "label": "Sandboxes", | |
| 6433 | − | "micros": 7210000, | |
| 6433 | + | "micros": 7212000, | |
| 6434 | 6434 | "meters": [ | |
| 6435 | 6435 | { | |
| 6436 | − | "key": "sandbox", | |
| 6437 | − | "label": "Sandbox time", | |
| 6436 | + | "key": "runner_2-core", | |
| 6437 | + | "label": "Runner minutes, 2-core (2 vCPU, 8 GiB, 16 GB)", | |
| 6438 | 6438 | "product": "sandboxes", | |
| 6439 | − | "unit": "seconds", | |
| 6440 | − | "quantity": 41300, | |
| 6441 | − | "micros": 7210000, | |
| 6439 | + | "unit": "minutes", | |
| 6440 | + | "quantity": 1803, | |
| 6441 | + | "micros": 7212000, | |
| 6442 | 6442 | "pending_micros": 0, | |
| 6443 | 6443 | "daily": [ | |
| 6444 | 6444 | 900000, | |
| ⋯ | |||
| 6447 | 6447 | 1100000, | |
| 6448 | 6448 | 0, | |
| 6449 | 6449 | 2100000, | |
| 6450 | − | 1110000 | |
| 6450 | + | 1112000 | |
| 6451 | 6451 | ], | |
| 6452 | − | "allowance": { | |
| 6453 | − | "used": 41300, | |
| 6454 | − | "of": 108000, | |
| 6455 | − | "unit": "seconds" | |
| 6456 | − | }, | |
| 6452 | + | "allowance": null, | |
| 6457 | 6453 | "by_project": [ | |
| 6458 | 6454 | { | |
| 6459 | 6455 | "project": "flagon-io/g1t", | |
| 6460 | − | "micros": 7210000, | |
| 6461 | − | "quantity": 41300 | |
| 6456 | + | "micros": 7212000, | |
| 6457 | + | "quantity": 1803 | |
| 6462 | 6458 | } | |
| 6463 | 6459 | ] | |
| 6464 | 6460 | } | |
| 319 | 319 | | Limit | Value | | |
| 320 | 320 | | --- | --- | | |
| 321 | 321 | | Largest machine | 4 vCPUs, 12 GiB of memory, 20 GB of disk (`g1t-4core`). No GPUs. | | |
| 322 | − | | Disk | 20 GB on every machine, the most Cloudflare Containers give one. | | |
| 322 | + | | Standard machine | 2 vCPUs, 8 GiB of memory, 16 GB of disk (`ubuntu-latest`, every other hosted label, and `g1t-2core`). | | |
| 323 | 323 | | One job on g1t's machines | 60 minutes. On a self-hosted runner, 24 hours. | | |
| 324 | 324 | | One cache entry | 2 GiB, compressed. A larger one is not saved. | | |
| 325 | 325 | | A repository's caches | 10 GiB together. Past it, the entries restored longest ago are removed. | |
| 69 | 69 | ||
| 70 | 70 | ## Sandboxes on g1t's runners | |
| 71 | 71 | ||
| 72 | − | `runner` defines three Container classes, picked by `runs-on`: | |
| 72 | + | `runner` defines three Container classes, picked by `runs-on`, each a | |
| 73 | + | named Cloudflare instance type: | |
| 73 | 74 | ||
| 74 | 75 | | `runs-on` | Class | Size | | |
| 75 | 76 | | --- | --- | --- | | |
| 76 | − | | Any other hosted label, and every other kind of work | `AttemptSandbox` | 1 vCPU, 4 GiB memory, 20 GB disk (a custom instance type) | | |
| 77 | − | | `g1t-2core` | `Sandbox2Core` | 2 vCPU, 8 GiB memory, 20 GB disk (a custom instance type) | | |
| 78 | − | | `g1t-4core` | `Sandbox4Core` | 4 vCPU, 12 GiB memory, 20 GB disk (Cloudflare's `standard-4`) | | |
| 77 | + | | Any other hosted label, and every other kind of work | `AttemptSandbox` | `standard-3`: 2 vCPU, 8 GiB memory, 16 GB disk | | |
| 78 | + | | `g1t-2core` | `Sandbox2Core` | `standard-3`: 2 vCPU, 8 GiB memory, 16 GB disk | | |
| 79 | + | | `g1t-4core` | `Sandbox4Core` | `standard-4`: 4 vCPU, 12 GiB memory, 20 GB disk | | |
| 79 | 80 | ||
| 80 | − | Each has the 20 GB of disk Containers allow at most. The named instance | |
| 81 | − | types with 1 and 2 vCPU come with 8 and 16 GB, so those two are custom | |
| 82 | − | instance types (`instance_type` with `vcpu`, `memory_mib` and `disk_mb` in | |
| 83 | − | `services/runner/wrangler.jsonc`); 1 vCPU is the smallest a custom type may | |
| 84 | − | have. | |
| 81 | + | `g1t-2core` is kept as a name of the standard machine, so workflows that | |
| 82 | + | ask for it keep working; it keeps its class and its own `max_instances` | |
| 83 | + | in `services/runner/wrangler.jsonc`. The contracts' instance table | |
| 84 | + | (`INSTANCE_TYPES`) and billing's price keeper say the same sizes, and a | |
| 85 | + | test holds them to `wrangler.jsonc`. | |
| 85 | 86 | ||
| 86 | 87 | Every sandbox starts from one image: `services/runner/base/Dockerfile`, | |
| 87 | 88 | which has Node, Python, Go, Rust, Java, .NET, Ruby and Docker, with the |
| 537 | 537 | ||
| 538 | 538 | ### Machine sizes | |
| 539 | 539 | ||
| 540 | − | A job runs on the standard machine unless its `runs-on` names a larger | |
| 541 | − | one: | |
| 540 | + | A job runs on the standard machine unless its `runs-on` is `g1t-4core`: | |
| 542 | 541 | ||
| 543 | − | | `runs-on` | vCPUs | Memory | Disk | | |
| 544 | − | | --- | --- | --- | --- | | |
| 545 | − | | `ubuntu-latest`, or any other Linux label | 1 | 4 GiB | 20 GB | | |
| 546 | − | | `g1t-2core` | 2 | 8 GiB | 20 GB | | |
| 547 | − | | `g1t-4core` | 4 | 12 GiB | 20 GB | | |
| 542 | + | | `runs-on` | vCPUs | Memory | Disk | A minute | | |
| 543 | + | | --- | --- | --- | --- | --- | | |
| 544 | + | | `ubuntu-latest`, or any other Linux label | 2 | 8 GiB | 16 GB | $0.004 | | |
| 545 | + | | `g1t-2core` (a name of the standard machine) | 2 | 8 GiB | 16 GB | $0.004 | | |
| 546 | + | | `g1t-4core` | 4 | 12 GiB | 20 GB | $0.007 | | |
| 548 | 547 | ||
| 549 | 548 | ```yaml | |
| 550 | 549 | jobs: | |
| ⋯ | |||
| 552 | 551 | runs-on: g1t-4core | |
| 553 | 552 | ``` | |
| 554 | 553 | ||
| 555 | − | The label can come from the matrix or the run's inputs | |
| 556 | − | (`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). A | |
| 557 | − | larger machine costs what it costs g1t, plus the same margin as all | |
| 558 | − | sandbox time: see [usage and billing](/guides/usage-and-billing/#workflow-jobs-on-larger-machines). | |
| 559 | − | Builds that compile, such as Rust or a large TypeScript project, finish | |
| 560 | − | several times faster on one. | |
| 554 | + | `g1t-4core` is for heavier jobs: builds that compile, such as Rust or a | |
| 555 | + | large TypeScript project, image builds and databases. `g1t-2core` is kept | |
| 556 | + | as a name of the standard machine, so workflows that ask for it keep | |
| 557 | + | working. The label can come from the matrix or the run's inputs | |
| 558 | + | (`runs-on: ${{ matrix.big && 'g1t-4core' || 'ubuntu-latest' }}`). | |
| 559 | + | ||
| 560 | + | Each job is charged its time rounded up to the whole minute (61 seconds | |
| 561 | + | is 2 minutes, and a job that ran is at least 1), at its machine's rate: | |
| 562 | + | see [runner minutes](/guides/usage-and-billing/#runner-minutes). Jobs on | |
| 563 | + | [self-hosted runners](/guides/self-hosted-runners/) are free. | |
| 561 | 564 | ||
| 562 | 565 | A job on g1t's machines runs for at most 60 minutes, whatever its | |
| 563 | 566 | `timeout-minutes`, and the workflow's page warns about a job that asks | |
| ⋯ | |||
| 751 | 754 | ||
| 752 | 755 | - **Machine.** Containers share the job's machine: its vCPUs, memory and | |
| 753 | 756 | disk ([machine sizes](#machine-sizes)). Image builds and databases want | |
| 754 | − | `g1t-2core` or `g1t-4core`. `--cpus` and `--memory` limit a container | |
| 755 | − | within that. | |
| 757 | + | `g1t-4core`. `--cpus` and `--memory` limit a container within that. | |
| 756 | 758 | - **Disk.** Images take room on the job's disk. On a machine whose disk | |
| 757 | 759 | cannot hold layered images, the Engine stores plain copies, which take | |
| 758 | 760 | more room; the log says when it does. | |
| ⋯ | |||
| 2002 | 2004 | runs them too, after a card check, until the month's pool is spent. | |
| 2003 | 2005 | ||
| 2004 | 2006 | Before each job starts, g1t reserves what it may cost (its time limit at | |
| 2005 | − | the sandbox price) with billing, and settles what it really cost when it | |
| 2006 | − | ends; each job's sandbox is charged as | |
| 2007 | − | [sandbox time](/guides/usage-and-billing/#sandbox-time), from the first | |
| 2008 | − | second. A job billing refuses does not start: it is recorded as failed | |
| 2007 | + | its machine's price) with billing, and settles what it really cost when | |
| 2008 | + | it ends; each job is charged in | |
| 2009 | + | [runner minutes](/guides/usage-and-billing/#runner-minutes), its time | |
| 2010 | + | rounded up to the whole minute, at the rate of the machine it ran on. A job billing refuses does not start: it is recorded as failed | |
| 2009 | 2011 | with "Not started:" and the reason, such as "Workflows run in g1t's | |
| 2010 | 2012 | sandboxes, which cost real money, so they need the g1t plan ($20 a month) | |
| 2011 | 2013 | or a card check", and what to do about it. The | |
| 305 | 305 | minutes, so a deploy never cuts one short; an agent run whose guardrails | |
| 306 | 306 | allow more than two hours can be. | |
| 307 | 307 | ||
| 308 | − | Jobs choose larger machines with `runs-on: g1t-2core` or `g1t-4core`. | |
| 309 | − | Every sandbox has 20 GB of disk: the standard one and `g1t-2core` are | |
| 310 | − | custom instance types (1 vCPU and 4 GiB, and 2 vCPU and 8 GiB), and | |
| 311 | − | `g1t-4core` is `standard-4`. Your account's Containers limits must allow | |
| 312 | − | all three. | |
| 308 | + | The standard sandbox is the instance type `standard-3` (2 vCPU, 8 GiB, | |
| 309 | + | 16 GB), and so is `runs-on: g1t-2core`, in a class of its own; jobs that | |
| 310 | + | ask for `runs-on: g1t-4core` get `standard-4` (4 vCPU, 12 GiB, 20 GB). At | |
| 311 | + | the `max_instances` in `services/runner/wrangler.jsonc` (20, 10 and 10), | |
| 312 | + | 40 sandboxes at once hold 100 vCPU, 360 GiB of memory and 680 GB of disk, | |
| 313 | + | which your account's Containers limits for live instances must allow. | |
| 313 | 314 | ||
| 314 | 315 | Set the runner Worker's `DOCKER` variable to `off` to give workflow jobs no | |
| 315 | 316 | Docker Engine. |
| 158 | 158 | [statement](/guides/usage-and-billing/), with each line at its price: what | |
| 159 | 159 | was charged plus what your plan's included usage, a trial, a pool or a | |
| 160 | 160 | discount paid of it. The lines are the statement's kinds of charge, such | |
| 161 | − | as **Sandbox time**, **Self-hosted runner time** and | |
| 161 | + | as **Runner minutes, 2-core**, **Self-hosted runner time** and | |
| 162 | 162 | **Deployments**, most first. Payments, credits, refunds, tax and card fees | |
| 163 | 163 | are money in, not spend, and are left out. Under the total are the days | |
| 164 | 164 | counted and the month so far, at price. |
| 273 | 273 | 2. **With a short probe.** If they share files, a sandbox merges the two | |
| 274 | 274 | commits and pushes nothing, and reports the files that | |
| 275 | 275 | conflict. Meanwhile the box says **Checking whether this merges cleanly**, | |
| 276 | − | and the merge button waits. Probes are metered as sandbox time; each | |
| 276 | + | and the merge button waits. Probes are charged as runner minutes; each | |
| 277 | 277 | pair of commits is probed once, and a repository runs at most | |
| 278 | 278 | three at a time, the rest following in turn. | |
| 279 | 279 |
| 431 | 431 | ||
| 432 | 432 | | Part | What it shows | | |
| 433 | 433 | | --- | --- | | |
| 434 | − | | **g1t cloud** | How many workflow jobs are running in g1t's sandboxes now, this month's sandbox time and what it came to at price, and the price per minute from g1t's [price book](/guides/usage-and-billing/). | | |
| 434 | + | | **g1t cloud** | How many workflow jobs are running in g1t's sandboxes now, this month's runner minutes and what they came to at price, and each machine's price a minute ([runner minutes](/guides/usage-and-billing/#runner-minutes)). | | |
| 435 | 435 | | **Your runners** | How many are online, busy, idle and offline, this month's self-hosted time (always $0), how many jobs are waiting for one of them, and whether they take [g1t's work](#g1ts-work-on-your-runners). | | |
| 436 | 436 | | **On g1t cloud now** | Each running workflow job, linked to its run, with its kind, project and when it started. Work handed to one of your runners is listed under that runner instead. | | |
| 437 | 437 | | **Machine time** | This month's time on g1t cloud against your runners', from your [usage](/guides/usage-and-billing/). | |
| 98 | 98 | on $20. | |
| 99 | 99 | ||
| 100 | 100 | - **$10 of usage each month**, used first: everything g1t runs, at cost | |
| 101 | − | plus 20%. | |
| 101 | + | plus 20%. That is 2,500 runner minutes on the standard machine, or | |
| 102 | + | about 1,428 on `g1t-4core`. | |
| 102 | 103 | - **Everyone in the workspace** at one price, never per person. | |
| 103 | 104 | - **Unlimited** projects, previews and repositories. | |
| 104 | 105 | - Workflows, the merge queue, | |
| ⋯ | |||
| 118 | 119 | work. | |
| 119 | 120 | ||
| 120 | 121 | **Included usage** pays for the month's usage first, at the same prices as | |
| 121 | − | everything else: sandbox time, builds, app traffic, custom domains, | |
| 122 | + | everything else: runner minutes, builds, app traffic, custom domains, | |
| 122 | 123 | storage, git operations, search embeddings and security scans. Past it, | |
| 123 | 124 | usage is charged at the same prices. It starts again on the 1st of each | |
| 124 | 125 | month (UTC). **Unused included usage does not roll over.** Billing shows | |
| ⋯ | |||
| 128 | 129 | ||
| 129 | 130 | Pricing has two rules: | |
| 130 | 131 | ||
| 131 | − | 1. **Everything g1t runs at cost plus 20%.** Sandboxes, builds, | |
| 132 | − | hosting, storage, git operations, search and security scans: what g1t | |
| 133 | − | pays for them, plus 20%. | |
| 132 | + | 1. **Everything g1t runs at cost plus 20%.** Builds, hosting, storage, | |
| 133 | + | git operations, search and security scans: what g1t pays for them, | |
| 134 | + | plus 20%. [Runner minutes](#runner-minutes) are a fixed rate for each | |
| 135 | + | machine, set the same way: what a minute of it costs g1t with half its | |
| 136 | + | vCPUs busy, plus 20%, rounded up to a tenth of a cent, and never under | |
| 137 | + | its cost with every vCPU busy. | |
| 134 | 138 | 2. **Your own runners are free.** Jobs on your | |
| 135 | 139 | [own runners](/guides/self-hosted-runners/) cost nothing here. | |
| 136 | 140 | ||
| ⋯ | |||
| 139 | 143 | ||
| 140 | 144 | | What | Unit | Costs g1t | You pay | | |
| 141 | 145 | | --- | --- | --- | --- | | |
| 142 | − | | Sandbox time (workflows, checks, the merge queue) | Second | About $0.001 a minute | About $0.0012 a minute | | |
| 143 | − | | [Larger machines](#workflow-jobs-on-larger-machines) for workflow jobs (`g1t-2core`, `g1t-4core`) | Second | About 1.9 and 3.4 times a sandbox second | Cost + 20% | | |
| 144 | − | | Deploy builds | Second | About $0.001 a minute | About $0.0012 a minute | | |
| 146 | + | | [Runner minutes](#runner-minutes) on the standard machine (2 vCPU, 8 GiB, 16 GB): workflow jobs, checks, the merge queue | Minute, each job rounded up | From $0.0014 a minute idle to $0.0038 with both vCPUs busy | $0.004 a minute | | |
| 147 | + | | [Runner minutes](#runner-minutes) on `g1t-4core` (4 vCPU, 12 GiB, 20 GB) | Minute, each job rounded up | From $0.0020 a minute idle to $0.0068 with all four vCPUs busy | $0.007 a minute | | |
| 148 | + | | Deploy builds | Second | About $0.0015 a minute | About $0.0018 a minute | | |
| 145 | 149 | | App requests | Million | $0.30 | $0.36 | | |
| 146 | 150 | | App CPU time | Million CPU milliseconds | $0.02 | $0.024 | | |
| 147 | 151 | | Custom domains | Domain-month | $0.10 | $0.12 | | |
| ⋯ | |||
| 154 | 158 | | Security scans | Million CPU ms / million rows | $0.02 / $1.00 | $0.024 / $1.20 | | |
| 155 | 159 | | Projects, previews, apps and repositories | | Next to nothing | Not charged | | |
| 156 | 160 | ||
| 157 | − | Sandbox and build seconds follow what Cloudflare bills g1t, so the | |
| 158 | − | figures above move a little; the pricing page always has today's. | |
| 161 | + | Build seconds follow what Cloudflare bills g1t, so that figure moves a | |
| 162 | + | little; the pricing page always has today's. Runner minutes are fixed | |
| 163 | + | rates. | |
| 159 | 164 | Actions cache storage is charged at R2's published price, and is not on | |
| 160 | 165 | the pricing page yet. | |
| 161 | 166 | ||
| ⋯ | |||
| 228 | 233 | ## The trial | |
| 229 | 234 | ||
| 230 | 235 | Once its card is checked, a workspace gets **$5 of trial credit**, once. It | |
| 231 | − | pays for usage at cost plus 20%: sandbox time, workflows, | |
| 232 | − | the merge queue and semantic search. It never pays for deployments. | |
| 236 | + | pays for usage at cost plus 20%: runner minutes for checks, workflows | |
| 237 | + | and the merge queue (1,250 on the standard machine), and semantic search. It never pays for deployments. | |
| 233 | 238 | ||
| 234 | 239 | - Each card gets one trial, whichever workspace it is checked in. Prepaid | |
| 235 | 240 | cards start none. | |
| ⋯ | |||
| 246 | 251 | ## The open-source pool | |
| 247 | 252 | ||
| 248 | 253 | g1t sets aside **$25 a month** for public repositories, at most **$2 a | |
| 249 | − | month** for any one repository. It pays for **checks, workflows and the | |
| 250 | − | merge queue on public repositories**, after a card check. It does not pay | |
| 254 | + | month** for any one repository: 6,250 and 500 runner minutes on the | |
| 255 | + | standard machine. It pays for **checks, workflows and the merge queue on | |
| 256 | + | public repositories**, after a card check. It does not pay | |
| 251 | 257 | for deployments, storage or embeddings. | |
| 252 | 258 | ||
| 253 | 259 | When the pool or the repository's share is spent, those runs wait for the | |
| ⋯ | |||
| 259 | 265 | ||
| 260 | 266 | | | Charged | | |
| 261 | 267 | | --- | --- | | |
| 262 | − | | The [merge queue](/guides/merge-queue/) | [Sandbox time](#sandbox-time) | | |
| 263 | − | | [Workflow](/guides/actions/) jobs | [Sandbox time](#sandbox-time), more on a [larger machine](#workflow-jobs-on-larger-machines) | | |
| 268 | + | | Checks and the [merge queue](/guides/merge-queue/) | [Runner minutes](#runner-minutes) | | |
| 269 | + | | [Workflow](/guides/actions/) jobs | [Runner minutes](#runner-minutes), at the rate of the machine each ran on | | |
| 264 | 270 | | [Actions cache](#actions-cache) | What it holds, on the plan only | | |
| 265 | 271 | | [Deployments](/guides/deployments/) | Builds, requests, CPU time, custom domains and container time, from the first. Projects and previews are not charged. | | |
| 266 | 272 | | [Private repository storage](#storage-search-embeddings-and-scans) | Past the free 1 GB, on the plan only | | |
| ⋯ | |||
| 293 | 299 | ||
| 294 | 300 | | Meter | Unit | | |
| 295 | 301 | | --- | --- | | |
| 296 | − | | Sandbox time | Per second: memory, disk and the Durable Object behind each container, plus CPU | | |
| 302 | + | | Runner minutes | Per minute, each job rounded up, at a fixed rate for each machine | | |
| 297 | 303 | | Deploy builds | Per second | | |
| 298 | 304 | | App requests and CPU | Per million requests, per million CPU milliseconds | | |
| 299 | 305 | | Custom domains | Per domain-month | | |
| ⋯ | |||
| 327 | 333 | days later, and for something charged once a month (git operations, | |
| 328 | 334 | storage, the actions cache, custom domains, embeddings, scans) at the | |
| 329 | 335 | start of the month after that, so no month is charged at two prices. | |
| 336 | + | - **A new machine** is priced at what it costs from its first minute, so | |
| 337 | + | g1t never charges less than it pays. When the standard machine became | |
| 338 | + | [2 vCPU, 8 GiB and 16 GB](#runner-minutes), its runner minutes and | |
| 339 | + | deploy builds were priced for it at once. | |
| 330 | 340 | - **Nothing already charged changes.** Each charge records the price it | |
| 331 | 341 | was made at. | |
| 332 | 342 | ||
| ⋯ | |||
| 334 | 344 | define exactly what they bill; see | |
| 335 | 345 | [What g1t can't do yet](/about/limitations/#billing). | |
| 336 | 346 | ||
| 337 | − | ### Sandbox time | |
| 347 | + | ### Runner minutes | |
| 348 | + | ||
| 349 | + | Every job g1t runs on its own machines (a workflow job, a check, a merge | |
| 350 | + | queue run) is charged by the minute, at a fixed rate for the machine it | |
| 351 | + | ran on: | |
| 338 | 352 | ||
| 339 | − | Every sandbox g1t starts runs on Cloudflare Containers, and Cloudflare | |
| 340 | − | charges g1t for every second of it. So each one is metered by the second, | |
| 341 | − | from start to stop, whatever it was for: checks, the merge queue and | |
| 342 | − | workflow jobs. | |
| 343 | − | There are no free minutes. | |
| 353 | + | | `runs-on` | Machine | vCPUs | Memory | Disk | A minute | | |
| 354 | + | | --- | --- | --- | --- | --- | --- | | |
| 355 | + | | `ubuntu-latest`, any other Linux label, or `g1t-2core` | Standard (`2-core`) | 2 | 8 GiB | 16 GB | $0.004 | | |
| 356 | + | | `g1t-4core` | `4-core` | 4 | 12 GiB | 20 GB | $0.007 | | |
| 344 | 357 | ||
| 345 | − | A sandbox second is priced in two parts: memory, disk and the Durable | |
| 346 | − | Object behind the container, for every second; and CPU, for the vCPU it | |
| 347 | − | used. A sandbox that reports its own CPU use is priced on it. One | |
| 348 | − | that does not is priced at the average CPU sandboxes use. | |
| 358 | + | Checks and the merge queue run on the standard machine. `g1t-2core` is | |
| 359 | + | kept as a name of the standard machine, so workflows that ask for it keep | |
| 360 | + | working, at its rate. | |
| 349 | 361 | ||
| 350 | − | Each sandbox is one line on the [statement](#the-statement), such as | |
| 351 | − | *Checks on acme/api#12: 3m 12s of sandbox time*. Deploy builds are not | |
| 352 | − | counted here: [Deployments](/guides/deployments/) charges them by the | |
| 353 | − | second, from the first, under **Builds**. | |
| 362 | + | **How a job's minutes are counted.** Each job's time, from when its | |
| 363 | + | machine starts to when it stops, is rounded up to the whole minute: a | |
| 364 | + | job of 61 seconds is 2 minutes, and any job that ran is at least 1. Each | |
| 365 | + | job is rounded on its own, so a run of three 20-second jobs is 3 | |
| 366 | + | minutes. There are no free minutes outside the plan's | |
| 367 | + | [included usage](#the-g1t-plan), the [trial](#the-trial) and the | |
| 368 | + | [open-source pool](#the-open-source-pool), which pay for minutes at these | |
| 369 | + | rates. | |
| 354 | 370 | ||
| 355 | − | ### Workflow jobs on larger machines | |
| 371 | + | **Why these rates.** Cloudflare bills g1t for a machine's memory and disk, | |
| 372 | + | and the Durable Object behind it, for every second it runs, busy or idle, | |
| 373 | + | and for its CPU as it is used. Each rate is what a minute of the machine | |
| 374 | + | costs g1t with half its vCPUs busy, plus 20%, rounded up to a tenth of a | |
| 375 | + | cent, and never under what it costs with every vCPU busy: | |
| 356 | 376 | ||
| 357 | − | A workflow job whose `runs-on` names a [larger machine](/guides/actions/#machine-sizes) | |
| 358 | − | runs on a larger Cloudflare Containers instance, and its seconds cost what | |
| 359 | − | that instance costs g1t, plus 20%. The standard machine they are compared | |
| 360 | − | with has 1 vCPU, 4 GiB and 20 GB: | |
| 377 | + | | Machine | Costs g1t a minute, idle | With half its vCPUs busy | With every vCPU busy | A minute | | |
| 378 | + | | --- | --- | --- | --- | --- | | |
| 379 | + | | Standard (`2-core`) | $0.0014 | $0.0026 | $0.0038 | $0.004 | | |
| 380 | + | | `4-core` | $0.0020 | $0.0044 | $0.0068 | $0.007 | | |
| 361 | 381 | ||
| 362 | − | | `runs-on` | Instance | Its memory and disk, against the standard machine's | A second with its vCPUs as busy as an average sandbox's | | |
| 363 | − | | --- | --- | --- | --- | | |
| 364 | − | | `g1t-2core` | 2 vCPU, 8 GiB, 20 GB | 1.8 times | About 1.9 times a sandbox second | | |
| 365 | − | | `g1t-4core` | 4 vCPU, 12 GiB, 20 GB | 2.5 times | About 3.4 times a sandbox second | | |
| 382 | + | Should a job ever cost g1t more than its minutes at the rate, it is | |
| 383 | + | charged that cost instead. A build that compiles often finishes several | |
| 384 | + | times sooner on `g1t-4core`, so it can cost about the same. | |
| 366 | 385 | ||
| 367 | − | A job that reports its own CPU is priced on it: its memory and disk for | |
| 368 | − | every second, and the vCPU-seconds it used. One that does not is priced | |
| 369 | − | at the right-hand column. Its line on the statement names the machine, | |
| 370 | − | such as *A workflow job in acme/api on g1t-4core: 6m 40s of sandbox | |
| 371 | − | time*. A job usually finishes several times sooner on a larger machine, | |
| 372 | − | so it often costs about the same. | |
| 386 | + | Each job is one line on the [statement](#the-statement), naming its | |
| 387 | + | machine and minutes, such as *A workflow job in acme/api on g1t-4core: | |
| 388 | + | 6m 40s, 7 minutes on the 4-core machine*. Usage, the statement and the API | |
| 389 | + | total them as **Runner minutes, 2-core** and **Runner minutes, 4-core**; | |
| 390 | + | months before runner minutes show **Sandbox time**, by the second. | |
| 391 | + | Deploy builds are not counted here: [Deployments](/guides/deployments/) | |
| 392 | + | charges them by the second, from the first, under **Builds**. Jobs on | |
| 393 | + | [your own runners](/guides/self-hosted-runners/) are free: they show as | |
| 394 | + | **Self-hosted runner time** at $0. | |
| 373 | 395 | ||
| 374 | 396 | ## Git operations | |
| 375 | 397 | ||
| ⋯ | |||
| 867 | 889 | **Monthly**), stacked by product, with **Cumulative** to add them up. | |
| 868 | 890 | Hover or focus a column for each product's part; **Show as a table** has | |
| 869 | 891 | every number. | |
| 870 | − | - **The breakdown**: each product family with its meters (sandbox time; | |
| 892 | + | - **The breakdown**: each product family with its meters (runner minutes | |
| 893 | + | for each machine; | |
| 871 | 894 | builds; git operations and private storage with what is free; and so | |
| 872 | 895 | on), each with a trend line, how much was used and its charge at price. | |
| 873 | 896 | Open a meter for its projects. | |
| ⋯ | |||
| 890 | 913 | ||
| 891 | 914 | | Line | What it holds | | |
| 892 | 915 | | --- | --- | | |
| 893 | − | | Sandbox time | Each sandbox's time. | | |
| 916 | + | | Runner minutes, 2-core and Runner minutes, 4-core | Each job on g1t's machines, with the minutes charged on that machine. | | |
| 917 | + | | Sandbox time | Each sandbox's time, in months before runner minutes. | | |
| 894 | 918 | | Self-hosted runner time | Each job on your own runners, at $0. | | |
| 895 | 919 | | Deployments | Builds as they finish; each month's requests, CPU time, custom domains and container time when it closes. | | |
| 896 | 920 | | Private storage | Storage past the free 1 GB, once a month. | | |
Binary or large file; its contents are not shown.
| 183 | 183 | </div> | |
| 184 | 184 | } | |
| 185 | 185 | > | |
| 186 | − | <Figure label="This month" hint="Sandbox time on your usage, at price, from the first of the month."> | |
| 186 | + | <Figure label="This month" hint="Runner minutes on your usage, at price, from the first of the month. Each job's time is rounded up to the minute."> | |
| 187 | 187 | {cost ? ( | |
| 188 | 188 | <> | |
| 189 | 189 | {machineTime(cost.cloudSeconds)} · {money(cost.cloudMicros)} | |
| ⋯ | |||
| 192 | 192 | <Unknown what="Billing" /> | |
| 193 | 193 | )} | |
| 194 | 194 | </Figure> | |
| 195 | − | {data.prices ? ( | |
| 196 | − | <Figure label="Price" hint={`From g1t's price book: ${data.prices.map((price) => price.title).join("; ")}.`}> | |
| 197 | − | <span className="flex flex-wrap justify-end gap-x-1"> | |
| 198 | − | {data.prices.map((price, at) => ( | |
| 199 | − | <span key={price.meter} className="whitespace-nowrap"> | |
| 200 | − | {at > 0 && "+ "} | |
| 201 | − | {perMinute(price.perMinuteMicros)}/{price.unit} | |
| 202 | − | </span> | |
| 203 | − | ))} | |
| 204 | − | </span> | |
| 205 | − | </Figure> | |
| 206 | − | ) : ( | |
| 207 | − | <Figure label="Price"> | |
| 208 | − | <Unknown what="Billing" /> | |
| 209 | − | </Figure> | |
| 210 | − | )} | |
| 195 | + | <Figure label="Price" hint={`A minute of each machine: ${data.prices.map((price) => price.title).join("; ")}.`}> | |
| 196 | + | <span className="flex flex-wrap justify-end gap-x-2"> | |
| 197 | + | {data.prices.map((price) => ( | |
| 198 | + | <span key={price.machine} className="whitespace-nowrap"> | |
| 199 | + | {price.machine} {perMinute(price.perMinuteMicros)}/min | |
| 200 | + | </span> | |
| 201 | + | ))} | |
| 202 | + | </span> | |
| 203 | + | </Figure> | |
| 211 | 204 | </PlaceCard> | |
| 212 | 205 | ||
| 213 | 206 | <PlaceCard | |
| 200 | 200 | month={statement.month} | |
| 201 | 201 | kind={line.kind} | |
| 202 | 202 | count={line.count} | |
| 203 | + | minutes={line.minutes ?? null} | |
| 203 | 204 | chargedMicros={atPrice && line.chargedMicros >= 0 ? (line.priceMicros ?? line.chargedMicros) : line.chargedMicros} | |
| 204 | 205 | coveredMicros={line.coveredMicros ?? 0} | |
| 205 | 206 | day={group === "day" ? g.key : null} | |
| ⋯ | |||
| 229 | 230 | month, | |
| 230 | 231 | kind, | |
| 231 | 232 | count, | |
| 233 | + | minutes, | |
| 232 | 234 | chargedMicros, | |
| 233 | 235 | coveredMicros, | |
| 234 | 236 | day, | |
| ⋯ | |||
| 238 | 240 | month: string; | |
| 239 | 241 | kind: string; | |
| 240 | 242 | count: number; | |
| 243 | + | /** Runner minutes on the line, as charged. */ | |
| 244 | + | minutes: number | null; | |
| 241 | 245 | chargedMicros: number; | |
| 242 | 246 | coveredMicros: number; | |
| 243 | 247 | day: string | null; | |
| ⋯ | |||
| 288 | 292 | <ChevronRight size={14} className={`shrink-0 text-faint transition-transform ${open ? "rotate-90" : ""}`} /> | |
| 289 | 293 | <span className="grow truncate font-medium">{kind}</span> | |
| 290 | 294 | <span className="shrink-0 text-xs text-faint tabular-nums"> | |
| 295 | + | {minutes !== null && `${minutes.toLocaleString("en-US")} ${minutes === 1 ? "minute" : "minutes"} · `} | |
| 291 | 296 | {count.toLocaleString("en-US")} {count === 1 ? "entry" : "entries"} | |
| 292 | 297 | {coveredMicros > 0 && ` · ${charge(coveredMicros)} paid for`} | |
| 293 | 298 | </span> | |
| 52 | 52 | waiting: OwnWaiting | null; | |
| 53 | 53 | /** This month's machine time; null when billing did not answer. */ | |
| 54 | 54 | cost: RunnerCost | null; | |
| 55 | − | /** A minute on g1t's cloud, from the price book; null when it has none. */ | |
| 56 | − | prices: CloudPrice[] | null; | |
| 55 | + | /** Each machine on g1t's cloud and its price a minute, from the machine catalogue. */ | |
| 56 | + | prices: CloudPrice[]; | |
| 57 | 57 | /** People's computers with the desktop app, as each app last said; null when the actions service did not answer. */ | |
| 58 | 58 | desktops: DesktopRunner[] | null; | |
| 59 | 59 | }; | |
| ⋯ | |||
| 75 | 75 | export async function loadRunnersPage(workspace: string, actor: User, repositories: string[] = [], now = new Date()): Promise<RunnersPageData> { | |
| 76 | 76 | const from = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString().slice(0, 10); | |
| 77 | 77 | const until = now.toISOString().slice(0, 10); | |
| 78 | − | const [base, activity, runs, report, book, desktops] = await Promise.all([ | |
| 78 | + | const [base, activity, runs, report, desktops] = await Promise.all([ | |
| 79 | 79 | loadRunners({ workspace }, actor, repositories), | |
| 80 | 80 | runners | |
| 81 | 81 | .activity(actor, workspace) | |
| ⋯ | |||
| 89 | 89 | .usageReport(workspace, actor, { from, until }) | |
| 90 | 90 | .then((result) => (result.ok ? result.value : null)) | |
| 91 | 91 | .catch(warn("usage report")), | |
| 92 | − | billing.prices().catch(warn("prices")), | |
| 93 | 92 | runners | |
| 94 | 93 | .desktops(actor, workspace) | |
| 95 | 94 | .then((result) => (result.ok ? result.value : null)) | |
| ⋯ | |||
| 100 | 99 | cloud: cloudNow(runs, activity, RUN_PAGE, RUNNER_ACTIVITY_LIMIT), | |
| 101 | 100 | waiting: ownWaiting(activity), | |
| 102 | 101 | cost: runnerCost(report), | |
| 103 | − | prices: cloudPrices(book?.prices ?? null), | |
| 102 | + | prices: cloudPrices(), | |
| 104 | 103 | desktops, | |
| 105 | 104 | }; | |
| 106 | 105 | } | |
| 1 | 1 | import assert from "node:assert/strict"; | |
| 2 | 2 | import { test } from "node:test"; | |
| 3 | 3 | ||
| 4 | − | import type { AgentRun, Price, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 4 | + | import type { AgentRun, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 5 | 5 | ||
| 6 | 6 | import { cloudNow, cloudPrices, machineTime, ownWaiting, perMinute, runnerCost } from "./runners.ts"; | |
| 7 | 7 | ||
| ⋯ | |||
| 74 | 74 | free: false, | |
| 75 | 75 | products: [ | |
| 76 | 76 | { key: "agent", label: "Agent", micros: 0, meters: [meter("agent_sandbox", 600, 1_000_000), meter("agent_models", 9, 5_000_000)] }, | |
| 77 | − | { key: "sandboxes", label: "Sandboxes", micros: 0, meters: [meter("sandbox", 120, 200_000), meter("self_hosted", 3600, 0)] }, | |
| 77 | + | { | |
| 78 | + | key: "sandboxes", | |
| 79 | + | label: "Sandboxes", | |
| 80 | + | micros: 0, | |
| 81 | + | meters: [ | |
| 82 | + | meter("runner_2-core", 10, 40_000), | |
| 83 | + | meter("runner_4-core", 2, 14_000), | |
| 84 | + | meter("sandbox", 120, 200_000), | |
| 85 | + | meter("self_hosted", 3600, 0), | |
| 86 | + | ], | |
| 87 | + | }, | |
| 78 | 88 | ], | |
| 79 | 89 | } as unknown as UsageReport; | |
| 80 | − | assert.deepEqual(runnerCost(report), { from: "2026-10-01", until: "2026-10-10", cloudSeconds: 720, cloudMicros: 1_200_000, ownSeconds: 3600, free: false }); | |
| 90 | + | // Runner minutes count as their minutes; older sandbox time by the second. | |
| 91 | + | assert.deepEqual(runnerCost(report), { | |
| 92 | + | from: "2026-10-01", | |
| 93 | + | until: "2026-10-10", | |
| 94 | + | cloudSeconds: 720 + 12 * 60, | |
| 95 | + | cloudMicros: 1_254_000, | |
| 96 | + | ownSeconds: 3600, | |
| 97 | + | free: false, | |
| 98 | + | }); | |
| 81 | 99 | }); | |
| 82 | 100 | ||
| 83 | − | test("prices are the sandbox's parts when both are priced, else the one price, per minute", () => { | |
| 84 | − | const price = (meter: string, unit: string, priceMicros: number) => ({ meter, title: meter, unit, priceMicros }) as Price; | |
| 101 | + | test("prices are each machine's rate a minute, from the catalogue", () => { | |
| 85 | 102 | assert.deepEqual( | |
| 86 | − | cloudPrices([price("sandbox_second", "second", 25), price("sandbox_base_second", "second", 14.5), price("sandbox_cpu_second", "vCPU-second", 24)])!.map((p) => [p.meter, p.unit, p.perMinuteMicros]), | |
| 103 | + | cloudPrices().map((p) => [p.machine, p.perMinuteMicros, p.title]), | |
| 87 | 104 | [ | |
| 88 | − | ["sandbox_base_second", "minute", 870], | |
| 89 | − | ["sandbox_cpu_second", "vCPU-minute", 1440], | |
| 105 | + | ["2-core", 4000, "2-core: 2 vCPU, 8 GiB, 16 GB"], | |
| 106 | + | ["4-core", 7000, "4-core: 4 vCPU, 12 GiB, 20 GB"], | |
| 90 | 107 | ], | |
| 91 | 108 | ); | |
| 92 | − | assert.deepEqual(cloudPrices([price("sandbox_second", "second", 25)])!.map((p) => p.perMinuteMicros), [1500]); | |
| 93 | − | assert.equal(cloudPrices([]), null); | |
| 94 | − | assert.equal(cloudPrices(null), null); | |
| 109 | + | assert.equal(perMinute(4000), "$0.004"); | |
| 95 | 110 | }); | |
| 96 | 111 | ||
| 97 | 112 | test("machine time and prices read as people say them", () => { | |
| 10 | 10 | * (`runner_activity`'s `handed_over`), and its workflow jobs running in | |
| 11 | 11 | * g1t's sandboxes (`cloud_jobs`). | |
| 12 | 12 | * - **This month** is billing's usage report from the first of the month: | |
| 13 | − | * sandbox time is g1t's cloud, at price (older months may also name | |
| 14 | − | * agent sandbox time); | |
| 13 | + | * runner minutes by machine are g1t's cloud, at price (older months | |
| 14 | + | * name sandbox time by the second, and agent sandbox time); | |
| 15 | 15 | * self-hosted runner time is the workspace's own, at $0. | |
| 16 | 16 | */ | |
| 17 | − | import type { AgentRun, MeterLine, Price, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 17 | + | import type { AgentRun, MeterLine, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 18 | + | import { machineSize, machines } from "@g1t/contracts/instances"; | |
| 18 | 19 | ||
| 19 | 20 | /** One thing running in one of g1t's sandboxes. */ | |
| 20 | 21 | export type CloudWork = { | |
| ⋯ | |||
| 55 | 56 | free: boolean; | |
| 56 | 57 | }; | |
| 57 | 58 | ||
| 58 | − | /** A price for time on g1t's cloud, per minute, from the price book. */ | |
| 59 | − | export type CloudPrice = { meter: string; title: string; unit: string; perMinuteMicros: number }; | |
| 59 | + | /** A machine on g1t's cloud and its price a minute, from the machine catalogue. */ | |
| 60 | + | export type CloudPrice = { machine: string; title: string; perMinuteMicros: number }; | |
| 60 | 61 | ||
| 61 | − | /** Billing's meters for machine time (`METERS` in services/billing/src/report.rs); `agent_sandbox` is in older months' reports. */ | |
| 62 | − | export const CLOUD_METERS = ["agent_sandbox", "sandbox"] as const; | |
| 62 | + | /** | |
| 63 | + | * Billing's meters for machine time (`meters()` in services/billing/src/report.rs): | |
| 64 | + | * runner minutes by machine (`runner_2-core`), and by the second, sandbox time | |
| 65 | + | * from before minutes (`agent_sandbox` in still older months' reports). | |
| 66 | + | */ | |
| 67 | + | export const RUNNER_METER_PREFIX = "runner_"; | |
| 68 | + | export const CLOUD_SECOND_METERS = ["agent_sandbox", "sandbox"] as const; | |
| 63 | 69 | export const OWN_METER = "self_hosted"; | |
| 64 | − | ||
| 65 | − | /** The price book's meters for a sandbox's time: its parts when both are priced, else the one price. */ | |
| 66 | − | const PRICE_PARTS = ["sandbox_base_second", "sandbox_cpu_second"] as const; | |
| 67 | − | const PRICE_WHOLE = "sandbox_second"; | |
| 68 | 70 | ||
| 69 | 71 | /** Where a sandbox run (checks, the merge queue) shows: its pull request, else the repository's pull requests. */ | |
| 70 | 72 | export function sandboxRunHref(repo: string, number?: number | null): string { | |
| ⋯ | |||
| 126 | 128 | const meters: MeterLine[] = report.products.flatMap((product) => product.meters); | |
| 127 | 129 | const sum = (keys: readonly string[], pick: (meter: MeterLine) => number) => | |
| 128 | 130 | meters.filter((meter) => keys.includes(meter.key)).reduce((total, meter) => total + pick(meter), 0); | |
| 131 | + | const runnerKeys = meters.filter((meter) => meter.key.startsWith(RUNNER_METER_PREFIX)).map((meter) => meter.key); | |
| 129 | 132 | return { | |
| 130 | 133 | from: report.from, | |
| 131 | 134 | until: report.until, | |
| 132 | − | cloudSeconds: sum(CLOUD_METERS, (meter) => meter.quantity), | |
| 133 | − | cloudMicros: sum(CLOUD_METERS, (meter) => meter.micros + (meter.pendingMicros ?? 0)), | |
| 135 | + | // Minutes as charged, and sandbox time from before them. | |
| 136 | + | cloudSeconds: sum(runnerKeys, (meter) => meter.quantity * 60) + sum(CLOUD_SECOND_METERS, (meter) => meter.quantity), | |
| 137 | + | cloudMicros: sum([...runnerKeys, ...CLOUD_SECOND_METERS], (meter) => meter.micros + (meter.pendingMicros ?? 0)), | |
| 134 | 138 | ownSeconds: sum([OWN_METER], (meter) => meter.quantity), | |
| 135 | 139 | free: report.free, | |
| 136 | 140 | }; | |
| 137 | 141 | } | |
| 138 | 142 | ||
| 139 | − | /** The price of a minute on g1t's cloud, as the price book has it; null when it has none. */ | |
| 140 | − | export function cloudPrices(prices: Price[] | null): CloudPrice[] | null { | |
| 141 | − | if (!prices) return null; | |
| 142 | − | const perMinute = (price: Price): CloudPrice => ({ | |
| 143 | − | meter: price.meter, | |
| 144 | − | title: price.title, | |
| 145 | − | unit: price.unit.replace(/second$/, "minute"), | |
| 146 | − | perMinuteMicros: price.priceMicros * 60, | |
| 147 | − | }); | |
| 148 | − | const parts = PRICE_PARTS.map((meter) => prices.find((price) => price.meter === meter)); | |
| 149 | − | if (parts.every(Boolean)) return parts.map((price) => perMinute(price!)); | |
| 150 | − | const whole = prices.find((price) => price.meter === PRICE_WHOLE); | |
| 151 | − | return whole ? [perMinute(whole)] : null; | |
| 143 | + | /** Each machine on g1t's cloud and its price a minute, from the machine catalogue. */ | |
| 144 | + | export function cloudPrices(): CloudPrice[] { | |
| 145 | + | return machines().map((instance) => ({ | |
| 146 | + | machine: instance.machine, | |
| 147 | + | title: `${instance.machine}: ${machineSize(instance)}`, | |
| 148 | + | perMinuteMicros: instance.microsPerMinute, | |
| 149 | + | })); | |
| 152 | 150 | } | |
| 153 | 151 | ||
| 154 | 152 | /** Seconds as people read machine time: "45s", "12 min", "3.4 h". */ | |
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…