Compare changes
Choose two branches to see what one has that the other does not, then open a pull request for it.
4 commits
- Usage per day, not spend, while nothing is chargedChase Pierce489d75b
- A comped workspace's usage chart and breakdowns show what runs used at cost, as the free tier's doChase Pierce8e6ab57
- A comped workspace's Usage shows what its runs used at cost, not $0.00 spent and a $0.000 averageChase Pierce5861afe
- A clone answered from the pack cache is served before the free operation cap: it is not an operationChase Pierce77a168a
3 files+30−130/3 viewed
| 191 | 191 | const days = Math.max(1, Math.ceil((Date.now() - new Date(since).getTime()) / 86_400_000)); | |
| 192 | 192 | // While g1t is free nothing is charged, so usage is measured at cost | |
| 193 | 193 | // and credit is not drawn down. | |
| 194 | − | const total = usage.free ? usage.usedMicros : usage.spentMicros; | |
| 195 | − | const perDay = usage.free ? 0 : total / days; | |
| 194 | + | // Free or comped, nothing is charged: what the runs used, at cost, is | |
| 195 | + | // what there is to show. | |
| 196 | + | const atCost = usage.free || comped; | |
| 197 | + | const total = atCost ? usage.usedMicros : usage.spentMicros; | |
| 198 | + | const perDay = atCost ? 0 : total / days; | |
| 196 | 199 | const runway = perDay > 0 ? Math.floor(account.balanceMicros / perDay) : null; | |
| 197 | 200 | return ( | |
| 198 | 201 | <div className="space-y-8"> | |
| 214 | 217 | </div> | |
| 215 | 218 | ||
| 216 | 219 | <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> | |
| 217 | − | {usage.free ? ( | |
| 220 | + | {atCost ? ( | |
| 218 | 221 | <Stat | |
| 219 | 222 | label="Used" | |
| 220 | 223 | value={dollars(total)} | |
| 221 | − | note="At cost. Nothing is charged while g1t is being built out." | |
| 224 | + | note={comped ? "At cost. The workspace is comped: nothing is charged." : "At cost. Nothing is charged while g1t is being built out."} | |
| 222 | 225 | /> | |
| 223 | 226 | ) : ( | |
| 224 | 227 | <Stat | |
| 270 | 273 | ||
| 271 | 274 | <section className="rounded-2xl bg-surface p-5 ring-1 ring-line"> | |
| 272 | 275 | <div className="flex flex-wrap items-baseline justify-between gap-3"> | |
| 273 | − | <h3 className="text-sm font-medium">{usage.free ? "Usage per day" : "Spend per day"}</h3> | |
| 276 | + | <h3 className="text-sm font-medium">{atCost ? "Usage per day" : "Spend per day"}</h3> | |
| 274 | 277 | <ul className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted"> | |
| 275 | 278 | {byTask.map((slice) => ( | |
| 276 | 279 | <li key={slice.key} className="flex items-center gap-1.5"> |
| 382 | 382 | micros: Option<i64>, | |
| 383 | 383 | runs: Option<u32>, | |
| 384 | 384 | } | |
| 385 | − | // While nothing is charged, what was used is what there is to show. | |
| 386 | − | let measure = if self.free { "COALESCE(cost_micros, 0)" } else { "-amount_micros" }; | |
| 385 | + | // While nothing is charged (g1t is free, or the workspace is | |
| 386 | + | // comped), what was used is what there is to show. | |
| 387 | + | #[derive(serde::Deserialize)] | |
| 388 | + | struct Comped { | |
| 389 | + | n: i64, | |
| 390 | + | } | |
| 391 | + | let comped = self | |
| 392 | + | .db | |
| 393 | + | .prepare("SELECT COUNT(*) AS n FROM billing_accounts WHERE kind = 'workspace' AND terms_kind = 'comped' AND substr(id, 4) = ?1") | |
| 394 | + | .bind(&[workspace.as_str().into()])? | |
| 395 | + | .first::<Comped>(None) | |
| 396 | + | .await? | |
| 397 | + | .is_some_and(|row| row.n > 0); | |
| 398 | + | let measure = if self.free || comped { "COALESCE(cost_micros, 0)" } else { "-amount_micros" }; | |
| 387 | 399 | let slices = |key: &str, limit: u32| { | |
| 388 | 400 | format!( | |
| 389 | 401 | "SELECT {key} AS key, SUM({measure}) AS micros, COUNT(*) AS runs FROM ledger |
| 1558 | 1558 | } | |
| 1559 | 1559 | }; | |
| 1560 | 1560 | timing.mark("kept"); | |
| 1561 | − | if let Some((response, status, message)) = limited? { | |
| 1562 | − | after.ended(status, Some(message.to_owned())); | |
| 1563 | − | after.spawn(env, ctx); | |
| 1564 | − | return Ok(response); | |
| 1565 | − | } | |
| 1561 | + | // A kept pack first: it never reaches the store, so it is never an | |
| 1562 | + | // operation, and a free workspace past its operation cap still gets | |
| 1563 | + | // it. (The other limits are a push's, and a pack is only a fetch.) | |
| 1566 | 1564 | if let Some(kept) = pack { | |
| 1567 | 1565 | timing.note("pack", "hit"); | |
| 1568 | − | // Never reached the store: never an operation. | |
| 1569 | 1566 | let sent = body.as_ref().map_or(0, |body| body.len() as u64); | |
| 1570 | 1567 | meters::record(pack_cache::HIT, &key, sent, kept.size); | |
| 1571 | 1568 | after.ended(200, None); | |
| 1572 | 1569 | after.spawn(env, ctx); | |
| 1573 | 1570 | return kept.response(); | |
| 1574 | 1571 | } | |
| 1572 | + | if let Some((response, status, message)) = limited? { | |
| 1573 | + | after.ended(status, Some(message.to_owned())); | |
| 1574 | + | after.spawn(env, ctx); | |
| 1575 | + | return Ok(response); | |
| 1576 | + | } | |
| 1575 | 1577 | if pack_key.is_some() { | |
| 1576 | 1578 | timing.note("pack", "miss"); | |
| 1577 | 1579 | } |