g1t's runners use only Cloudflare's named instance types, sized for the least they cost while giving a job what it needs: the standard machine (ubuntu-latest, every other hosted label and all other work) is standard-3, 2 vCPU, 8 GiB and 16 GB of disk, where a custom 1 vCPU, 4 GiB, 20 GB type had been asked for and standard-1's half vCPU, 4 GiB and 8 GB was still being served; g1t-2core is kept as a name of the same machine, so workflows that ask for it keep working, at the same price; and g1t-4core stays standard-4, 4 vCPU, 12 GiB and 20 GB, for heavier jobs. At the classes' max_instances (20, 10, 10) the runners hold at most 100 vCPU, 360 GiB and 680 GB at once, well inside the account's 1,500 vCPU and 6 TiB. The contracts' instance table and its TypeScript mirror say the same sizes; the price scale, worked out as before from Cloudflare's list prices with every vCPU busy and the Durable Object behind each, is 1.8 for g1t-4core against the standard machine. The billing keeper's base is standard-3's memory, disk and Durable Object (22.6825 millionths of a dollar a second, billed whether the CPU is busy or not), and the fallbacks follow it: 63 for a sandbox second with both vCPUs busy, 26 for a build second. Migration 0056 moves the price book to the new base at once rather than after the notice period, so g1t never charges less than a sandbox costs it: sandbox_base_second to 22.6825, and sandbox_second and build_second up by the base's growth of 10.56, replacing any rise still waiting for its date. Tests hold wrangler.jsonc to the contracts' named sizes, the scales to the list prices, and every machine's price scale to at least its base's, so an idle job that does not report its CPU still pays for the memory and disk it held. The docs' machine tables, prices and the Cloudflare deploy guide say so.
16 files+235−1210/16 viewed
| 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 | 542 | | `runs-on` | vCPUs | Memory | Disk | | |
| 544 | 543 | | --- | --- | --- | --- | | |
| 545 | − | | `ubuntu-latest`, or any other Linux label | 1 | 4 GiB | 20 GB | | |
| 546 | − | | `g1t-2core` | 2 | 8 GiB | 20 GB | | |
| 544 | + | | `ubuntu-latest`, or any other Linux label | 2 | 8 GiB | 16 GB | | |
| 545 | + | | `g1t-2core` (a name of the standard machine) | 2 | 8 GiB | 16 GB | | |
| 547 | 546 | | `g1t-4core` | 4 | 12 GiB | 20 GB | | |
| 548 | 547 | ||
| 549 | 548 | ```yaml | |
| ⋯ | |||
| 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' }}`). What | |
| 559 | + | each costs is in [usage and billing](/guides/usage-and-billing/#machines). | |
| 561 | 560 | ||
| 562 | 561 | A job on g1t's machines runs for at most 60 minutes, whatever its | |
| 563 | 562 | `timeout-minutes`, and the workflow's page warns about a job that asks | |
| ⋯ | |||
| 751 | 750 | ||
| 752 | 751 | - **Machine.** Containers share the job's machine: its vCPUs, memory and | |
| 753 | 752 | 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. | |
| 753 | + | `g1t-4core`. `--cpus` and `--memory` limit a container within that. | |
| 756 | 754 | - **Disk.** Images take room on the job's disk. On a machine whose disk | |
| 757 | 755 | cannot hold layered images, the Engine stores plain copies, which take | |
| 758 | 756 | more room; the log says when it does. | |
| 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. |
| 139 | 139 | ||
| 140 | 140 | | What | Unit | Costs g1t | You pay | | |
| 141 | 141 | | --- | --- | --- | --- | | |
| 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 | | |
| 142 | + | | Sandbox time (workflows, checks, the merge queue) on [the standard machine](#machines) (2 vCPU, 8 GiB, 16 GB) | Second | About $0.0015 a minute: from $0.0014 idle to $0.0038 with both vCPUs busy | About $0.0018 a minute: from $0.0016 to $0.0045 | | |
| 143 | + | | Workflow jobs on [`g1t-4core`](#machines) (4 vCPU, 12 GiB, 20 GB) | Second | About 1.8 times a sandbox second: from $0.0020 a minute idle to $0.0068 with all four vCPUs busy | Cost + 20%: from $0.0024 to $0.0081 | | |
| 144 | + | | Deploy builds | Second | About $0.0015 a minute | About $0.0018 a minute | | |
| 145 | 145 | | App requests | Million | $0.30 | $0.36 | | |
| 146 | 146 | | App CPU time | Million CPU milliseconds | $0.02 | $0.024 | | |
| 147 | 147 | | Custom domains | Domain-month | $0.10 | $0.12 | | |
| ⋯ | |||
| 260 | 260 | | | Charged | | |
| 261 | 261 | | --- | --- | | |
| 262 | 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) | | |
| 263 | + | | [Workflow](/guides/actions/) jobs | [Sandbox time](#sandbox-time), more on [`g1t-4core`](#machines) | | |
| 264 | 264 | | [Actions cache](#actions-cache) | What it holds, on the plan only | | |
| 265 | 265 | | [Deployments](/guides/deployments/) | Builds, requests, CPU time, custom domains and container time, from the first. Projects and previews are not charged. | | |
| 266 | 266 | | [Private repository storage](#storage-search-embeddings-and-scans) | Past the free 1 GB, on the plan only | | |
| ⋯ | |||
| 327 | 327 | days later, and for something charged once a month (git operations, | |
| 328 | 328 | storage, the actions cache, custom domains, embeddings, scans) at the | |
| 329 | 329 | start of the month after that, so no month is charged at two prices. | |
| 330 | + | - **A new machine** is priced at what it costs from its first second, so | |
| 331 | + | g1t never charges less than it pays. When the standard sandbox became | |
| 332 | + | [2 vCPU, 8 GiB and 16 GB](#machines), sandbox time and deploy builds | |
| 333 | + | rose with it at once. | |
| 330 | 334 | - **Nothing already charged changes.** Each charge records the price it | |
| 331 | 335 | was made at. | |
| 332 | 336 | ||
| ⋯ | |||
| 352 | 356 | counted here: [Deployments](/guides/deployments/) charges them by the | |
| 353 | 357 | second, from the first, under **Builds**. | |
| 354 | 358 | ||
| 355 | − | ### Workflow jobs on larger machines | |
| 359 | + | ### Machines | |
| 356 | 360 | ||
| 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: | |
| 361 | + | Checks, the merge queue, deploy builds and workflow jobs run on the | |
| 362 | + | standard machine: 2 vCPU, 8 GiB of memory and 16 GB of disk. A workflow | |
| 363 | + | job whose `runs-on` is `g1t-4core` gets 4 vCPU, 12 GiB and 20 GB, for | |
| 364 | + | heavier jobs. `g1t-2core` is kept as a name of the standard machine, so | |
| 365 | + | workflows that ask for it keep working, at the standard price. | |
| 361 | 366 | ||
| 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 | | |
| 367 | + | | `runs-on` | Machine | Memory, disk and the Durable Object, billed every second | CPU, billed as used | A second without its own CPU reported | | |
| 368 | + | | --- | --- | --- | --- | --- | | |
| 369 | + | | Any hosted label, and `g1t-2core` | 2 vCPU, 8 GiB, 16 GB | About $0.0014 a minute | $0.00002 a vCPU-second, up to $0.0024 a minute | A sandbox second | | |
| 370 | + | | `g1t-4core` | 4 vCPU, 12 GiB, 20 GB | About $0.0020 a minute, 1.45 times the standard machine's | Up to $0.0048 a minute | About 1.8 times a sandbox second | | |
| 366 | 371 | ||
| 367 | − | A job that reports its own CPU is priced on it: its memory and disk for | |
| 372 | + | Those are what each costs g1t; you pay them plus 20%. A job that reports | |
| 373 | + | its own CPU is priced on it: its memory, disk and Durable Object for | |
| 368 | 374 | 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. | |
| 375 | + | at the right-hand column, from the average CPU sandboxes use. Either way, | |
| 376 | + | an idle second is charged at no less than its memory, disk and Durable | |
| 377 | + | Object cost g1t, plus 20%. A job's line on the statement names the | |
| 378 | + | machine, such as *A workflow job in acme/api on g1t-4core: 6m 40s of | |
| 379 | + | sandbox time*. A build that compiles often finishes sooner on | |
| 380 | + | `g1t-4core`, so it can cost about the same. | |
| 373 | 381 | ||
| 374 | 382 | ## Git operations | |
| 375 | 383 | ||
| 1037 | 1037 | pub brokered: bool, | |
| 1038 | 1038 | } | |
| 1039 | 1039 | ||
| 1040 | − | /// A size of machine g1t runs workflow jobs on, asked for by a label in | |
| 1041 | − | /// `runs-on`. Each is a Cloudflare Containers instance type (named, or a | |
| 1042 | − | /// custom one: services/runner/wrangler.jsonc), every one with the 20 GB of | |
| 1043 | − | /// disk Containers allow at most; it costs what that instance costs g1t, | |
| 1044 | − | /// plus the margin, like any sandbox time. | |
| 1040 | + | /// A machine g1t runs workflow jobs on, asked for by a label in `runs-on`. | |
| 1041 | + | /// Each is a named Cloudflare Containers instance type (see | |
| 1042 | + | /// services/runner/wrangler.jsonc; no custom types): the standard machine | |
| 1043 | + | /// is standard-3 (2 vCPU, 8 GiB, 16 GB of disk), `g1t-2core` is kept as a | |
| 1044 | + | /// name of it so workflows that ask for it keep working, and `g1t-4core` is | |
| 1045 | + | /// standard-4 (4 vCPU, 12 GiB, 20 GB). It costs what that instance costs | |
| 1046 | + | /// g1t, plus the margin, like any sandbox time. | |
| 1045 | 1047 | #[derive(Clone, Copy, Debug, PartialEq)] | |
| 1046 | 1048 | pub struct InstanceType { | |
| 1047 | 1049 | /// The `runs-on` label, or `standard` for the default. | |
| 1048 | 1050 | pub label: &'static str, | |
| 1049 | − | /// The Containers instance type: a named one, or `custom` for one | |
| 1050 | − | /// sized by its vCPU, memory and disk. | |
| 1051 | + | /// The named Containers instance type. | |
| 1051 | 1052 | pub container: &'static str, | |
| 1052 | 1053 | pub vcpu: f64, | |
| 1053 | 1054 | pub memory_gib: f64, | |
| ⋯ | |||
| 1063 | 1064 | ||
| 1064 | 1065 | /// The default: what `ubuntu-latest` and every other hosted label get. | |
| 1065 | 1066 | pub const STANDARD_INSTANCE: InstanceType = | |
| 1066 | − | InstanceType { label: "standard", container: "custom", vcpu: 1.0, memory_gib: 4.0, disk_gb: 20.0, price_scale: 1.0 }; | |
| 1067 | + | InstanceType { label: "standard", container: "standard-3", vcpu: 2.0, memory_gib: 8.0, disk_gb: 16.0, price_scale: 1.0 }; | |
| 1067 | 1068 | ||
| 1068 | − | /// Every machine a workflow job can ask for, the default first. | |
| 1069 | + | /// Every label a workflow job can ask for, the default first. | |
| 1069 | 1070 | pub const INSTANCE_TYPES: [InstanceType; 3] = [ | |
| 1070 | 1071 | STANDARD_INSTANCE, | |
| 1071 | − | InstanceType { label: "g1t-2core", container: "custom", vcpu: 2.0, memory_gib: 8.0, disk_gb: 20.0, price_scale: 1.9 }, | |
| 1072 | − | InstanceType { label: "g1t-4core", container: "standard-4", vcpu: 4.0, memory_gib: 12.0, disk_gb: 20.0, price_scale: 3.4 }, | |
| 1072 | + | // The standard machine under the name it had when it was larger. | |
| 1073 | + | InstanceType { label: "g1t-2core", ..STANDARD_INSTANCE }, | |
| 1074 | + | InstanceType { label: "g1t-4core", container: "standard-4", vcpu: 4.0, memory_gib: 12.0, disk_gb: 20.0, price_scale: 1.8 }, | |
| 1073 | 1075 | ]; | |
| 1074 | 1076 | ||
| 1075 | 1077 | /// The machine a job's `runs-on` labels ask for: the largest named, or the | |
| ⋯ | |||
| 1583 | 1585 | fn runs_on_picks_the_machine() { | |
| 1584 | 1586 | assert_eq!(instance_for(&labels(&["ubuntu-latest"])), STANDARD_INSTANCE); | |
| 1585 | 1587 | assert_eq!(instance_for(&labels(&[])).label, "standard"); | |
| 1586 | − | assert_eq!(instance_for(&labels(&["g1t-4core"])).container, "standard-4"); | |
| 1588 | + | assert_eq!(instance_for(&labels(&["g1t-4core"])).label, "g1t-4core"); | |
| 1587 | 1589 | assert_eq!(instance_for(&labels(&["ubuntu-latest", "G1T-2Core"])).label, "g1t-2core"); | |
| 1588 | − | // Both named: the larger. | |
| 1590 | + | // Both named: the later one. | |
| 1589 | 1591 | assert_eq!(instance_for(&labels(&["g1t-2core", "g1t-4core"])).label, "g1t-4core"); | |
| 1592 | + | assert_eq!(instance_named("g1t-2core").map(|i| i.vcpu), Some(2.0)); | |
| 1590 | 1593 | assert_eq!(instance_named("g1t-4core").map(|i| i.vcpu), Some(4.0)); | |
| 1591 | 1594 | assert_eq!(instance_named("standard"), Some(STANDARD_INSTANCE)); | |
| 1592 | 1595 | assert_eq!(instance_named("g1t-64core"), None); | |
| 1593 | 1596 | } | |
| 1594 | 1597 | ||
| 1595 | 1598 | #[test] | |
| 1596 | − | fn every_machine_has_the_most_disk_containers_allow_and_fits_a_custom_type() { | |
| 1599 | + | fn every_machine_is_a_named_instance_type_at_its_size() { | |
| 1600 | + | // Containers' named instance types, at the sizes Cloudflare gives | |
| 1601 | + | // them; no custom types. | |
| 1602 | + | let named = |container: &str| match container { | |
| 1603 | + | "standard-3" => Some((2.0, 8.0, 16.0)), | |
| 1604 | + | "standard-4" => Some((4.0, 12.0, 20.0)), | |
| 1605 | + | _ => None, | |
| 1606 | + | }; | |
| 1597 | 1607 | for instance in INSTANCE_TYPES { | |
| 1598 | − | assert_eq!(instance.disk_gb, 20.0, "{}", instance.label); | |
| 1599 | − | // Custom instance types: 1 to 4 vCPU, at most 12 GiB, at least | |
| 1600 | − | // 3 GiB a vCPU. | |
| 1601 | − | assert!((1.0..=4.0).contains(&instance.vcpu), "{}", instance.label); | |
| 1602 | − | assert!(instance.memory_gib <= 12.0 && instance.memory_gib >= 3.0 * instance.vcpu, "{}", instance.label); | |
| 1608 | + | assert_eq!(named(instance.container), Some((instance.vcpu, instance.memory_gib, instance.disk_gb)), "{}", instance.label); | |
| 1603 | 1609 | } | |
| 1610 | + | // g1t-2core is a name of the standard machine. | |
| 1611 | + | assert_eq!(instance_named("g1t-2core").map(|i| InstanceType { label: "standard", ..i }), Some(STANDARD_INSTANCE)); | |
| 1604 | 1612 | } | |
| 1605 | 1613 | ||
| 1606 | 1614 | #[test] | |
| 387 | 387 | pub const MICROS_PER_MILLION_CPU_MS: i64 = 20_000; | |
| 388 | 388 | /// What one second of a build's sandbox costs g1t (the standard | |
| 389 | 389 | /// sandbox, `actions::STANDARD_INSTANCE`), rounded up: the price keeper | |
| 390 | − | /// measured 14.5 on 2026-10-05, when it had 8 GB of disk, and its 20 GB | |
| 391 | − | /// now add 0.84. Only a fallback: billing charges builds at the price | |
| 392 | − | /// book's `build_second`, which the keeper keeps current. | |
| 393 | − | pub const MICROS_PER_BUILD_SECOND: i64 = 16; | |
| 390 | + | /// measured 14.5 on 2026-10-05, when it had 4 GiB and 8 GB of disk | |
| 391 | + | /// (12.1225 of it memory, disk and the Durable Object), and standard-3's | |
| 392 | + | /// 8 GiB and 16 GB (22.6825) add 10.56, for the same CPU in use. Only a | |
| 393 | + | /// fallback: billing charges builds at the price book's `build_second`, | |
| 394 | + | /// which the keeper keeps current. | |
| 395 | + | pub const MICROS_PER_BUILD_SECOND: i64 = 26; | |
| 394 | 396 | /// What one custom hostname costs g1t a month (Cloudflare for SaaS): | |
| 395 | 397 | /// $0.10. | |
| 396 | 398 | pub const MICROS_PER_DOMAIN_MONTH: i64 = 100_000; |
| 249 | 249 | ||
| 250 | 250 | /** | |
| 251 | 251 | * What one second of a sandbox costs g1t, in millionths of a dollar, when | |
| 252 | − | * the price book cannot be read: the standard sandbox (1 vCPU, 4 GiB, 20 GB) | |
| 253 | − | * with its vCPU busy and its Durable Object, 32.96 at list prices, rounded | |
| 254 | − | * up. | |
| 252 | + | * the price book cannot be read: the standard sandbox (standard-3: 2 vCPU, | |
| 253 | + | * 8 GiB, 16 GB) with both vCPUs busy and its Durable Object, 62.68 at list | |
| 254 | + | * prices, rounded up. | |
| 255 | 255 | */ | |
| 256 | − | export const FALLBACK_SANDBOX_MICROS_PER_SECOND = 33; | |
| 256 | + | export const FALLBACK_SANDBOX_MICROS_PER_SECOND = 63; | |
| 257 | 257 | ||
| 258 | 258 | /** A sandbox for `minutes` at `microsPerSecond`. */ | |
| 259 | 259 | export function sandboxEstimateMicros(minutes: number, microsPerSecond: number): number { |
| 1 | 1 | /** | |
| 2 | − | * Sizes of machine g1t runs workflow jobs on, asked for by a label in | |
| 3 | − | * `runs-on`. Mirrors `g1t_contracts::actions::INSTANCE_TYPES`. Each is a | |
| 4 | − | * Cloudflare Containers instance type (named, or a custom one sized in | |
| 5 | − | * services/runner/wrangler.jsonc), every one with the 20 GB of disk | |
| 6 | − | * Containers allow at most, and costs what that instance costs g1t, plus | |
| 7 | − | * the margin, like any sandbox time. | |
| 2 | + | * Machines g1t runs workflow jobs on, asked for by a label in `runs-on`. | |
| 3 | + | * Mirrors `g1t_contracts::actions::INSTANCE_TYPES`. Each is a named | |
| 4 | + | * Cloudflare Containers instance type (see services/runner/wrangler.jsonc; | |
| 5 | + | * no custom types): the standard machine is standard-3 (2 vCPU, 8 GiB, | |
| 6 | + | * 16 GB of disk), `g1t-2core` is kept as a name of it so workflows that ask | |
| 7 | + | * for it keep working, and `g1t-4core` is standard-4 (4 vCPU, 12 GiB, | |
| 8 | + | * 20 GB). It costs what that instance costs g1t, plus the margin, like any | |
| 9 | + | * sandbox time. | |
| 8 | 10 | */ | |
| 9 | 11 | export type InstanceType = { | |
| 10 | 12 | /** The `runs-on` label, or `standard` for the default. */ | |
| 11 | 13 | label: string; | |
| 12 | − | /** The Containers instance type: a named one, or `custom`. */ | |
| 14 | + | /** The named Containers instance type. */ | |
| 13 | 15 | container: string; | |
| 14 | 16 | vcpu: number; | |
| 15 | 17 | memoryGib: number; | |
| ⋯ | |||
| 26 | 28 | /** The default: what `ubuntu-latest` and every other hosted label get. */ | |
| 27 | 29 | export const STANDARD_INSTANCE: InstanceType = { | |
| 28 | 30 | label: "standard", | |
| 29 | − | container: "custom", | |
| 30 | − | vcpu: 1, | |
| 31 | − | memoryGib: 4, | |
| 32 | − | diskGb: 20, | |
| 31 | + | container: "standard-3", | |
| 32 | + | vcpu: 2, | |
| 33 | + | memoryGib: 8, | |
| 34 | + | diskGb: 16, | |
| 33 | 35 | estimateScale: 1, | |
| 34 | 36 | }; | |
| 35 | 37 | ||
| 36 | 38 | /** | |
| 37 | − | * Every machine a job can ask for, the default first. The scales follow | |
| 39 | + | * Every label a job can ask for, the default first. The scales follow | |
| 38 | 40 | * Cloudflare's list prices: memory $0.0000025 a GiB-second, disk | |
| 39 | 41 | * $0.00000007 a GB-second, vCPU $0.00002 a second, and the Durable Object | |
| 40 | 42 | * behind each at 128 MB, $0.0000125 a GB-second; to one decimal. | |
| 41 | 43 | */ | |
| 42 | 44 | export const INSTANCE_TYPES: readonly InstanceType[] = [ | |
| 43 | 45 | STANDARD_INSTANCE, | |
| 44 | − | { label: "g1t-2core", container: "custom", vcpu: 2, memoryGib: 8, diskGb: 20, estimateScale: 1.9 }, | |
| 45 | − | { label: "g1t-4core", container: "standard-4", vcpu: 4, memoryGib: 12, diskGb: 20, estimateScale: 3.4 }, | |
| 46 | + | // The standard machine under the name it had when it was larger. | |
| 47 | + | { ...STANDARD_INSTANCE, label: "g1t-2core" }, | |
| 48 | + | { label: "g1t-4core", container: "standard-4", vcpu: 4, memoryGib: 12, diskGb: 20, estimateScale: 1.8 }, | |
| 46 | 49 | ]; | |
| 47 | 50 | ||
| 48 | 51 | /** An instance type by its label; the standard one for anything else. */ | |
| 1 | + | -- The standard sandbox is now Cloudflare Containers' standard-3: 2 vCPU, | |
| 2 | + | -- 8 GiB of memory and 16 GB of disk, where it had half a vCPU, 4 GiB and | |
| 3 | + | -- 8 GB (standard-1; a custom 1 vCPU, 4 GiB, 20 GB type was never served). | |
| 4 | + | -- Cloudflare bills memory and disk as provisioned for every second a | |
| 5 | + | -- container runs, so a sandbox second's base (memory, disk and the Durable | |
| 6 | + | -- Object behind it) is now, at list prices: | |
| 7 | + | -- | |
| 8 | + | -- 8 x 2.5 + 16 x 0.07 + 0.125 x 12.5 = 22.6825 millionths of a dollar | |
| 9 | + | -- | |
| 10 | + | -- against 12.1225 for 4 GiB and 8 GB. g1t-4core (standard-4) is priced | |
| 11 | + | -- from it by its scale in the contracts. The price book follows it now, | |
| 12 | + | -- not after the keeper's notice period, so g1t never charges less than a | |
| 13 | + | -- sandbox costs it: | |
| 14 | + | -- | |
| 15 | + | -- - sandbox_base_second (runs that report their own CPU): 22.6825. | |
| 16 | + | -- - sandbox_second and build_second (the average, for runs that do not): | |
| 17 | + | -- what they were, or a rise still waiting for its date if that is more, | |
| 18 | + | -- plus 10.56, the base's growth from 12.1225. The CPU in use on top of | |
| 19 | + | -- the base is unchanged, and counting it from the smallest base the | |
| 20 | + | -- price book had keeps the result at or above cost. | |
| 21 | + | -- | |
| 22 | + | -- Rises still waiting for their date on these meters are replaced, as | |
| 23 | + | -- pricing.rs does when a newer decision replaces a waiting version, so | |
| 24 | + | -- none can take the price below cost later. The keeper measures again on | |
| 25 | + | -- its daily run. | |
| 26 | + | ||
| 27 | + | -- 1. What changes, and why. | |
| 28 | + | INSERT OR IGNORE INTO price_changes (id, meter, old_cost_micros, new_cost_micros, markup_percent, reason, created_at) | |
| 29 | + | SELECT 'prc_standard_3_' || meter, meter, cost_micros, | |
| 30 | + | CASE meter | |
| 31 | + | WHEN 'sandbox_base_second' THEN MAX(cost_micros, 22.6825, | |
| 32 | + | COALESCE((SELECT MAX(v.cost_micros) FROM price_versions v WHERE v.meter = prices.meter AND v.applied_at IS NULL), 0)) | |
| 33 | + | ELSE MAX(cost_micros, | |
| 34 | + | COALESCE((SELECT MAX(v.cost_micros) FROM price_versions v WHERE v.meter = prices.meter AND v.applied_at IS NULL), 0)) + 10.56 | |
| 35 | + | END, | |
| 36 | + | markup_percent, | |
| 37 | + | 'Higher: the standard sandbox is now 2 vCPU, 8 GiB and 16 GB, and Cloudflare bills its memory and disk for every second it runs', | |
| 38 | + | strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | |
| 39 | + | FROM prices WHERE meter IN ('sandbox_base_second', 'sandbox_second', 'build_second'); | |
| 40 | + | ||
| 41 | + | -- 2. The price book, now. | |
| 42 | + | UPDATE prices | |
| 43 | + | SET cost_micros = (SELECT c.new_cost_micros FROM price_changes c WHERE c.id = 'prc_standard_3_' || prices.meter), | |
| 44 | + | updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | |
| 45 | + | WHERE meter IN ('sandbox_base_second', 'sandbox_second', 'build_second') | |
| 46 | + | AND EXISTS (SELECT 1 FROM price_changes c WHERE c.id = 'prc_standard_3_' || prices.meter); | |
| 47 | + | ||
| 48 | + | -- 3. Rises waiting for their date are replaced. | |
| 49 | + | UPDATE price_proposals SET status = 'superseded' | |
| 50 | + | WHERE version_id IN (SELECT id FROM price_versions | |
| 51 | + | WHERE meter IN ('sandbox_base_second', 'sandbox_second', 'build_second') AND applied_at IS NULL); | |
| 52 | + | ||
| 53 | + | DELETE FROM price_versions | |
| 54 | + | WHERE meter IN ('sandbox_base_second', 'sandbox_second', 'build_second') AND applied_at IS NULL; | |
| 55 | + | ||
| 56 | + | -- 4. A version in force from now, so the charges after it are explained by | |
| 57 | + | -- it. | |
| 58 | + | INSERT OR IGNORE INTO price_versions (id, meter, version, cost_micros, markup_percent, effective_at, reason, created_by, created_at, applied_at) | |
| 59 | + | SELECT 'pv_' || p.meter || '_' || (COALESCE((SELECT MAX(v.version) FROM price_versions v WHERE v.meter = p.meter), 0) + 1), | |
| 60 | + | p.meter, | |
| 61 | + | COALESCE((SELECT MAX(v.version) FROM price_versions v WHERE v.meter = p.meter), 0) + 1, | |
| 62 | + | p.cost_micros, p.markup_percent, | |
| 63 | + | strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), | |
| 64 | + | 'The standard sandbox is now 2 vCPU, 8 GiB and 16 GB', 'migration', | |
| 65 | + | strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), strftime('%Y-%m-%dT%H:%M:%SZ', 'now') | |
| 66 | + | FROM prices p WHERE p.meter IN ('sandbox_base_second', 'sandbox_second', 'build_second'); |
| 854 | 854 | ||
| 855 | 855 | #[test] | |
| 856 | 856 | fn every_build_second_is_metered_at_cost_plus_the_margin() { | |
| 857 | − | // A 5-minute build at 16 millionths a second costs g1t 4,800, and | |
| 857 | + | // A 5-minute build at 26 millionths a second costs g1t 7,800, and | |
| 858 | 858 | // is charged at cost plus 20%, from the first second: there are no | |
| 859 | 859 | // included build minutes, only the plan's included usage. | |
| 860 | 860 | let cost = 300 * costs::MICROS_PER_BUILD_SECOND; | |
| 861 | − | assert_eq!(cost, 4_800); | |
| 862 | − | assert_eq!(crate::credits::with_margin(cost, 20), 5_760); | |
| 861 | + | assert_eq!(cost, 7_800); | |
| 862 | + | assert_eq!(crate::credits::with_margin(cost, 20), 9_360); | |
| 863 | 863 | } | |
| 864 | 864 | ||
| 865 | 865 | #[test] |
| 187 | 187 | memory_byte_seconds: f64, | |
| 188 | 188 | } | |
| 189 | 189 | ||
| 190 | − | /// g1t's standard sandbox (`STANDARD_INSTANCE` in the contracts): a custom | |
| 191 | − | /// Containers instance type of 1 vCPU, 4 GiB and 20 GB of disk. | |
| 192 | − | const SANDBOX_GIB: f64 = 4.0; | |
| 193 | − | const SANDBOX_DISK_GB: f64 = 20.0; | |
| 190 | + | /// g1t's standard sandbox (`STANDARD_INSTANCE` in the contracts): | |
| 191 | + | /// Containers' standard-3, 2 vCPU, 8 GiB and 16 GB of disk. Memory and disk | |
| 192 | + | /// are billed as provisioned for every second it runs, so they are in the | |
| 193 | + | /// base; CPU only as used. | |
| 194 | + | const SANDBOX_GIB: f64 = 8.0; | |
| 195 | + | const SANDBOX_DISK_GB: f64 = 16.0; | |
| 194 | 196 | const GIB: f64 = 1024.0 * 1024.0 * 1024.0; | |
| 195 | 197 | /// The Durable Object behind each container is billed for as long as the | |
| 196 | 198 | /// container runs, at 128 MB. | |
| ⋯ | |||
| 563 | 565 | #[test] | |
| 564 | 566 | fn a_sandbox_second_is_its_memory_and_disk_and_the_cpu_it_uses() { | |
| 565 | 567 | // An hour of sandboxes that kept a fifth of a vCPU busy. | |
| 566 | − | let usage = ContainerUsage { cpu_seconds: 720.0, memory_byte_seconds: 3600.0 * 4.0 * GIB }; | |
| 568 | + | let usage = ContainerUsage { cpu_seconds: 720.0, memory_byte_seconds: 3600.0 * 8.0 * GIB }; | |
| 567 | 569 | let micros = | |
| 568 | 570 | sandbox_second_micros(usage, LIST_MEMORY_GIB_SECOND, LIST_DISK_GB_SECOND, LIST_VCPU_SECOND, LIST_DO_GB_SECOND).unwrap(); | |
| 569 | − | // 4 x 2.5 + 20 x 0.07 + 0.125 x 12.5 + 0.2 x 20 = 16.9625 | |
| 570 | − | assert!((micros - 16.9625).abs() < 1e-9, "{micros}"); | |
| 571 | − | // The Durable Object adds about 14% to the second it left out. | |
| 572 | − | assert!((sandbox_base_micros(LIST_MEMORY_GIB_SECOND, LIST_DISK_GB_SECOND, LIST_DO_GB_SECOND) - 12.9625).abs() < 1e-9); | |
| 571 | + | // 8 x 2.5 + 16 x 0.07 + 0.125 x 12.5 + 0.2 x 20 = 26.6825 | |
| 572 | + | assert!((micros - 26.6825).abs() < 1e-9, "{micros}"); | |
| 573 | + | // Memory, disk and the Durable Object, billed for every second. | |
| 574 | + | let base = sandbox_base_micros(LIST_MEMORY_GIB_SECOND, LIST_DISK_GB_SECOND, LIST_DO_GB_SECOND); | |
| 575 | + | assert!((base - 22.6825).abs() < 1e-9); | |
| 576 | + | // An hour of sandboxes that used no CPU still costs their base. | |
| 577 | + | let idle = ContainerUsage { cpu_seconds: 0.0, memory_byte_seconds: 3600.0 * 8.0 * GIB }; | |
| 578 | + | let idle = sandbox_second_micros(idle, LIST_MEMORY_GIB_SECOND, LIST_DISK_GB_SECOND, LIST_VCPU_SECOND, LIST_DO_GB_SECOND).unwrap(); | |
| 579 | + | assert!((idle - base).abs() < 1e-9); | |
| 573 | 580 | // Too little use to say anything. | |
| 574 | 581 | assert!(sandbox_second_micros(ContainerUsage { cpu_seconds: 1.0, memory_byte_seconds: GIB }, 1.0, 1.0, 1.0, 1.0).is_none()); | |
| 575 | 582 | } | |
| ⋯ | |||
| 578 | 585 | fn a_run_that_reports_its_cpu_is_priced_on_it() { | |
| 579 | 586 | let base = sandbox_base_micros(LIST_MEMORY_GIB_SECOND, LIST_DISK_GB_SECOND, LIST_DO_GB_SECOND); | |
| 580 | 587 | let vcpu = LIST_VCPU_SECOND * MICROS_PER_DOLLAR as f64; | |
| 581 | − | // A 10-minute cargo build that kept half its vCPU busy throughout. | |
| 588 | + | // A 10-minute cargo build that kept half a vCPU busy throughout. | |
| 582 | 589 | let heavy = run_cost(600, 300.0, base, vcpu); | |
| 583 | − | assert!((heavy - (600.0 * 12.9625 + 300.0 * 20.0)).abs() < 1e-6); | |
| 590 | + | assert!((heavy - (600.0 * 22.6825 + 300.0 * 20.0)).abs() < 1e-6); | |
| 591 | + | // Ten idle minutes cost the memory, disk and Durable Object that | |
| 592 | + | // Cloudflare bills for all of them, and are charged that plus the | |
| 593 | + | // margin, never less. | |
| 594 | + | let idle = run_cost(600, 0.0, base, vcpu); | |
| 595 | + | let provisioned = 600.0 * (8.0 * 2.5 + 16.0 * 0.07 + 0.125 * 12.5); | |
| 596 | + | assert!((idle - provisioned).abs() < 1e-6, "{idle}"); | |
| 597 | + | let charged = crate::credits::with_margin(idle.ceil() as i64, 20); | |
| 598 | + | assert!(charged as f64 >= provisioned * 1.2, "{charged}"); | |
| 584 | 599 | // The same ten minutes, mostly idle, costs less. | |
| 585 | 600 | let light = run_cost(600, 30.0, base, vcpu); | |
| 586 | 601 | assert!(light < heavy); | |
| ⋯ | |||
| 590 | 605 | assert_eq!(run_cost(0, -1.0, base, vcpu), 0.0); | |
| 591 | 606 | // A larger machine's base: its memory and disk, not its CPU. | |
| 592 | 607 | assert!((base_scale(SANDBOX_GIB, SANDBOX_DISK_GB) - 1.0).abs() < 1e-12); | |
| 593 | − | assert!((base_scale(12.0, 20.0) - 2.54).abs() < 0.01, "{}", base_scale(12.0, 20.0)); | |
| 594 | − | assert!((base_scale(8.0, 20.0) - 1.77).abs() < 0.01, "{}", base_scale(8.0, 20.0)); | |
| 595 | − | // Each machine in the contracts, priced from its own memory and disk. | |
| 608 | + | assert!((base_scale(12.0, 20.0) - 1.45).abs() < 0.01, "{}", base_scale(12.0, 20.0)); | |
| 609 | + | // Each machine in the contracts, priced from its own memory and | |
| 610 | + | // disk. Its price scale is at least its base's, so a job that does | |
| 611 | + | // not report its CPU (charged the average sandbox second, which is | |
| 612 | + | // never under the base, times the price scale) still pays for the | |
| 613 | + | // memory and disk it held, idle or not. | |
| 596 | 614 | for instance in g1t_contracts::actions::INSTANCE_TYPES { | |
| 597 | 615 | let scale = base_scale(instance.memory_gib, instance.disk_gb); | |
| 598 | − | assert!(scale >= 1.0 && scale < instance.price_scale.max(1.0) + 1e-9, "{}: {scale}", instance.label); | |
| 616 | + | assert!(scale >= 1.0 - 1e-12 && scale <= instance.price_scale + 1e-9, "{}: {scale}", instance.label); | |
| 599 | 617 | } | |
| 618 | + | let standard = g1t_contracts::actions::STANDARD_INSTANCE; | |
| 619 | + | assert_eq!((standard.memory_gib, standard.disk_gb), (SANDBOX_GIB, SANDBOX_DISK_GB)); | |
| 600 | 620 | } | |
| 601 | 621 | ||
| 602 | 622 | #[test] | |
| 228 | 228 | const gate = new ComputeGate(billing({}).binding, memory(), quiet); | |
| 229 | 229 | assert.equal(await gate.microsPerSecond(), 20); | |
| 230 | 230 | const down = new ComputeGate(billing({ fail: new Set(["prices"]) }).binding, memory(), quiet); | |
| 231 | − | assert.equal(await down.microsPerSecond(), 33); | |
| 231 | + | assert.equal(await down.microsPerSecond(), 63); | |
| 232 | 232 | }); | |
| 233 | 233 | ||
| 234 | 234 | // ---- Caps ------------------------------------------------------------------------------- |
| 68 | 68 | export interface RunnerEnv { | |
| 69 | 69 | SANDBOX: DurableObjectNamespace<AttemptSandbox>; | |
| 70 | 70 | /** | |
| 71 | − | * Larger machines for workflow jobs that ask for one with `runs-on` | |
| 72 | − | * (`g1t-2core`, `g1t-4core`): the same image on a larger instance type. | |
| 71 | + | * Workflow jobs that ask with `runs-on` for `g1t-2core` (the standard | |
| 72 | + | * machine, kept as a name so the label keeps working) or `g1t-4core` (a | |
| 73 | + | * larger instance type): the same image, in classes of their own. | |
| 73 | 74 | */ | |
| 74 | 75 | SANDBOX_2CORE?: DurableObjectNamespace<Sandbox2Core>; | |
| 75 | 76 | SANDBOX_4CORE?: DurableObjectNamespace<Sandbox4Core>; |
| 50 | 50 | type Size = { vcpu: number; memory_mib: number; disk_mb: number }; | |
| 51 | 51 | // wrangler.jsonc's comments are whole lines. | |
| 52 | 52 | const text = readFileSync(new URL("../wrangler.jsonc", import.meta.url), "utf8").replace(/^\s*\/\/.*$/gm, ""); | |
| 53 | − | const config = JSON.parse(text) as { containers: { class_name: string; instance_type: string | Size }[] }; | |
| 54 | − | // The named Cloudflare instance types a machine may be. | |
| 55 | − | const named: Record<string, Size> = { "standard-4": { vcpu: 4, memory_mib: 12 * 1024, disk_mb: 20_000 } }; | |
| 53 | + | const config = JSON.parse(text) as { containers: { class_name: string; instance_type: unknown }[] }; | |
| 54 | + | // The named Cloudflare instance types a machine may be, at the sizes | |
| 55 | + | // Cloudflare gives them. No custom instance types. | |
| 56 | + | const named: Record<string, Size> = { | |
| 57 | + | "standard-3": { vcpu: 2, memory_mib: 8 * 1024, disk_mb: 16_000 }, | |
| 58 | + | "standard-4": { vcpu: 4, memory_mib: 12 * 1024, disk_mb: 20_000 }, | |
| 59 | + | }; | |
| 56 | 60 | const classes = Object.keys(SANDBOX_BINDINGS); | |
| 57 | 61 | assert.equal(INSTANCE_TYPES.length, classes.length); | |
| 58 | 62 | INSTANCE_TYPES.forEach((instance, i) => { | |
| 59 | 63 | const type = config.containers.find((c) => c.class_name === classes[i])?.instance_type; | |
| 60 | − | const size = typeof type === "string" ? named[type] : type; | |
| 64 | + | const size = typeof type === "string" ? named[type] : undefined; | |
| 61 | 65 | assert.ok(size, `${classes[i]}: ${JSON.stringify(type)}`); | |
| 62 | − | assert.equal(typeof type === "string" ? type : "custom", instance.container, instance.label); | |
| 66 | + | assert.equal(type, instance.container, instance.label); | |
| 63 | 67 | assert.deepEqual( | |
| 64 | 68 | { vcpu: size.vcpu, memoryGib: size.memory_mib / 1024, diskGb: size.disk_mb / 1000 }, | |
| 65 | 69 | { vcpu: instance.vcpu, memoryGib: instance.memoryGib, diskGb: instance.diskGb }, |
| 5 | 5 | "compatibility_date": "2026-09-26", | |
| 6 | 6 | "main": "./src/index.ts", | |
| 7 | 7 | "workers_dev": false, | |
| 8 | − | // One image (the base plus the runner binary), on three machine sizes, | |
| 9 | − | // each with the 20 GB of disk Containers allow at most. The standard and | |
| 10 | − | // g1t-2core machines are custom instance types (1 vCPU is the smallest a | |
| 11 | − | // custom type may have, with at least 3 GiB of memory per vCPU), since | |
| 12 | − | // the named ones that size come with 8 and 16 GB. crates/contracts | |
| 8 | + | // One image (the base plus the runner binary), on two named instance | |
| 9 | + | // types: standard-3 (2 vCPU, 8 GiB, 16 GB of disk) for the standard | |
| 10 | + | // machine and g1t-2core, which is kept as a name of it so workflows that | |
| 11 | + | // ask for it keep working, and standard-4 (4 vCPU, 12 GiB, 20 GB) for | |
| 12 | + | // g1t-4core. At these max_instances, 40 sandboxes at once hold 100 vCPU, | |
| 13 | + | // 360 GiB and 680 GB, well inside the account's limits for live | |
| 14 | + | // instances (1,500 vCPU, 6 TiB of memory). crates/contracts | |
| 13 | 15 | // (INSTANCE_TYPES) and services/billing (keeper.rs) say the same sizes. | |
| 14 | 16 | // scripts/deploy.mjs deploys with each image set to the reference it | |
| 15 | 17 | // built and pushed ("The runner's images" in | |
| ⋯ | |||
| 20 | 22 | "class_name": "AttemptSandbox", | |
| 21 | 23 | "image": "./Dockerfile", | |
| 22 | 24 | // Workflow jobs with `runs-on: ubuntu-latest` (and every other | |
| 23 | − | // hosted label), and all other work: 1 vCPU, 4 GiB, 20 GB. | |
| 24 | − | "instance_type": { "vcpu": 1, "memory_mib": 4096, "disk_mb": 20000 }, | |
| 25 | + | // hosted label), and all other work: 2 vCPU, 8 GiB, 16 GB. | |
| 26 | + | "instance_type": "standard-3", | |
| 25 | 27 | "max_instances": 20, | |
| 26 | 28 | // A deploy replaces sandboxes. Ones that are busy are given this | |
| 27 | 29 | // long, in seconds, to finish first, so shipping g1t does not | |
| ⋯ | |||
| 29 | 31 | "rollout_active_grace_period": 7200 | |
| 30 | 32 | }, | |
| 31 | 33 | { | |
| 32 | − | // Workflow jobs with `runs-on: g1t-2core`: 2 vCPU, 8 GiB, 20 GB. | |
| 34 | + | // Workflow jobs with `runs-on: g1t-2core`: the standard machine. | |
| 33 | 35 | "class_name": "Sandbox2Core", | |
| 34 | 36 | "image": "./Dockerfile", | |
| 35 | − | "instance_type": { "vcpu": 2, "memory_mib": 8192, "disk_mb": 20000 }, | |
| 37 | + | "instance_type": "standard-3", | |
| 36 | 38 | "max_instances": 10, | |
| 37 | 39 | "rollout_active_grace_period": 7200 | |
| 38 | 40 | }, | |
Comments
Sign in to comment.