Commit

Margin alerts measure what is sold, and say dollars when a percentage would mislead

- 'Costs more than it pays' compares a workspace's cost with what its usage was priced at (new workspace_costs.value_micros), not the cash it paid: a trial or a gift paying for usage was read as prices below cost (syntaqx: $7.20 cost, $9.14 at price, $6.02 paid). It is no longer emailed when new, as the runbook said. - The all-of-g1t alert leaves comped workspaces' share out, and while under $1 a day comes in it says the money ('took in $X against $Y') instead of a percentage like -8239.7%.

syntaqxcommitted Parent3a2c3ceBrowse files
3 files+85−130/3 viewed
+2−2
191191 | Alert | Raised when | First steps |
192192 | --- | --- | --- |
193193 | Margin under the floor | A product's value against cost under `margin_floor_percent` (10%) for `alert_days` (3) days running, each with at least `min_daily_cost` | Open the product on Costs & margin. Cost up? Check proposals (approve a rise; it waits out the notice). Value down? A mapping or `revenue_map` may have moved. |
194−| All of g1t under the floor | The same for money in against every cost | Look at which products moved; check `platform` (it has no revenue of its own and grows with traffic). |
194+| All of g1t under the floor | The same for money in against every cost, comped workspaces' share left out (their spend is a budget, watched in budget.rs). While less than $1 a day comes in, it says the dollars, not a percentage | Look at which products moved; check `platform` (it has no revenue of its own and grows with traffic). Before launch, with little paid usage, expect it. |
195195 | Leak | Drift of kind leak | Map the meter, or decide it is overhead. |
196196 | Drift | Count drift | See Drift above. Cloudflare's definitions change in beta: ask them in writing ([ARTIFACTS.md](ARTIFACTS.md), §7). |
197−| Costs more than it pays | A workspace's shared cost over 30 days above its revenue × `anomaly_factor`, at least `anomaly_floor`; not g1t's own | Shown on Reach out as "Costs more than it pays". Abuse (Abuse & fraud page) or a gap in pricing. Not emailed. |
197+| Costs more than it pays | A workspace's shared cost over 30 days above what its usage was priced at (`value_micros`, whoever paid: card, trial, gift or included usage) × `anomaly_factor`, at least `anomaly_floor`; not comped workspaces | Shown on Reach out as "Costs more than it pays": its usage is priced below what it costs. Abuse (Abuse & fraud page) or a gap in pricing. Not emailed. A trial or gift paying for usage does not raise it. |
198198
199199 Alerts close on their own when the condition clears. Open ones are
200200 emailed again weekly. The red bar on every sudo page shows margin,
+5−0
1+-- What each workspace's usage was priced at (cost plus 20%), whoever paid
2+-- for it: the trial, a gift, the plan's included usage or a card. The
3+-- cost-over-revenue alert compares cost with this, so a giveaway is not
4+-- mistaken for a price below cost; revenue_micros stays the cash paid.
5+ALTER TABLE workspace_costs ADD COLUMN value_micros INTEGER NOT NULL DEFAULT 0;
+78−11
125125 pub workspace: String,
126126 pub bucket: String,
127127 pub cost: i64,
128+ /// What the workspace paid in cash.
128129 pub revenue: i64,
130+ /// What its usage was priced at, whoever paid for it.
131+ pub value: i64,
129132 }
130133
131134 fn micros(dollars: f64) -> i64 {
201204 let mut value_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
202205 let mut cost_by: BTreeMap<(String, String), Vec<(String, f64)>> = BTreeMap::new();
203206 let mut revenue: BTreeMap<(String, String, String), i64> = BTreeMap::new();
207+ let mut valued: BTreeMap<(String, String, String), i64> = BTreeMap::new();
204208 let mut active: BTreeMap<String, Vec<(String, f64)>> = BTreeMap::new();
205209 for u in usage {
206210 let bucket = bucket_of(&u.key);
211215 row.cash_micros += u.cash;
212216 value_by.entry(key.clone()).or_default().push((u.workspace.clone(), u.value as f64));
213217 cost_by.entry(key).or_default().push((u.workspace.clone(), u.cost as f64));
214− *revenue.entry((u.day.clone(), u.workspace.clone(), bucket)).or_default() += u.cash;
218+ *revenue.entry((u.day.clone(), u.workspace.clone(), bucket.clone())).or_default() += u.cash;
219+ *valued.entry((u.day.clone(), u.workspace.clone(), bucket)).or_default() += u.value;
215220 active.entry(u.day.clone()).or_default().push((u.workspace.clone(), u.value.max(u.cost) as f64));
216221 }
217222 // Each bucket's cost shared out: by Cloudflare's own count per
238243 .map(|(day, workspace, bucket)| WorkspaceDay {
239244 cost: shares.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
240245 revenue: revenue.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
246+ value: valued.get(&(day.clone(), workspace.clone(), bucket.clone())).copied().unwrap_or(0),
241247 day,
242248 workspace,
243249 bucket,
399405 /// Workspaces that cost g1t more than `factor` times what they paid, with
400406 /// at least `floor_micros` of cost: each (workspace, cost, revenue), the
401407 /// biggest gap first.
408+/// What the overall alert says: the money as money, and a percentage only
409+/// while there is enough coming in for one to mean something (a few cents
410+/// against dollars of cost reads as -8000%).
411+pub(crate) fn overall_detail(took: i64, spent: i64, days: usize, floor: f64, worst: f64) -> String {
412+ if took < 1_000_000 * days as i64 {
413+ return format!(
414+ "All of g1t, comped workspaces left out: took in {} against {} of Cloudflare's bill over {days} days.",
415+ dollars(took),
416+ dollars(spent)
417+ );
418+ }
419+ format!("All of g1t, comped workspaces left out: money in against Cloudflare's bill under {floor:.0}% for {days} days running, as low as {worst:.1}%.")
420+}
421+
402422 pub(crate) fn anomalies(rows: &[(String, i64, i64)], factor: f64, floor_micros: i64) -> Vec<(String, i64, i64)> {
403423 let mut out: Vec<(String, i64, i64)> = rows
404424 .iter()
770790 for w in chunk {
771791 statements.push(
772792 self.db
773− .prepare("INSERT OR REPLACE INTO workspace_costs (day, workspace, bucket, cost_micros, revenue_micros) VALUES (?, ?, ?, ?, ?)")
793+ .prepare("INSERT OR REPLACE INTO workspace_costs (day, workspace, bucket, cost_micros, revenue_micros, value_micros) VALUES (?, ?, ?, ?, ?, ?)")
774794 .bind(&[
775795 w.day.as_str().into(),
776796 w.workspace.as_str().into(),
777797 w.bucket.as_str().into(),
778798 (w.cost as f64).into(),
779799 (w.revenue as f64).into(),
800+ (w.value as f64).into(),
780801 ])?,
781802 );
782803 }
967988 ));
968989 }
969990 }
991+ // Comped workspaces' share is a budget g1t chose to spend, watched
992+ // on its own (budget.rs): not part of whether what is sold pays.
993+ for (day, cost) in self.comped_costs(&since, until).await? {
994+ if let Some(overall) = all.get_mut(&day) {
995+ overall.1 = (overall.1 - cost).max(0);
996+ }
997+ }
970998 let series: Vec<(String, i64, i64)> = all.into_iter().map(|(day, (revenue, cost))| (day, revenue, cost)).collect();
971999 if let Some((from, worst)) = breach(&series, floor, n, settings.min_daily_cost_micros) {
972− conditions.push((
973− "overall".into(),
974− "g1t".into(),
975− format!("All of g1t: money in against Cloudflare's bill under {floor:.0}% for {n} days running, as low as {worst:.1}%."),
976− from,
977− ));
1000+ let tail = &series[series.len().saturating_sub(n)..];
1001+ let (took, spent) = tail.iter().fold((0i64, 0i64), |(r, c), (_, revenue, cost)| (r + revenue, c + cost));
1002+ conditions.push(("overall".into(), "g1t".into(), overall_detail(took, spent, n, floor, worst), from));
9781003 }
9791004 for (d, detail) in drift {
9801005 let kind = if d.kind == DriftKind::Leak { "leak" } else { "drift" };
9851010 conditions.push((
9861011 "workspace".into(),
9871012 workspace.clone(),
988− format!("{workspace} cost g1t {} on Cloudflare over {ANOMALY_DAYS} days and paid {}.", dollars(cost), dollars(revenue)),
1013+ format!(
1014+ "{workspace} cost g1t {} on Cloudflare over {ANOMALY_DAYS} days, and its usage was priced at {}: its prices are below cost.",
1015+ dollars(cost),
1016+ dollars(revenue)
1017+ ),
9891018 day_before(until, ANOMALY_DAYS - 1),
9901019 ));
9911020 }
10271056 .run()
10281057 .await?;
10291058 kept.insert(id.clone());
1030− to_email.push(detail.clone());
1059+ // A workspace's is for Reach out, not the inbox.
1060+ if kind != "workspace" {
1061+ to_email.push(detail.clone());
1062+ }
10311063 kept.insert(format!("email:{id}"));
10321064 }
10331065 }
10611093 }
10621094
10631095 /// Workspaces costing g1t more than they pay over 30 days, not g1t's own.
1096+ /// Each day's cost shared out to comped workspaces.
1097+ async fn comped_costs(&self, since: &str, until: &str) -> Result<Vec<(String, i64)>> {
1098+ #[derive(Deserialize)]
1099+ struct Row {
1100+ day: String,
1101+ cost: Option<i64>,
1102+ }
1103+ Ok(self
1104+ .db
1105+ .prepare(format!(
1106+ "SELECT day, SUM(cost_micros) AS cost FROM workspace_costs
1107+ WHERE day >= ?1 AND day <= ?2 AND workspace IN ({}) GROUP BY day",
1108+ crate::sales::INTERNAL_SQL
1109+ ))
1110+ .bind(&[since.into(), until.into()])?
1111+ .all()
1112+ .await?
1113+ .results::<Row>()?
1114+ .into_iter()
1115+ .map(|r| (r.day, r.cost.unwrap_or(0)))
1116+ .collect())
1117+ }
1118+
10641119 async fn workspace_anomalies(&self, until: &str, settings: &CostSettings) -> Result<Vec<(String, i64, i64)>> {
10651120 #[derive(Deserialize)]
10661121 struct Row {
10711126 let rows = self
10721127 .db
10731128 .prepare(format!(
1074− "SELECT workspace, SUM(cost_micros) AS cost, SUM(revenue_micros) AS revenue FROM workspace_costs
1129+ // Against what its usage was priced at, not the cash it
1130+ // paid: a trial or a gift paying for usage is not a price
1131+ // below cost.
1132+ "SELECT workspace, SUM(cost_micros) AS cost, SUM(value_micros) AS revenue FROM workspace_costs
10751133 WHERE day >= ?1 AND day <= ?2 AND workspace NOT IN ({}) GROUP BY workspace",
10761134 crate::sales::INTERNAL_SQL
10771135 ))
14101468 mod tests {
14111469 use super::*;
14121470
1471+ #[test]
1472+ fn the_overall_alert_says_dollars_while_little_comes_in() {
1473+ let small = overall_detail(90_000, 7_500_000, 3, 10.0, -8239.7);
1474+ assert!(small.contains("took in $0.09 against $7.50"), "{small}");
1475+ assert!(!small.contains('%'), "{small}");
1476+ let real = overall_detail(30_000_000, 40_000_000, 3, 10.0, -33.3);
1477+ assert!(real.contains("as low as -33.3%"), "{real}");
1478+ }
1479+
14131480 fn rule(product: &str, meter: &str, bucket: &str, own: Option<&str>) -> Rule {
14141481 Rule { product: product.into(), meter: meter.into(), bucket: bucket.into(), price_meter: None, own_meter: own.map(Into::into), drift_percent: 10.0 }
14151482 }