From acc3657fb7df2866982eed9cbe1a28493cf5844e Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Thu, 17 Sep 2026 22:06:15 +0200 Subject: [PATCH] Let a leaf read from a block Every runtime value the servers export has no pragma to declare it - the five of #apptiveGrid, the two of the worker, the 23 of #soil. They all need a leaf whose value is worked out on the spot, and the tree could already hold one: the block is the object and evaluating it is the read. Spelling that out at the call site works but reads like a trick, and there are some thirty of them to write. CanopyAccessor reading: [ ... ] says what it is instead. Co-Authored-By: Claude Opus 5 (1M context) --- .../CanopyAccessorTest.class.st | 12 ++++++++++ source/Canopy-Core/CanopyAccessor.class.st | 11 +++++++++ .../CanopyMetricsVisitorTest.class.st | 24 +++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/source/Canopy-Core-Tests/CanopyAccessorTest.class.st b/source/Canopy-Core-Tests/CanopyAccessorTest.class.st index eba3b93..21b06b4 100644 --- a/source/Canopy-Core-Tests/CanopyAccessorTest.class.st +++ b/source/Canopy-Core-Tests/CanopyAccessorTest.class.st @@ -5,6 +5,18 @@ Class { #package : 'Canopy-Core-Tests' } +{ #category : 'as yet unclassified' } +CanopyAccessorTest >> testALeafCanReadFromABlockInsteadOfAComponent [ + "Runtime values have no pragma to declare them - the block is the object and evaluating it is + the read. The value stays live, which is the whole point: it is worked out per scrape." + | count leaf | + count := 0. + leaf := CanopyAccessor reading: [ count ]. + self assert: leaf value equals: 0. + count := 42. + self assert: leaf value equals: 42 +] + { #category : 'as yet unclassified' } CanopyAccessorTest >> testAcceptCanopySkipsWriteOnlyAccessorsInsteadOfCrashing [ "Found 2026-07-21 while sanity-checking format: over the real Canopy systemMetrics diff --git a/source/Canopy-Core/CanopyAccessor.class.st b/source/Canopy-Core/CanopyAccessor.class.st index 4a921ae..806026c 100644 --- a/source/Canopy-Core/CanopyAccessor.class.st +++ b/source/Canopy-Core/CanopyAccessor.class.st @@ -10,6 +10,17 @@ Class { #package : 'Canopy-Core' } +{ #category : 'instance creation' } +CanopyAccessor class >> reading: aBlock [ + "A leaf whose value is worked out on the spot instead of being read off a component: the + block is the object and evaluating it is the read. That is how a value nobody declared with + a pragma gets into the tree - every runtime metric of the servers is built that way." + ^ self new + object: aBlock; + readSelector: #value; + yourself +] + { #category : 'visiting' } CanopyAccessor >> acceptCanopy: aVisitor [ "Write-only accessors (no readSelector) contribute nothing to export/formatting - diff --git a/source/Canopy-Metrics-Tests/CanopyMetricsVisitorTest.class.st b/source/Canopy-Metrics-Tests/CanopyMetricsVisitorTest.class.st index 8e5a744..2704a1e 100644 --- a/source/Canopy-Metrics-Tests/CanopyMetricsVisitorTest.class.st +++ b/source/Canopy-Metrics-Tests/CanopyMetricsVisitorTest.class.st @@ -141,6 +141,30 @@ CanopyMetricsVisitorTest >> testAFamilyIsWrittenAsHelpTypeAndOneLine [ self assert: (lines third substrings last allSatisfy: [ :each | each isDigit ]) ] +{ #category : 'as yet unclassified' } +CanopyMetricsVisitorTest >> testABlockBackedMetricExportsLikeAnyOther [ + "The shape every runtime metric of the servers will take: no pragma anywhere, the block hands + back a full holder, and the exported line is indistinguishable from a declared one - inherited + label included. All of #apptiveGrid and the 23 #soil values are built like this today." + | count root soil | + count := 41. + root := CanopyBranchNode new. + soil := CanopyBranchNode new. + root at: #soil put: soil. + root labelAt: #stage put: 'production'. + soil + at: #objectsSerialized + put: (CanopyAccessor reading: [ CanopyMetric new + type: #counter; + description: 'the number of serialized objects'; + value: count ]). + self assert: ((CanopyPrometheusVisitor new format: root) lines last + beginsWith: 'soil_objectsSerialized{stage="production"} 41 '). + count := 42. + self assert: ((CanopyPrometheusVisitor new format: root) lines last + beginsWith: 'soil_objectsSerialized{stage="production"} 42 ') +] + { #category : 'as yet unclassified' } CanopyMetricsVisitorTest >> testAMetricWithoutLabelsIsWrittenWithoutBraces [ "An empty label set used to come out as name{} 1024, because the check was for nil and the