Skip to content

Commit 694a22e

Browse files
committed
Fix exception-threshold-bump and restore lost headroom
### What does this PR do? Fix `fetch_main_headroom()` in tasks/static_quality_gates/metrics.py so `quality-gates.exception-threshold-bump` actually restores headroom instead of reproducing `main`'s own failure. Widen its lookback window from 1 day to 7 so it doesn't miss `main`'s last report when no commit lands over a weekend. Floor the computed headroom at `BUFFER_SIZE` instead of 0 when main is already over its own limit, since `max - current` is then negative and previously clamped straight to 0. ### Motivation `static_quality_gates` has been failing by a handful of KB on at least 3 [unrelated PRs last week](https://dd.slack.com/archives/C08SK4B0FK8/p1786035443536819) and on `main` itself: `agent_rpm_arm64` and `agent_suse_arm64` both exceeded their 735.07 MiB limit after #52372 and #54319 landed. Main's own headroom was already at or below 0 for these gates, so `exception-threshold-bump`'s formula, `new_threshold = PR_current + main_headroom`, reduced to just PR_current: it silently produced a threshold still below main's actual usage, which would not even have unblocked `main`. Separately, `fetch_main_headroom()`'s 1-day lookback returned no data at all across the weekend gap between Friday's merges and Monday's incident, since main only reports this metric when a commit lands. ### Describe how you validated your changes Added `test_headroom_floored_when_main_over_its_own_limit` (renamed from the now-inverted `test_headroom_never_negative`) asserting the floor applies when main is over budget. Reran `quality-gates.exception-threshold-bump` twice independently: both runs produced identical thresholds (`agent_rpm_arm64/agent_suse_arm64` -> 736.03 MiB, `iot_agent_deb/rpm/suse_amd64` -> 47.33 MiB), confirming the fix is deterministic. ### Additional Notes This restores headroom but does not address recurring growths that disregard earlier efforts.
1 parent b1e8c38 commit 694a22e

3 files changed

Lines changed: 35 additions & 30 deletions

File tree

tasks/static_quality_gates/metrics.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ def fetch_main_headroom(failing_gates: list[str]) -> dict[str, dict[str, int]]:
109109
f"avg:datadog.agent.static_quality_gate.{m}{{git_ref:main AND ({gate_filter})}} by {{gate_name}}"
110110
for m in metric_map
111111
)
112-
result = query_metrics(queries, from_time="now-1d", to_time="now")
112+
# 1-day window can miss main's last report (e.g. no commits over a weekend)
113+
result = query_metrics(queries, from_time="now-7d", to_time="now")
113114

114115
for series in result:
115116
gate_name = _extract_gate_name_from_scope(series.get("scope", ""))
@@ -127,13 +128,16 @@ def fetch_main_headroom(failing_gates: list[str]) -> dict[str, dict[str, int]]:
127128
main_metrics[gate_name][key] = int(latest_value)
128129
break
129130

131+
from tasks.static_quality_gates.thresholds import BUFFER_SIZE # lazy: avoids a circular import
132+
130133
headroom: dict[str, dict[str, int]] = {}
131134
for gate_name, metrics in main_metrics.items():
132135
disk_headroom = metrics.get("max_disk", 0) - metrics.get("current_disk", 0)
133136
wire_headroom = metrics.get("max_wire", 0) - metrics.get("current_wire", 0)
137+
# Floor at BUFFER_SIZE, not 0: main over its own limit shouldn't clamp headroom to 0
134138
headroom[gate_name] = {
135-
"disk_headroom": max(0, disk_headroom),
136-
"wire_headroom": max(0, wire_headroom),
139+
"disk_headroom": max(BUFFER_SIZE, disk_headroom),
140+
"wire_headroom": max(BUFFER_SIZE, wire_headroom),
137141
}
138142

139143
return headroom

tasks/unit_tests/static_quality_gates/metrics_tests.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
fetch_main_headroom,
99
fetch_pr_metrics,
1010
)
11+
from tasks.static_quality_gates.thresholds import BUFFER_SIZE
1112

1213

1314
class MockPoint:
@@ -248,8 +249,8 @@ def test_calculates_headroom_correctly(self, mock_query):
248249
self.assertEqual(headroom["wire_headroom"], 25 * 1024 * 1024)
249250

250251
@patch("tasks.static_quality_gates.metrics.query_metrics")
251-
def test_headroom_never_negative(self, mock_query):
252-
"""Headroom should never be negative (clamped to 0)."""
252+
def test_headroom_floored_when_main_over_its_own_limit(self, mock_query):
253+
"""Headroom should floor at BUFFER_SIZE, not 0-, when main is over its own limit."""
253254
# Single API call with current > max
254255
mock_query.return_value = [
255256
{
@@ -277,8 +278,8 @@ def test_headroom_never_negative(self, mock_query):
277278
result = fetch_main_headroom(["static_quality_gate_agent_deb_amd64"])
278279

279280
headroom = result["static_quality_gate_agent_deb_amd64"]
280-
# disk_headroom = max(0, 150 - 200) = 0
281-
self.assertEqual(headroom["disk_headroom"], 0)
281+
# disk_headroom = max(BUFFER_SIZE, 150 - 200) = BUFFER_SIZE
282+
self.assertEqual(headroom["disk_headroom"], BUFFER_SIZE)
282283

283284
def test_returns_empty_for_no_gates(self):
284285
"""Should return empty dict when no gates provided."""

test/static/static_quality_gates.yml

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
11
static_quality_gate_agent_deb_amd64:
2-
max_on_disk_size: 764.01 MiB
3-
max_on_wire_size: 182.72 MiB
2+
max_on_disk_size: 764.02 MiB
3+
max_on_wire_size: 182.75 MiB
44
static_quality_gate_agent_deb_amd64_fips:
55
max_on_disk_size: 715.32 MiB
66
max_on_wire_size: 176.9 MiB
77
static_quality_gate_agent_heroku_amd64:
88
max_on_disk_size: 318.88 MiB
99
max_on_wire_size: 81.49 MiB
1010
static_quality_gate_agent_msi:
11-
max_on_disk_size: 660.49 MiB
12-
max_on_wire_size: 159.03 MiB
11+
max_on_disk_size: 660.5 MiB
12+
max_on_wire_size: 159.04 MiB
1313
static_quality_gate_agent_rpm_amd64:
14-
max_on_disk_size: 763.98 MiB
14+
max_on_disk_size: 763.99 MiB
1515
max_on_wire_size: 186.49 MiB
1616
static_quality_gate_agent_rpm_amd64_fips:
1717
max_on_disk_size: 715.32 MiB
1818
max_on_wire_size: 177.67 MiB
1919
static_quality_gate_agent_rpm_arm64:
20-
max_on_disk_size: 735.07 MiB
21-
max_on_wire_size: 167.02 MiB
20+
max_on_disk_size: 736.03 MiB
21+
max_on_wire_size: 167.01 MiB
2222
static_quality_gate_agent_rpm_arm64_fips:
2323
max_on_disk_size: 693.29 MiB
24-
max_on_wire_size: 159.34 MiB
24+
max_on_wire_size: 159.32 MiB
2525
static_quality_gate_agent_suse_amd64:
26-
max_on_disk_size: 763.98 MiB
26+
max_on_disk_size: 763.99 MiB
2727
max_on_wire_size: 186.46 MiB
2828
static_quality_gate_agent_suse_amd64_fips:
2929
max_on_disk_size: 715.32 MiB
3030
max_on_wire_size: 177.88 MiB
3131
static_quality_gate_agent_suse_arm64:
32-
max_on_disk_size: 735.07 MiB
33-
max_on_wire_size: 166.98 MiB
32+
max_on_disk_size: 736.03 MiB
33+
max_on_wire_size: 167.01 MiB
3434
static_quality_gate_agent_suse_arm64_fips:
3535
max_on_disk_size: 693.29 MiB
36-
max_on_wire_size: 159.32 MiB
36+
max_on_wire_size: 159.34 MiB
3737
static_quality_gate_docker_agent_amd64:
38-
max_on_disk_size: 819.6 MiB
39-
max_on_wire_size: 277.77 MiB
38+
max_on_disk_size: 819.61 MiB
39+
max_on_wire_size: 277.78 MiB
4040
static_quality_gate_docker_agent_arm64:
41-
max_on_disk_size: 820.34 MiB
42-
max_on_wire_size: 265.7 MiB
41+
max_on_disk_size: 820.35 MiB
42+
max_on_wire_size: 265.72 MiB
4343
static_quality_gate_docker_agent_jmx_amd64:
44-
max_on_disk_size: 1010.36 MiB
44+
max_on_disk_size: 1010.37 MiB
4545
max_on_wire_size: 346.39 MiB
4646
static_quality_gate_docker_agent_jmx_arm64:
47-
max_on_disk_size: 1000.02 MiB
48-
max_on_wire_size: 330.31 MiB
47+
max_on_disk_size: 1000.03 MiB
48+
max_on_wire_size: 330.32 MiB
4949
static_quality_gate_docker_cluster_agent_amd64:
5050
max_on_disk_size: 211.25 MiB
51-
max_on_wire_size: 74.4 MiB
51+
max_on_wire_size: 74.45 MiB
5252
static_quality_gate_docker_cluster_agent_arm64:
5353
max_on_disk_size: 223.75 MiB
5454
max_on_wire_size: 69.5 MiB
@@ -83,7 +83,7 @@ static_quality_gate_dogstatsd_suse_amd64:
8383
max_on_disk_size: 31.15 MiB
8484
max_on_wire_size: 8.94 MiB
8585
static_quality_gate_iot_agent_deb_amd64:
86-
max_on_disk_size: 46.38 MiB
86+
max_on_disk_size: 47.33 MiB
8787
max_on_wire_size: 13.64 MiB
8888
static_quality_gate_iot_agent_deb_arm64:
8989
max_on_disk_size: 43.72 MiB
@@ -92,8 +92,8 @@ static_quality_gate_iot_agent_deb_armhf:
9292
max_on_disk_size: 43.96 MiB
9393
max_on_wire_size: 12.19 MiB
9494
static_quality_gate_iot_agent_rpm_amd64:
95-
max_on_disk_size: 46.38 MiB
95+
max_on_disk_size: 47.33 MiB
9696
max_on_wire_size: 13.65 MiB
9797
static_quality_gate_iot_agent_suse_amd64:
98-
max_on_disk_size: 46.38 MiB
98+
max_on_disk_size: 47.33 MiB
9999
max_on_wire_size: 13.64 MiB

0 commit comments

Comments
 (0)