From b27d91e484d38fdf0c20dcfa4ddf1fb786e14c7b Mon Sep 17 00:00:00 2001 From: vitostrokov <57577597+vitostrokov@users.noreply.github.com> Date: Sun, 18 Oct 2026 08:29:40 +0100 Subject: [PATCH] Don't count requests rejected before routing as service hits Nginx answers some requests, e.g. ones with a malformed request line, with 400 before the server-level `set $dstack_replica_hit 0` runs, so the flag is logged empty. The stats collector read these 4-field lines as pre-0.19.11 logs and counted them as replica hits, so stray internet traffic could scale a service up from zero. Tell the two cases apart by the trailing separator that the current log format leaves when the flag is empty, and treat an empty flag as no hit. Co-Authored-By: Claude Opus 5.5 --- .../_internal/proxy/gateway/services/stats.py | 6 +++++- .../proxy/gateway/services/test_stats.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/dstack/_internal/proxy/gateway/services/stats.py b/src/dstack/_internal/proxy/gateway/services/stats.py index 358eff451a..f29707b7eb 100644 --- a/src/dstack/_internal/proxy/gateway/services/stats.py +++ b/src/dstack/_internal/proxy/gateway/services/stats.py @@ -121,10 +121,14 @@ def _read_access_log(self, after: datetime.datetime) -> Iterable[LogEntry]: line = self._file.readline() if not line: break - cells = line.split() + cells = line.rstrip("\n").split(" ") if len(cells) == 4: # compatibility with pre-0.19.11 logs cells.append("0" if cells[2] in ["403", "404"] else "1") timestamp_str, host, status, request_time, dstack_replica_hit = cells + if not dstack_replica_hit: + # Nginx rejected the request before the server-level `set`, e.g. a + # malformed request answered with 400, so it never reached a replica + dstack_replica_hit = "0" timestamp = datetime.datetime.fromisoformat(timestamp_str) if timestamp < after: continue diff --git a/src/tests/_internal/proxy/gateway/services/test_stats.py b/src/tests/_internal/proxy/gateway/services/test_stats.py index f949178a4b..0873e64c50 100644 --- a/src/tests/_internal/proxy/gateway/services/test_stats.py +++ b/src/tests/_internal/proxy/gateway/services/test_stats.py @@ -92,6 +92,22 @@ }, id="ignores-replica-not-hit", ), + pytest.param( + # Nginx rejected the 400 requests before setting $dstack_replica_hit, + # so the flag is empty and the lines end with a space + "2024-12-06T12:08:01+00:00 srv.gtw.test 200 0.100 1\n" + "2024-12-06T12:08:02+00:00 srv.gtw.test 400 0.001 \n" + "2024-12-06T12:08:02+00:00 srv.gtw.test 400 0.002 \n" + "2024-12-06T12:08:03+00:00 srv.gtw.test 200 0.300 1\n", + { + "srv.gtw.test": { + 30: Stat(requests=0, request_time=0.0), + 60: Stat(requests=0, request_time=0.0), + 300: Stat(requests=2, request_time=0.2), + }, + }, + id="ignores-requests-rejected-before-routing", + ), pytest.param( dedent( """