fix(relay-ops): make each reserve-watch sample one Cloud Logging read (#31619)

* fix(relay-ops): make each reserve-watch sample one Cloud Logging read

The project allows 60 ListLogEntries a minute shared by every watch and tool. Each watch sample
made four to six reads (two director runtime-metrics sub-windows, the ledger summaries, the
seat-behind-row lines, the cell's phone metrics, and since #31484 the 503 request log), so on
10-21 18:39Z twelve watches drew 429s until c21 and c22 tripped unverifiable.

A sample now sends its queries as one OR filter and splits the lines back client-side. The
per-instance 503 split counts each director's own logged /v1/assign 503s from the runtime
metrics it already reads, with the rest of the fleet's count as one unattributed instance,
instead of reading the request log.

* fix(relay-ops): judge unattributed 503s on their 3-minute sum against the whole fleet line

* docs(relay-ops): state that an instance that stops logging trips the unattributed rule
This commit is contained in:
Jinwoo Hong
2026-10-21 18:25:30 -04:00
committed by GitHub
parent 88aa3f27ce
commit 1b42c4ce5c
7 changed files with 525 additions and 151 deletions
@@ -0,0 +1,151 @@
{
"comment": "Production 2026-10-21 07:44-07:58Z, read-only: Cloud Run director 503s per minute (request_count, code 503) and every director runtime-metrics line, instance ids replaced. d5 stalled on its database 07:55-07:57.",
"fleet503PerMinute": {"2026-10-21T07:44":6,"2026-10-21T07:45":7,"2026-10-21T07:46":5,"2026-10-21T07:47":55,"2026-10-21T07:48":11,"2026-10-21T07:49":10,"2026-10-21T07:50":12,"2026-10-21T07:51":4,"2026-10-21T07:52":6,"2026-10-21T07:53":8,"2026-10-21T07:54":6,"2026-10-21T07:55":57,"2026-10-21T07:56":173,"2026-10-21T07:57":45},
"directorMetricsLines": [
{"timestamp":"2026-10-21T07:44:05.179655Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:44:08.062180Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-in-flight":1,"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":3}},
{"timestamp":"2026-10-21T07:44:13.822472Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"relay_assignment_row_busy":1}},
{"timestamp":"2026-10-21T07:44:14.159472Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:44:25.765825Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:44:35.181579Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:44:38.060162Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:44:43.823368Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:44:44.158999Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:44:55.766172Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:05.180654Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:45:08.060467Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:13.824498Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:14.159451Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:25.764958Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"placementRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:45:35.181560Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:38.062560Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:45:43.831975Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:44.160118Z","instance":"d4","placementRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:45:55.767508Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:05.181410Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:08.061944Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":3},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":3}},
{"timestamp":"2026-10-21T07:46:13.824904Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:14.160287Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:46:25.766494Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:35.180987Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:38.061343Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:43.824785Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:46:44.161722Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:46:55.768452Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:47:05.182855Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:47:08.066053Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:47:13.825495Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":3},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":3}},
{"timestamp":"2026-10-21T07:47:14.161928Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"relay_assignment_row_busy":1,"sticky-lane":1}},
{"timestamp":"2026-10-21T07:47:25.767488Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":4},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":4}},
{"timestamp":"2026-10-21T07:47:35.183676Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:47:38.064515Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:47:43.827753Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-in-flight":1,"host-rate-limited":1,"wait-timeout":9},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":11}},
{"timestamp":"2026-10-21T07:47:44.161502Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-in-flight":1,"wait-timeout":7},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":8}},
{"timestamp":"2026-10-21T07:47:55.769396Z","instance":"d5","stickyRejectionsByReasonDelta":{"wait-timeout":9},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":9}},
{"timestamp":"2026-10-21T07:48:05.184152Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1,"wait-timeout":10},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":11}},
{"timestamp":"2026-10-21T07:48:08.069122Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:48:13.826317Z","instance":"d3","stickyRejectionsByReasonDelta":{"wait-timeout":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:48:14.162388Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1,"wait-timeout":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:48:25.770092Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:48:35.183997Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:48:38.072360Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:48:43.826028Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:48:44.162106Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:48:55.769497Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:49:05.183794Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:49:08.070369Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:49:13.826477Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:49:14.162921Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"relay_assignment_row_busy":1,"sticky-lane":2}},
{"timestamp":"2026-10-21T07:49:25.771495Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:49:35.183472Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:49:38.072836Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:49:43.826252Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:49:44.163115Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:49:55.769846Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:50:05.184917Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:50:08.072488Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:50:13.827130Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":3},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":3}},
{"timestamp":"2026-10-21T07:50:14.163400Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:50:25.769945Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:50:35.184832Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:50:38.075017Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:50:43.827931Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:50:44.164604Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:50:55.772344Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":3},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":3}},
{"timestamp":"2026-10-21T07:51:05.184087Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"relay_assignment_row_busy":1}},
{"timestamp":"2026-10-21T07:51:08.074651Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:51:13.829283Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:51:14.165434Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:51:25.769048Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:51:35.183654Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:51:38.073759Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:51:43.829359Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:51:44.166698Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:51:55.770350Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:05.184492Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:08.076738Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:52:13.830929Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:14.167578Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:52:25.770188Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:35.185557Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:38.073056Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:52:43.830612Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:44.170744Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:52:55.770072Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:05.185184Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:08.073799Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:53:13.831126Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:14.168954Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:25.771642Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:35.185375Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:38.073871Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:53:43.832470Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:53:44.169801Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:53:55.772778Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:54:05.184779Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:54:08.075259Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:54:13.831013Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:54:14.170568Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:54:25.772879Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:54:35.185747Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:54:38.074242Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:54:43.832593Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:54:44.170223Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:54:55.772796Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:55:05.186732Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:55:08.074506Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:55:13.832282Z","instance":"d3","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:55:14.170168Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:55:25.773007Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:55:35.186844Z","instance":"d1","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:55:38.073584Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"placementRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:55:43.832666Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:55:44.171703Z","instance":"d4","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:55:55.774228Z","instance":"d5","stickyRejectionsByReasonDelta":{"wait-timeout":21},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":21}},
{"timestamp":"2026-10-21T07:56:05.186316Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":4},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":4}},
{"timestamp":"2026-10-21T07:56:08.074683Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:56:13.836910Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:56:14.171278Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:56:25.774220Z","instance":"d5","stickyRejectionsByReasonDelta":{"wait-timeout":132},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":132}},
{"timestamp":"2026-10-21T07:56:35.187168Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:56:38.075749Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:56:43.834101Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":4},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":4}},
{"timestamp":"2026-10-21T07:56:44.171918Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:56:55.775731Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1,"wait-timeout":56},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":57}},
{"timestamp":"2026-10-21T07:57:05.187236Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:57:08.074119Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:57:13.833738Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:57:14.171816Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:57:25.775798Z","instance":"d5","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:57:35.187493Z","instance":"d1","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}},
{"timestamp":"2026-10-21T07:57:38.075303Z","instance":"d2","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:57:43.833226Z","instance":"d3","stickyRejectionsByReasonDelta":{"host-rate-limited":2},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":2}},
{"timestamp":"2026-10-21T07:57:44.170780Z","instance":"d4","drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0},
{"timestamp":"2026-10-21T07:57:55.775393Z","instance":"d5","stickyRejectionsByReasonDelta":{"wait-timeout":5},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":5}},
{"timestamp":"2026-10-21T07:58:05.187113Z","instance":"d1","stickyRejectionsByReasonDelta":{"wait-timeout":9},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":9}},
{"timestamp":"2026-10-21T07:58:08.073793Z","instance":"d2","stickyRejectionsByReasonDelta":{"host-rate-limited":1,"wait-timeout":3},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":4}},
{"timestamp":"2026-10-21T07:58:13.849059Z","instance":"d3","stickyRejectionsByReasonDelta":{"wait-timeout":11},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":11}},
{"timestamp":"2026-10-21T07:58:14.171116Z","instance":"d4","stickyRejectionsByReasonDelta":{"wait-timeout":13},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":13}},
{"timestamp":"2026-10-21T07:58:25.776011Z","instance":"d5","stickyRejectionsByReasonDelta":{"host-rate-limited":1},"drainReturnDeferralsDelta":0,"drainReturnAssignmentsDelta":0,"assign503sByCauseDelta":{"sticky-lane":1}}
]
}
@@ -39,6 +39,14 @@ export const RESERVE_WATCH_RULES = {
reserveApplyWaitMs: 10 * MINUTE_MS
}
// The fleet's non-drain 503s no instance's own lines account for. Unclamped per minute: the fleet
// buckets by request minute and an instance's lines by 30 s, so a minute carries that skew and
// only the sum over the judged minutes is judged, against the whole fleet line: no one
// instance's stall explains it then. Its largest 3-minute sum in 10-20/21 was 38, against 90. An
// instance answering 503s without logging its lines lands here and trips on purpose: a director
// that stops logging is not the database stall the split lets through (10-21 07:55Z kept logging).
export const UNATTRIBUTED_503S = 'unattributed'
// Precedence: the first tripped rule names the verdict, so exact signals come before proxies. The
// sample line's `rules` keys follow this order too.
export const RULE_NAMES = ['deadMan', 'overrun', 'director503', 'duplicateSeat', 'hostDrop', 'ledgerDrift']
@@ -238,10 +246,15 @@ function judgeDirector503(read, state, at) {
const directors = Math.min(...recent.map((minute) => read.instances.directors?.[minute]))
if (!Number.isInteger(directors)) return unverifiable(`no director instance count for ${recent.join(', ')}`)
const instanceAbove = above / Math.max(directors, instances.minOver)
const over = Object.entries(read.instances.perMinute)
const { [UNATTRIBUTED_503S]: rest = {}, ...byInstance } = read.instances.perMinute
const unattributed = recent.reduce((sum, minute) => sum + (rest[minute] ?? 0), 0)
const over = Object.entries(byInstance)
.filter(([, byMinute]) => recent.every((minute) => (byMinute[minute] ?? 0) > instanceAbove))
.map(([instance]) => instance)
const split = { ...detail, instanceAbove, instancesOver: over.length }
const split = { ...detail, instanceAbove, instancesOver: over.length, unattributed }
if (unattributed > above * recent.length) {
return trip(`director 503s above ${above}/min ${sustained}, ${unattributed} of them no director instance logged`, split)
}
if (over.length >= instances.minOver) {
return trip(`director 503s above ${above}/min ${sustained}, ${over.length} instances each above ${instanceAbove}/min`, split)
}
@@ -202,6 +202,7 @@ export function drainReturnByMinute(reads, limit) {
const retries = new Map()
const busy = new Map()
const assignments = new Map()
const assign503s = new Map()
let retryAfterSecondsMax = 0
let truncated = false
const charge = (map, endedAt, count) => {
@@ -225,6 +226,7 @@ export function drainReturnByMinute(reads, limit) {
charge(retries, endedAt, ownRetries(sample))
charge(busy, endedAt, rowBusy(sample))
charge(assignments, endedAt, sample.drainReturnAssignmentsDelta ?? 0)
charge(assign503s, endedAt, Object.values(sample.assign503sByCauseDelta ?? {}).reduce((sum, count) => sum + count, 0))
retryAfterSecondsMax = Math.max(
retryAfterSecondsMax,
sample.drainReturnRetryAfterSecondsMax ?? 0
@@ -241,6 +243,8 @@ export function drainReturnByMinute(reads, limit) {
return {
deferralsPerMinute: Object.fromEntries(deferrals),
ownRetriesPerMinute: Object.fromEntries(retries),
// Every /v1/assign 503 the director logged, scheduled or not: the same count by instance.
assign503sPerMinute: Object.fromEntries(assign503s),
deferralsTotal: sum(deferrals),
deferralsPeakPerMinute: Math.round(Math.max(0, ...deferrals.values())),
assignmentsTotal: sum(assignments),
@@ -222,7 +222,7 @@ export async function readDirectorRequestsByCodePerMinute(reader, { config, span
// textPayload filter matches nothing here and returns zero without saying so.
const CELL_LOG_SCOPE = 'resource.type="gce_instance" AND logName:"cos_containers"'
const DIRECTOR_DRAIN_FIELDS = [
export const DIRECTOR_DRAIN_FIELDS = [
'stickyRejectionsByReasonDelta',
'placementRejectionsByReasonDelta',
'drainReturnDeferralsDelta',
@@ -234,6 +234,12 @@ const DIRECTOR_DRAIN_FIELDS = [
// Five instances at one sample per 30 s is ~100 per 10-min sub-window; this many is truncation.
export const DIRECTOR_METRICS_LIMIT = 1000
export const directorMetricsSample = (entry) => ({
timestamp: entry.timestamp,
instanceId: entry.labels?.instanceId,
...entry.jsonPayload
})
export async function readDirectorDrainReturn(reader, { config, window }) {
const projection = `json(timestamp,labels.instanceId,${DIRECTOR_DRAIN_FIELDS
.map((field) => `jsonPayload.${field}`)
@@ -250,11 +256,7 @@ export async function readDirectorDrainReturn(reader, { config, window }) {
})
reads.push({
failed: read.failed,
samples: read.entries.map((entry) => ({
timestamp: entry.timestamp,
instanceId: entry.labels?.instanceId,
...entry.jsonPayload
})),
samples: read.entries.map(directorMetricsSample),
minSamples: Math.floor(
(subWindow.endedAt.getTime() - subWindow.startedAt.getTime()) / DIRECTOR_METRICS_INTERVAL_MS
)
+151 -89
View File
@@ -22,7 +22,9 @@ import {
WRITE_CONFIRMATION
} from './operate-relay-cell-flags.mjs'
import {
DIRECTOR_DRAIN_FIELDS,
DIRECTOR_METRICS_LIMIT,
directorMetricsSample,
readDirectorDrainReturn,
readDirectorRequestsByCodePerMinute,
readLogEntries
@@ -43,7 +45,8 @@ import {
measuredPhoneBackground,
phoneSinceOf,
PHONE_WATCH_RULES,
RESERVE_WATCH_RULES
RESERVE_WATCH_RULES,
UNATTRIBUTED_503S
} from './relay-reserve-watch-rules.mjs'
const execFileAsync = promisify(execFile)
@@ -161,7 +164,7 @@ const failed503Read = (error) => ({ ok: false, error, perMinute: {}, minutes: []
// point for (the director always serves something, so no front at all is no evidence); a minute
// with no metrics line from any instance is `unmetered`, since subtracting nothing there is a guess.
// Coverage is any instance, not each: one instance's missing lines under-subtract, biased to a trip.
export async function readDirector503Minutes(reader, { projectId, directorService, endedAt, minutes }) {
export async function readDirector503Minutes(reader, { projectId, directorService, endedAt, minutes, metrics: logged }) {
const span = { startedAt: new Date(endedAt - minutes * MINUTE_MS), endedAt: new Date(endedAt) }
const config = { projectId, directorService }
let read
@@ -172,11 +175,10 @@ export async function readDirector503Minutes(reader, { projectId, directorServic
return failed503Read(`director 503 metric unreadable: ${error instanceof Error ? error.message : error}`)
}
if (read.failed) return failed503Read('director 503 metric read failed')
// A line covers the 30 s before it, so the last minute's tail is in the next minute's lines.
const metrics = await readDirectorDrainReturn(reader, {
config,
window: { startedAt: span.startedAt, endedAt: new Date(endedAt + DIRECTOR_METRICS_INTERVAL_MS) }
})
// The sample's combined log read passes these in; a background read takes its own.
const metrics = logged ?? (await readDirectorDrainReturn(reader, { config, window: directorMetricsWindow(endedAt, minutes) }))
const failedPart = metrics.find((part) => part.failed && part.error)
if (failedPart) return failed503Read(`director runtime metrics read failed: ${failedPart.error}`)
const lineTimes = metrics.flatMap((part) => part.samples.map((sample) => Date.parse(sample.timestamp)))
if (lineTimes.some((at) => !Number.isFinite(at))) return failed503Read('director runtime metrics line with an unparseable timestamp')
const scheduled = drainReturnByMinute(metrics, DIRECTOR_METRICS_LIMIT)
@@ -191,55 +193,35 @@ export async function readDirector503Minutes(reader, { projectId, directorServic
minutes: keys,
publishedThrough: published.at(-1) ?? null,
unmetered: keys.filter((minute) => !metered.has(minute)),
instances: await readDirector503ByInstance(reader, { projectId, directorService, span, metrics, keys })
instances: director503ByInstance(metrics, keys, nonDrain.series)
}
}
// Raw 503 request-log lines in one 10-minute read; 10-20/21 peaked at 1,681. Past it only a
// brownout over the broad line, which needs no split.
export const DIRECTOR_503_LOG_LIMIT = 5_000
// Platform 503s answered by no instance (none in 10-20/21) count as one instance of their own.
const NO_INSTANCE = 'no-instance'
// A line covers the 30 s before it, so the last minute's tail is in the next minute's lines.
const directorMetricsWindow = (endedAt, minutes) => ({
startedAt: new Date(endedAt - minutes * MINUTE_MS),
endedAt: new Date(endedAt + DIRECTOR_METRICS_INTERVAL_MS)
})
// The same non-drain measure per director instance: the metric names no instance, so the 503s
// come from Cloud Run's request log, less each instance's own scheduled 503s. `directors` is, per
// minute, how many instances logged a runtime-metrics line ending in it.
async function readDirector503ByInstance(reader, { projectId, directorService, span, metrics, keys }) {
const read = await readLogEntries(reader, {
filter: [
'resource.type="cloud_run_revision"',
`resource.labels.service_name="${directorService}"`,
`logName="projects/${projectId}/logs/run.googleapis.com%2Frequests"`,
'httpRequest.status=503',
`timestamp>="${span.startedAt.toISOString()}"`,
`timestamp<"${span.endedAt.toISOString()}"`
].join(' AND '),
projection: 'json(timestamp,labels.instanceId)',
limit: DIRECTOR_503_LOG_LIMIT
})
if (read.failed) return failedRead(`director 503 request log read failed: ${read.error}`)
if (read.entries.length >= DIRECTOR_503_LOG_LIMIT) return failedRead('director 503 request log read truncated')
const raw = new Map()
for (const entry of read.entries) {
const at = Date.parse(entry.timestamp)
if (!Number.isFinite(at)) return failedRead('director 503 request log line with an unparseable timestamp')
const instance = entry.labels?.instanceId || NO_INSTANCE
const perMinute = raw.get(instance) ?? {}
perMinute[minuteKey(at)] = (perMinute[minuteKey(at)] ?? 0) + 1
raw.set(instance, perMinute)
}
// The same non-drain measure per director instance, from each instance's own runtime-metrics
// counts of the /v1/assign 503s it answered, less its own scheduled ones. 10-21 07:55Z read 40,
// 169, 5 on the stalled instance this way, against 54, 155, 5 in Cloud Run's request log.
// `directors` is, per minute, how many instances logged a line ending in it.
function director503ByInstance(metrics, keys, fleet) {
const samples = metrics.flatMap((part) => part.samples)
const perInstance = {}
for (const [instance, perMinute] of raw) {
// Coverage is checked fleet-wide above; an instance with no lines under-subtracts, toward a trip.
const own = drainReturnByMinute(
metrics.map((part) => ({ samples: part.samples.filter((sample) => sample.instanceId === instance) })),
DIRECTOR_METRICS_LIMIT
)
const { series } = withoutDrainDeferrals({ perMinute }, own, keys)
for (const instance of new Set(samples.map((sample) => sample.instanceId).filter(Boolean))) {
const own = drainReturnByMinute([{ samples: samples.filter((sample) => sample.instanceId === instance) }], DIRECTOR_METRICS_LIMIT)
const { series } = withoutDrainDeferrals({ perMinute: own.assign503sPerMinute }, own, keys)
perInstance[instance] = Object.fromEntries(keys.map((minute, index) => [minute, series[index]]))
}
// Cloud Run's own 503s, or a route the director does not count; an instance that stopped logging
// lands here too.
perInstance[UNATTRIBUTED_503S] = Object.fromEntries(
keys.map((minute, index) => [minute, fleet[index] - Object.values(perInstance).reduce((sum, byMinute) => sum + (byMinute[minute] ?? 0), 0)])
)
const logged = new Map(keys.map((minute) => [minute, new Set()]))
for (const sample of metrics.flatMap((part) => part.samples)) {
for (const sample of samples) {
if (sample.instanceId) logged.get(minuteKey(Date.parse(sample.timestamp) - 1))?.add(sample.instanceId)
}
const directors = Object.fromEntries([...logged].map(([minute, instances]) => [minute, instances.size]))
@@ -253,22 +235,28 @@ const sumOf = (counts) => Object.values(counts ?? {}).reduce((sum, count) => sum
// The cell's own runtime metrics lines (one per 30 s) for the `minutes` whole minutes ending at
// `endedAt`, per minute: what the phone rules judge. A line counts toward the minute it ends in;
// a minute no line covers is `unmetered`, never zero.
export async function readCellPhoneMinutes(reader, { cellId, endedAt, minutes }) {
const span = { startedAt: new Date(endedAt - minutes * MINUTE_MS), endedAt: new Date(endedAt) }
const read = await readLogEntries(reader, {
function cellPhoneQuery({ cellId, endedAt, minutes }) {
return {
filter: [
'resource.type="gce_instance"',
`jsonPayload.event="${CELL_METRICS_EVENT}"`,
`jsonPayload.cellId="${cellId}"`,
`timestamp>="${span.startedAt.toISOString()}"`,
`timestamp<"${new Date(endedAt + DIRECTOR_METRICS_INTERVAL_MS).toISOString()}"`
...timestampClauses(endedAt - minutes * MINUTE_MS, endedAt + DIRECTOR_METRICS_INTERVAL_MS)
].join(' AND '),
projection:
'json(timestamp,jsonPayload.phoneRejectionsByCodeDelta,jsonPayload.resumeConfirmFailuresByCodeDelta,' +
'jsonPayload.phoneAttachDeadlinesDelta,jsonPayload.phoneAcceptDatabaseErrorsDelta,jsonPayload.resumeRenewalFailuresDelta,' +
'jsonPayload.clientAcceptCompletedDelta)',
limit: LOG_ENTRY_LIMIT
})
fields: [
'jsonPayload.phoneRejectionsByCodeDelta', 'jsonPayload.resumeConfirmFailuresByCodeDelta', 'jsonPayload.phoneAttachDeadlinesDelta',
'jsonPayload.phoneAcceptDatabaseErrorsDelta', 'jsonPayload.resumeRenewalFailuresDelta', 'jsonPayload.clientAcceptCompletedDelta'
],
match: (entry) => entry.resource?.type === 'gce_instance'
}
}
export async function readCellPhoneMinutes(reader, request) {
return parseCellPhoneMinutes(await readQuery(reader, cellPhoneQuery(request)), request)
}
function parseCellPhoneMinutes(read, { endedAt, minutes }) {
const span = { startedAt: new Date(endedAt - minutes * MINUTE_MS), endedAt: new Date(endedAt) }
if (read.failed) return failedRead(read.error)
if (read.entries.length >= LOG_ENTRY_LIMIT) return failedRead('cell runtime metrics read truncated')
const keys = minutesOf(span)
@@ -306,13 +294,24 @@ function directorLogFilter(directorService, eventClause, sinceAt) {
].join(' AND ')
}
const atOrAfter = (entry, sinceAt) => Date.parse(entry.timestamp) >= sinceAt
const isEvent = (entry, event) => entry.resource?.type === 'cloud_run_revision' && entry.jsonPayload?.event === event
function ledgerQuery({ directorService, at }) {
const sinceAt = at - RESERVE_WATCH_RULES.ledgerSummaryMaxAgeMs
return {
filter: directorLogFilter(directorService, `jsonPayload.event="${LEDGER_SUMMARY_EVENT}"`, sinceAt),
fields: ['jsonPayload.pending', 'jsonPayload.dropped'],
match: (entry) => isEvent(entry, LEDGER_SUMMARY_EVENT) && atOrAfter(entry, sinceAt)
}
}
export async function readLedgerSample(reader, request) {
return parseLedgerSample(await readQuery(reader, ledgerQuery(request)))
}
// Newest summary per director instance; queued plus dropped rows are the ones not in Postgres.
export async function readLedgerSample(reader, { directorService, at }) {
const read = await readLogEntries(reader, {
filter: directorLogFilter(directorService, `jsonPayload.event="${LEDGER_SUMMARY_EVENT}"`, at - RESERVE_WATCH_RULES.ledgerSummaryMaxAgeMs),
projection: 'json(timestamp,labels.instanceId,jsonPayload.pending,jsonPayload.dropped)',
limit: LOG_ENTRY_LIMIT
})
function parseLedgerSample(read) {
if (read.failed) return failedRead(read.error)
if (read.entries.length >= LOG_ENTRY_LIMIT) return failedRead('ledger summary read truncated')
if (read.entries.length === 0) return failedRead('no placing director logged a ledger summary')
@@ -337,16 +336,26 @@ export async function readLedgerSample(reader, { directorService, at }) {
// The same read also takes the ledger summary every placing director logs each minute, as a
// positive control: with no fresh summary in it, no seat-behind-row line is blindness, not zero.
export async function readSeatBehindRowSample(reader, { directorService, cellId, at }) {
const read = await readLogEntries(reader, {
function seatBehindRowQuery({ directorService, cellId, at }) {
const sinceAt = at - RESERVE_WATCH_RULES.duplicateSeat.lookbackMs
return {
filter: directorLogFilter(
directorService,
`((jsonPayload.event="${SEAT_BEHIND_ROW_EVENT}" AND jsonPayload.cellId="${cellId}") OR jsonPayload.event="${LEDGER_SUMMARY_EVENT}")`,
at - RESERVE_WATCH_RULES.duplicateSeat.lookbackMs
sinceAt
),
projection: 'json(timestamp,labels.instanceId,jsonPayload.event,jsonPayload.epoch,jsonPayload.rowEpoch)',
limit: LOG_ENTRY_LIMIT
})
fields: ['jsonPayload.epoch', 'jsonPayload.rowEpoch'],
match: (entry) =>
atOrAfter(entry, sinceAt) &&
((isEvent(entry, SEAT_BEHIND_ROW_EVENT) && entry.jsonPayload?.cellId === cellId) || isEvent(entry, LEDGER_SUMMARY_EVENT))
}
}
export async function readSeatBehindRowSample(reader, request) {
return parseSeatBehindRowSample(await readQuery(reader, seatBehindRowQuery(request)), request)
}
function parseSeatBehindRowSample(read, { cellId, at }) {
if (read.failed) return failedRead(read.error)
if (read.entries.length >= LOG_ENTRY_LIMIT) return failedRead('seat-behind-row read truncated')
const reports = []
@@ -368,6 +377,60 @@ export async function readSeatBehindRowSample(reader, { directorService, cellId,
return { ok: true, reports }
}
const timestampClauses = (startedAt, endedAt) => [
`timestamp>="${new Date(startedAt).toISOString()}"`,
`timestamp<"${new Date(endedAt).toISOString()}"`
]
const projectionOf = (queries) =>
`json(${[...new Set(['timestamp', 'resource.type', 'labels.instanceId', 'jsonPayload.event', 'jsonPayload.cellId', ...queries.flatMap((query) => query.fields)])].join(',')})`
const readQuery = (reader, query) => readLogEntries(reader, { filter: query.filter, projection: projectionOf([query]), limit: LOG_ENTRY_LIMIT })
function directorMetricsQuery({ directorService, endedAt, minutes }) {
const window = directorMetricsWindow(endedAt, minutes)
return {
filter: [
'resource.type="cloud_run_revision"',
`resource.labels.service_name="${directorService}"`,
`jsonPayload.event="${CELL_METRICS_EVENT}"`,
...timestampClauses(window.startedAt, window.endedAt)
].join(' AND '),
fields: DIRECTOR_DRAIN_FIELDS.map((field) => `jsonPayload.${field}`),
match: (entry) => isEvent(entry, CELL_METRICS_EVENT),
minSamples: Math.floor((window.endedAt - window.startedAt) / DIRECTOR_METRICS_INTERVAL_MS)
}
}
// The queries one sample sends; `phoneRequest` is false while the phone path is not watched.
export const sampleLogQueries = ({ phoneRequest, director, seatRequest }) => ({
phone: phoneRequest && cellPhoneQuery(phoneRequest),
directorMetrics: directorMetricsQuery(director),
ledger: ledgerQuery({ directorService: director.directorService, at: seatRequest.at }),
seatBehindRow: seatBehindRowQuery(seatRequest)
})
// Every Cloud Logging query a sample needs, as one read: the project allows 60 reads a minute
// across every watch and tool, and on 10-21 18:39Z twelve watches at five reads a sample drew 429s
// until c21 and c22 tripped unverifiable. Each query keeps its own filter and takes back the lines
// that are its own; the combined read truncated or failed fails every one of them.
export async function readSampleLogs(reader, queries) {
const named = Object.entries(queries).filter(([, query]) => query)
const read = await readLogEntries(reader, {
filter: named.map(([, query]) => `(${query.filter})`).join(' OR '),
projection: projectionOf(named.map(([, query]) => query)),
limit: LOG_ENTRY_LIMIT
})
const failure = read.failed
? read.error
: read.entries.length >= LOG_ENTRY_LIMIT
? 'sample log read truncated'
: read.entries.some((entry) => !Number.isFinite(Date.parse(entry.timestamp)))
? 'sample log line with an unparseable timestamp'
: null
return Object.fromEntries(
named.map(([name, query]) => [name, failure ? { failed: true, error: failure, entries: [] } : { failed: false, entries: read.entries.filter(query.match) }])
)
}
function createReader(dependencies, projectId) {
const now = dependencies.now
return {
@@ -511,27 +574,26 @@ export async function watchSegment(config, dependencies) {
}
state = { ...state, phoneBackground: measuredPhoneBackground(read) }
}
// Complete minutes only, held back past the publishing delay.
const endedAt = minuteFloor(at - METRIC_DELAY_MS)
const phoneRequest = { cellId: request.cellId, endedAt, minutes: PHONE_WATCH_RULES.readMinutes }
const director = { directorService, endedAt, minutes: RESERVE_WATCH_RULES.director503.readMinutes }
const seatRequest = { directorService, cellId: request.cellId, at }
const queries = sampleLogQueries({ phoneRequest: phoneWatched && phoneRequest, director, seatRequest })
const logs = await readSampleLogs(reader, queries)
const directorMetrics = {
failed: logs.directorMetrics.failed,
error: logs.directorMetrics.error,
samples: logs.directorMetrics.entries.map(directorMetricsSample),
minSamples: queries.directorMetrics.minSamples
}
const sample = {
at,
cell,
...(phoneWatched
? {
phone: await readCellPhoneMinutes(reader, {
cellId: request.cellId,
endedAt: minuteFloor(at - METRIC_DELAY_MS),
minutes: PHONE_WATCH_RULES.readMinutes
})
}
: {}),
director503: await readDirector503Minutes(reader, {
projectId: request.projectId,
directorService,
// Complete minutes only, held back past the publishing delay.
endedAt: minuteFloor(at - METRIC_DELAY_MS),
minutes: RESERVE_WATCH_RULES.director503.readMinutes
}),
ledger: await readLedgerSample(reader, { directorService, at }),
seatBehindRow: await readSeatBehindRowSample(reader, { directorService, cellId: request.cellId, at })
...(phoneWatched ? { phone: parseCellPhoneMinutes(logs.phone, phoneRequest) } : {}),
director503: await readDirector503Minutes(reader, { projectId: request.projectId, ...director, metrics: [directorMetrics] }),
ledger: parseLedgerSample(logs.ledger),
seatBehindRow: parseSeatBehindRowSample(logs.seatBehindRow, seatRequest)
}
const result = evaluateWatchSample(state, sample, { unverifiableLimit: config.unverifiableLimit })
state = result.state
@@ -25,7 +25,9 @@ import {
raiseAlarm,
readCellPhoneMinutes,
readDirector503Minutes,
readSampleLogs,
readSeatBehindRowSample,
sampleLogQueries,
recordOutcome,
rolloutLeaseRefusal,
watchSegment
@@ -198,9 +200,14 @@ test('one stalled director instance does not trip the 503 rule; two instances or
assert.equal(blind.rules.director503.status, 'unverifiable')
assert.match(blind.rules.director503.reason, /truncated/)
assert.equal(blind.state.unverifiableStreak, 1)
// Platform 503s no instance answered count as an instance of their own.
const platform = judge(read([37, 37, 37], { 'no-instance': [16, 16, 16], d2: [16, 16, 16] }))
// 503s no director logged, over the whole fleet line by themselves, are no one instance's stall.
const platform = judge(read([37, 37, 37], { unattributed: [31, 31, 31], d2: [6, 6, 6] }))
assert.equal(platform.state.state, 'tripped')
assert.match(platform.state.verdict, /93 of them no director instance logged$/)
// A stalled instance's 30 s lines skew against the fleet's minutes; that remainder is not a second instance.
const skewed = judge(read([60, 170, 60], { sick: [52, 162, 52], unattributed: [8, 8, 8], d2: [0, 0, 0] }))
assert.equal(skewed.state.state, 'watching')
assert.equal(skewed.rules.director503.unattributed, 24)
// The share follows the directors that logged: eight make it 30/8, so an even 45 trips.
const eight = read([45, 45, 45], Object.fromEntries(Array.from({ length: 8 }, (_, index) => [`d${index}`, [5.6, 5.6, 5.6]])))
eight.instances.directors = byMinute([8, 8, 8])
@@ -225,27 +232,65 @@ test('one stalled director instance does not trip the 503 rule; two instances or
assert.equal(calm.rules.director503.status, 'pass')
})
test('per-instance 503s come from the request log, less each instance\'s own re-dials', async () => {
// 20 a minute split 10/10 over a and b; a alone answered 10 of its own re-dials a minute.
const sources = fakeSources({ per503: () => 20, metrics: () => [rateLimited(5), {}] })
test('per-instance 503s are each director\'s own logged 503s less its re-dials, and the rest is unattributed', async () => {
// Fleet 30 a minute. a logged 20 (10 its own re-dials), b logged 6: 10 nobody accounts for.
const lineOf = (count, own) => ({
placementRejectionsByReasonDelta: { 'host-rate-limited': own },
assign503sByCauseDelta: { 'placement-lane': count }
})
const sources = fakeSources({ per503: () => 30, metrics: () => [lineOf(10, 5), lineOf(3, 0)] })
const read = await readDirector503Minutes(
{ ...fakeReader(), fetch: sources.fetchImpl, runGcloud: sources.runGcloud },
{ projectId: 'onorca-cloud', directorService: 'orca-cloud-relay', endedAt: START, minutes: 3 }
)
assert.equal(read.ok, true)
assert.deepEqual(Object.values(read.perMinute), [10, 10, 10])
assert.deepEqual(Object.keys(read.instances.perMinute).sort(), ['a', 'b'])
assert.deepEqual(Object.values(read.instances.perMinute.a), [0, 0, 0])
assert.deepEqual(Object.values(read.instances.perMinute.b), [10, 10, 10])
assert.deepEqual(Object.values(read.perMinute), [20, 20, 20])
assert.deepEqual(Object.keys(read.instances.perMinute).sort(), ['a', 'b', 'unattributed'])
assert.deepEqual(Object.values(read.instances.perMinute.a), [10, 10, 10])
assert.deepEqual(Object.values(read.instances.perMinute.b), [6, 6, 6])
assert.deepEqual(Object.values(read.instances.perMinute.unattributed), [4, 4, 4])
assert.deepEqual(Object.values(read.instances.directors), [2, 2, 2])
// A full read is truncated, not every 503.
const flood = fakeSources({ per503: () => 2_000 })
const truncated = await readDirector503Minutes(
{ ...fakeReader(), fetch: flood.fetchImpl, runGcloud: flood.runGcloud },
{ projectId: 'onorca-cloud', directorService: 'orca-cloud-relay', endedAt: START, minutes: 3 }
})
test('the recorded 10-21 07:55Z director stall reads as one instance and does not trip', async () => {
const recorded = JSON.parse(readFileSync(new URL('../fixtures/relay-reserve-watch/2026-10-21-director-stall.json', import.meta.url), 'utf8'))
const series = (code, perMinute) => ({
metric: { labels: { response_code: code } },
points: Object.entries(perMinute).map(([minute, count]) => ({
interval: { endTime: new Date(Date.parse(`${minute}Z`) + MINUTE).toISOString() },
value: { int64Value: String(count) }
}))
})
const ok = Object.fromEntries(Object.keys(recorded.fleet503PerMinute).map((minute) => [minute, 1_000]))
const lines = recorded.directorMetricsLines.map(({ timestamp, instance, ...deltas }) => ({
timestamp,
labels: { instanceId: instance },
resource: { type: 'cloud_run_revision' },
jsonPayload: { event: METRICS_EVENT, ...deltas }
}))
const read = await readDirector503Minutes(
{
...fakeReader(),
fetch: async () => new Response(JSON.stringify({ timeSeries: [series('200', ok), series('503', recorded.fleet503PerMinute)] })),
runGcloud: async (args) => {
if (args[0] === 'auth') return { stdout: 'token' }
const [, from, to] = args[2].match(/timestamp>="([^"]+)" AND timestamp<"([^"]+)"/)
return { stdout: JSON.stringify(lines.filter(({ timestamp }) => timestamp >= from && timestamp < to)) }
}
},
{ projectId: 'onorca-cloud', directorService: 'orca-cloud-relay', endedAt: Date.parse('2026-10-21T07:58:00Z'), minutes: 10 }
)
assert.equal(truncated.ok, true)
assert.deepEqual(truncated.instances, { ok: false, error: 'director 503 request log read truncated' })
const judged = evaluateWatchSample(
freshState({ background: { medianPerMinute: 10, source: 'input' } }),
sampleAt(START, { director503: read }),
LIMIT
)
// The fleet alone was over 30 all three minutes, which is what tripped all four watches.
assert.deepEqual(Object.values(judged.rules.director503.recent), [45, 164, 40])
assert.equal(judged.rules.director503.status, 'pass')
// Not even the stalled instance held its share of 6 a minute: it fell to 5 in the third minute.
assert.equal(judged.rules.director503.instancesOver, 0)
assert.deepEqual(['07:55', '07:56', '07:57'].map((minute) => read.instances.perMinute.d5[`2026-10-21T${minute}`]), [40, 169, 5])
})
const SEAT_EVENT = 'orca_relay_reserve_ledger_seat_behind_row'
@@ -510,19 +555,8 @@ function metricsLines(filter, metrics) {
return lines.reverse()
}
// The `metrics` lines' instances in order; each minute's 503s are split between the first two.
// The `metrics` lines' instances, in order.
const FAKE_DIRECTORS = ['a', 'b', 'c', 'd', 'e']
function requestLines(filter, per503) {
const [, from, to] = filter.match(/timestamp>="([^"]+)" AND timestamp<"([^"]+)"/)
const lines = []
for (let minute = Date.parse(from); minute < Date.parse(to); minute += MINUTE) {
const count = per503(minute)
for (let index = 0; index < count; index += 1) {
lines.push({ timestamp: new Date(minute + 1_000).toISOString(), labels: { instanceId: FAKE_DIRECTORS[index % 2] } })
}
}
return lines.reverse()
}
function fakeSources({
cell = () => ({}),
@@ -534,7 +568,9 @@ function fakeSources({
seatControl = true,
rolloutLease = null,
// Each director instance's runtime-metrics deltas for the 30 s ending at `endedAt`.
metrics = () => [{}]
metrics = () => [{}],
// The watched cell's own runtime-metrics deltas, when it runs the phone path.
phone = null
} = {}) {
let samples = 0
const monitoringSpans = []
@@ -576,17 +612,33 @@ function fakeSources({
}
throw new Error(`unexpected fetch ${text}`)
}
const director = (entries) => entries.map((entry) => ({ ...entry, resource: { type: 'cloud_run_revision' } }))
// One query's lines, as Cloud Logging would answer its filter alone.
const answer = (filter) => {
const summaries = director(ledger(samples).map((entry) => ({ ...entry, jsonPayload: { ...entry.jsonPayload, event: SUMMARY_EVENT } })))
if (filter.includes(SEAT_EVENT)) return seatControl ? summaries : []
if (filter.includes(SUMMARY_EVENT)) return summaries
if (filter.includes('gce_instance')) {
return phone ? metricsLines(filter, (at) => [{ ...phone(at), event: METRICS_EVENT, cellId: CELL }]).map((entry) => ({ ...entry, resource: { type: 'gce_instance' } })) : []
}
if (filter.includes(METRICS_EVENT)) return director(metricsLines(filter, (at) => metrics(at).map((deltas) => ({ ...deltas, event: METRICS_EVENT }))))
return []
}
const logReads = []
const runGcloud = async (args) => {
if (args[0] === 'auth') return { stdout: 'token' }
const filter = args[2]
const summaries = ledger(samples).map((entry) => ({ ...entry, jsonPayload: { ...entry.jsonPayload, event: SUMMARY_EVENT } }))
if (filter.includes(SEAT_EVENT)) return { stdout: JSON.stringify(seatControl ? summaries : []) }
if (filter.includes(SUMMARY_EVENT)) return { stdout: JSON.stringify(ledger(samples)) }
if (filter.includes(METRICS_EVENT)) return { stdout: JSON.stringify(metricsLines(filter, metrics)) }
if (filter.includes('httpRequest.status=503')) return { stdout: JSON.stringify(requestLines(filter, per503)) }
return { stdout: '[]' }
logReads.push({ sample: samples, filter })
// A sample's combined read is `(A) OR (B) ...`; each part answers as it would alone, deduplicated.
const parts = filter.startsWith('(') ? filter.slice(1, -1).split(') OR (') : [filter]
const seen = new Set()
const entries = parts.flatMap(answer).filter((entry) => {
const key = JSON.stringify(entry)
return !seen.has(key) && seen.add(key)
})
return { stdout: JSON.stringify(entries) }
}
return { fetchImpl, runGcloud, monitoringSpans, readRolloutLease: async () => rolloutLease }
return { fetchImpl, runGcloud, monitoringSpans, logReads, readRolloutLease: async () => rolloutLease }
}
test('a segment samples once a minute, carries state to the next segment, and stops on a trip', async () => {
@@ -968,15 +1020,95 @@ test('503s the director could not serve trip after three minutes over the line,
const flippedAt = START - 2 * MINUTE
const { state } = await watchFor(10, fakeSources({
per503: (minute) => (minute < flippedAt ? 20 : 60),
// 10 re-dials a minute throughout; the 40 more after the flip are queue waits that timed out.
metrics: (at) => [{
...rateLimited(5),
stickyRejectionsByReasonDelta: at <= flippedAt ? {} : { 'wait-timeout': 20 }
}]
// 10 re-dials a minute on a throughout; the 40 more after the flip are queue waits that timed
// out, 20 a minute on each of two instances.
metrics: (at) => {
const waits = at <= flippedAt ? {} : { stickyRejectionsByReasonDelta: { 'wait-timeout': 10 }, assign503sByCauseDelta: { 'sticky-lane': 10 } }
const a = rateLimited(5)
return [{ ...a, ...waits, assign503sByCauseDelta: { ...a.assign503sByCauseDelta, ...waits.assign503sByCauseDelta } }, waits]
}
}), flippedAt)
assert.equal(state.state, 'tripped')
assert.deepEqual(state.background, { medianPerMinute: 10, minutes: 10, source: 'measured' })
assert.match(state.verdict, /director503: director 503s above 30\/min for 3 min \(50, 50, 50\)/)
assert.match(state.verdict, /director503: director 503s above 30\/min for 3 min \(50, 50, 50\), 2 instances each above 15\/min$/)
})
test('the combined read hands each query only its own lines', async () => {
const at = START
const endedAt = START - 3 * MINUTE
const queries = sampleLogQueries({
phoneRequest: { cellId: CELL, endedAt, minutes: 10 },
director: { directorService: 'orca-cloud-relay', endedAt, minutes: 10 },
seatRequest: { directorService: 'orca-cloud-relay', cellId: CELL, at }
})
const director = (ago, jsonPayload) => ({ timestamp: new Date(at - ago).toISOString(), resource: { type: 'cloud_run_revision' }, labels: { instanceId: 'a' }, jsonPayload })
const entries = {
metrics: director(5 * MINUTE, { event: METRICS_EVENT }),
phone: { timestamp: new Date(at - 5 * MINUTE).toISOString(), resource: { type: 'gce_instance' }, jsonPayload: { event: METRICS_EVENT, cellId: CELL } },
freshSummary: director(60_000, { event: SUMMARY_EVENT, pending: 0, dropped: 0 }),
staleSummary: director(10 * MINUTE, { event: SUMMARY_EVENT, pending: 0, dropped: 0 }),
ownSeat: director(2 * MINUTE, { event: SEAT_EVENT, cellId: CELL, epoch: 1, rowEpoch: 2 }),
otherSeat: director(2 * MINUTE, { event: SEAT_EVENT, cellId: 'production-gce-c99', epoch: 1, rowEpoch: 2 })
}
let filter
const logs = await readSampleLogs(
fakeReader({ runGcloud: async (args) => ((filter = args[2]), { stdout: JSON.stringify(Object.values(entries)) }) }),
queries
)
const names = (read) => read.entries.map((entry) => Object.keys(entries).find((name) => JSON.stringify(entries[name]) === JSON.stringify(entry)))
assert.equal(filter.split(') OR (').length, 4)
assert.deepEqual(names(logs.directorMetrics), ['metrics'])
assert.deepEqual(names(logs.phone), ['phone'])
// The ledger rule wants a summary from the last 150 s; the seat read's positive control, 15 min.
assert.deepEqual(names(logs.ledger), ['freshSummary'])
assert.deepEqual(names(logs.seatBehindRow), ['freshSummary', 'staleSummary', 'ownSeat'])
// Without the phone path its query is not sent.
const unwatched = sampleLogQueries({ phoneRequest: false, director: { directorService: 'orca-cloud-relay', endedAt, minutes: 10 }, seatRequest: { directorService: 'orca-cloud-relay', cellId: CELL, at } })
const sent = await readSampleLogs(fakeReader({ runGcloud: async (args) => ((filter = args[2]), { stdout: '[]' }) }), unwatched)
assert.equal(filter.split(') OR (').length, 3)
assert.doesNotMatch(filter, /gce_instance/)
assert.equal('phone' in sent, false)
// A full read is truncated for every query; a failed one names its error.
const full = await readSampleLogs(fakeReader({ runGcloud: async () => ({ stdout: JSON.stringify(Array.from({ length: 1_000 }, () => entries.metrics)) }) }), unwatched)
assert.ok(Object.values(full).every((read) => read.failed && read.error === 'sample log read truncated'))
})
// 10-21 18:39Z: twelve watches at ~5 Cloud Logging reads a sample hit the project's 60 a minute.
test('a sample makes one Cloud Logging read, and a 429 retried inside its budget stays verifiable', async () => {
const memory = { flagsApplied: { generation: 4, flags: { admitMode: 'reserve', readinessLocal: true, phonePath: 'memory' } } }
const sources = fakeSources({ cell: () => memory, phone: () => ({ phoneAttachDeadlinesDelta: 0, clientAcceptCompletedDelta: 10 }) })
// Sample 6's combined read answers 429 once; sample 8's (the 9th read), on all three attempts.
let combined = 0
let ninth = 0
const runGcloud = async (args, options) => {
if (args[0] === 'logging' && args[2].startsWith('(')) {
combined += 1
if (combined === 6) throw new Error('RESOURCE_EXHAUSTED: 429 Quota exceeded for ReadRequestsPerMinutePerProject')
if (combined >= 9 && ninth < 3) {
ninth += 1
throw new Error('RESOURCE_EXHAUSTED: 429 Quota exceeded for ReadRequestsPerMinutePerProject')
}
}
return sources.runGcloud(args, options)
}
const { state, lines } = await watchFor(12, { ...sources, runGcloud })
assert.equal(state.state, 'watching')
assert.equal(lines.length, 12)
// One combined read per sample: 12, one retry for sample 6 and two for sample 8. Only the
// one-time backgrounds read alone.
assert.equal(combined, 15)
const reads = sources.logReads.map(({ filter }) => filter)
assert.equal(reads.filter((filter) => filter.startsWith('(')).length, 11)
assert.ok(reads.filter((filter) => !filter.startsWith('(')).length <= 3)
const combinedRead = reads.find((filter) => filter.startsWith('('))
for (const part of ['gce_instance', METRICS_EVENT, SUMMARY_EVENT, SEAT_EVENT]) assert.ok(combinedRead.includes(part), part)
// The retried 429 left sample 6 whole; sample 8, which never read, is one gap the next heals.
assert.equal(lines[5].unverifiableStreak, 0)
assert.ok(Object.values(lines[5].rules).every((rule) => rule.status !== 'unverifiable'))
assert.equal(lines[7].unverifiableStreak, 1)
assert.match(lines[7].rules.duplicateSeat.reason, /RESOURCE_EXHAUSTED/)
assert.match(lines[7].rules.director503.reason, /director runtime metrics read failed: RESOURCE_EXHAUSTED/)
assert.equal(lines[8].unverifiableStreak, 0)
})
test('a minute no director instance logged runtime metrics for is unverifiable, not zero', async () => {
@@ -1382,13 +1514,11 @@ async function phoneWatch(phoneMetrics, minutes) {
const lines = []
const config = parseAt(watchValues({ 'state-file': join(directory, 'state.json'), 'segment-minutes': String(minutes) }))
const sources = fakeSources({
cell: () => ({ flagsApplied: { generation: 4, flags: { admitMode: 'reserve', readinessLocal: true, phonePath: 'memory' } } })
cell: () => ({ flagsApplied: { generation: 4, flags: { admitMode: 'reserve', readinessLocal: true, phonePath: 'memory' } } }),
phone: phoneMetrics
})
const runGcloud = async (args) =>
args[2]?.includes('gce_instance') ? { stdout: JSON.stringify(metricsLines(args[2], (at) => [phoneMetrics(at)])) } : sources.runGcloud(args)
const state = await watchSegment(config, {
...sources,
runGcloud,
idToken: 'id',
accessToken: 'access',
audit: {},
+20 -8
View File
@@ -845,14 +845,26 @@ with fewer lines than a single instance logs (20) or at its entry limit is unver
measured background leaves such minutes out (and is not measured with fewer than half of its 10
metered). Coverage is any instance, not every one: an instance whose lines are missing while others
log under-subtracts its re-dials, which can only push toward a trip, never hide one. The instance
split counts 503s from Cloud Run's request log by `labels.instanceId` (the metric names no
instance), less each instance's own scheduled 503s; 503s no instance answered count as one instance of their
own, which can be one of the 2. With a single director the share is half the line, so that one
instance alone never trips between the lines. A judged minute with no instance count is
unverifiable. A single instance id must hold its share for all 3 minutes, so a fault that keeps replacing
instances can pass under the broad line. That read stops at 5,000 lines (the 24 hours to 10-21
peaked at 1,681): truncated or failed while the fleet is between the two lines, the sample is
unverifiable. The same measure is the
split counts each instance's own `/v1/assign` 503s from its runtime-metrics lines
(`assign503sByCauseDelta`, by `labels.instanceId`; the metric names no instance), less that
instance's own scheduled 503s. The fleet's non-drain 503s no instance's lines account for (Cloud
Run's own, a route the director does not count, or an instance that stopped logging) are
`unattributed`: summed over the 3 judged minutes, since an instance's 30 s lines skew against the
fleet's minutes, and over the whole fleet line by themselves they trip (no one instance's stall
explains them; the largest such sum in the 24 hours to 10-21 was 38, against 90). A stalled instance that also stops logging its lines therefore trips, on purpose: the 10-21 07:55Z instance kept logging through its stall. On the recorded 10-21 07:55Z stall this reads 40, 169, 5
on the stalled instance, where Cloud Run's request log read 54, 155, 5. With a single director the
share is half the line, so that one instance alone never trips between the lines. A judged minute
with no instance count is unverifiable. A single instance id must hold its share for all 3
minutes, so a fault that keeps replacing instances can pass under the broad line.
Each sample makes one Cloud Logging read: the director's runtime metrics, its ledger summaries,
the cell's seat-behind-row lines and, while the phone path is watched, the cell's own runtime
metrics, as one OR filter split back client-side. The project allows 60 reads a minute shared by
every watch and tool; on 2026-10-21 18:39Z twelve watches at about five reads a sample drew 429s
until c21 and c22 tripped unverifiable. A read is ~200 lines (1,000 is one page and the limit); one
truncated or failed after its retries makes every log-read rule unverifiable for that sample,
which counts once toward the streak as any unreadable rule did. The 503 background and the phone
background are each read once, on their own. The same measure is the
background and `background-503-per-minute`. Until the
minutes before `flipped-at` are published (3 minutes past it) the 503 rule is pending, which does
not add to the streak. The duplicate read also takes `orca_relay_reserve_ledger_summary`, which