From 00c32f8f7af998b9340c8b08ea014e10e3b32b1d Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Thu, 17 Sep 2026 22:10:56 +0200 Subject: [PATCH] Start the Zinc counter where production starts Comparing both exports line by line turned up two differences in the counter, one of them not a difference but a defect: a fresh counter answered nil for the average duration, and nil is not a value a Prometheus scrape can parse. Before the first request, Canopy's endpoint would have served a broken line. The second: pharo-metrics seeds fifteen status codes with zero, so those series exist from the first scrape. Canopy started with an empty map, so zinc_httpResponses was absent until the first response of a kind arrived - and a counter that starts existing halfway through the day reads differently from one that starts at zero. Both now start where production starts. That makes the nil guard in adjustDuration: unreachable, and with it the first request no longer sets the baseline but moves the average by a hundredth - which is what the servers have been exporting all along. The test said it mirrored pharo-metrics and did not; it does now. Co-Authored-By: Claude Opus 5 (1M context) --- .../CanopyZincCounterTest.class.st | 28 +++++++++++++++---- .../Canopy-Metrics/CanopyZincCounter.class.st | 14 +++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/source/Canopy-Metrics-Tests/CanopyZincCounterTest.class.st b/source/Canopy-Metrics-Tests/CanopyZincCounterTest.class.st index 3c6e623..9754eec 100644 --- a/source/Canopy-Metrics-Tests/CanopyZincCounterTest.class.st +++ b/source/Canopy-Metrics-Tests/CanopyZincCounterTest.class.st @@ -6,14 +6,30 @@ Class { } { #category : 'as yet unclassified' } -CanopyZincCounterTest >> testHandleAnnouncementAdjustsDurationWithExponentialMovingAverage [ - "Mirrors ZincCounter (pharo-metrics, out of scope otherwise): first duration sets the - baseline, subsequent durations are folded in with 99% weight on the running average." +CanopyZincCounterTest >> testAFreshCounterAlreadyCarriesTheUsualStatusCodes [ + "Parity with what the servers export today: the status series exist from the first scrape at + zero instead of appearing with the first response of that kind, and the average duration is a + number rather than nil - which is not even valid exposition." + | counter | + counter := CanopyZincCounter new. + self assert: counter duration equals: 0. + self assert: (counter responses at: 200) equals: 0. + self assert: (counter responses at: 500) equals: 0. + self assert: counter responses size equals: 15 +] + +{ #category : 'as yet unclassified' } +CanopyZincCounterTest >> testHandleAnnouncementFoldsDurationsIntoARunningAverage [ + "Mirrors ZincCounter (pharo-metrics): the average starts at zero and every duration is folded + in with 99% weight on the running value. The first request therefore does not set a baseline - + it moves the average by a hundredth, which is what the servers have been exporting all along. + Canopy did set the baseline from the first request, because its average started as nil; the + nil guard in adjustDuration: is unreachable now." [ CanopyZincCounter reset. - CanopyZincCounter instance handleAnnouncement: (CanopyZincTestAnnouncement new responseCode: 200 duration: 10; yourself). + CanopyZincCounter instance handleAnnouncement: (CanopyZincTestAnnouncement new responseCode: 200 duration: 1000; yourself). self assert: CanopyZincCounter instance duration equals: 10. - CanopyZincCounter instance handleAnnouncement: (CanopyZincTestAnnouncement new responseCode: 200 duration: 20; yourself). - self assert: CanopyZincCounter instance duration equals: (((99 * 10) + 20) / 100) asInteger ] + CanopyZincCounter instance handleAnnouncement: (CanopyZincTestAnnouncement new responseCode: 200 duration: 1000; yourself). + self assert: CanopyZincCounter instance duration equals: (((99 * 10) + 1000) / 100) asInteger ] ensure: [ CanopyZincCounter reset ] ] diff --git a/source/Canopy-Metrics/CanopyZincCounter.class.st b/source/Canopy-Metrics/CanopyZincCounter.class.st index 223cc09..c4ff648 100644 --- a/source/Canopy-Metrics/CanopyZincCounter.class.st +++ b/source/Canopy-Metrics/CanopyZincCounter.class.st @@ -83,7 +83,19 @@ CanopyZincCounter >> increaseStatusCode: statusCode [ CanopyZincCounter >> initialize [ super initialize. requests := 0. - responses := Dictionary new + duration := 0. + responses := Dictionary new. + self initializeResponses +] + +{ #category : 'initialization' } +CanopyZincCounter >> initializeResponses [ + "Seed the status codes the servers actually answer, so their series exist from the first + scrape instead of appearing out of nowhere with the first response of that kind. Same list + as pharo-metrics seeds today - a counter that starts at zero reads differently from one that + starts existing halfway through the day." + #( 200 201 202 204 301 302 400 401 403 404 405 500 502 503 504 ) do: [ :code | + responses at: code put: 0 ] ] { #category : 'installing' }