Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions source/Canopy-Metrics-Tests/CanopyDeclaredMetricTest.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"
The runtime metrics a server declares on a branch - #gauge:description:reading: and its
siblings - and what a scrape makes of them.
"
Class {
#name : 'CanopyDeclaredMetricTest',
#superclass : 'TestCase',
#instVars : [
'root',
'branch'
],
#category : 'Canopy-Metrics-Tests',
#package : 'Canopy-Metrics-Tests'
}

{ #category : 'running' }
CanopyDeclaredMetricTest >> scrape [
^ CanopyPrometheusVisitor new format: root
]

{ #category : 'running' }
CanopyDeclaredMetricTest >> setUp [
super setUp.
root := CanopyBranchNode new.
branch := root /+ #server
]

{ #category : 'tests' }
CanopyDeclaredMetricTest >> testACounterIsWrittenAsOne [
branch counter: #requests description: 'requests served' reading: [ 3 ].
self assert: (self scrape includesSubstring: '# TYPE server_requests counter').
self assert: (self scrape includesSubstring: 'server_requests 3 ')
]

{ #category : 'tests' }
CanopyDeclaredMetricTest >> testAGaugeIsReadAtEveryScrape [
| count |
count := 1.
branch gauge: #open description: 'open databases' reading: [ count ].
self assert: (self scrape includesSubstring: '# HELP server_open open databases').
self assert: (self scrape includesSubstring: 'server_open 1 ').
count := 2.
self assert: (self scrape includesSubstring: 'server_open 2 ')
]

{ #category : 'tests' }
CanopyDeclaredMetricTest >> testALabelledGaugeWritesALinePerKey [
branch
gauge: #owing
label: #task
description: 'databases owing a task'
reading: [ { #backup -> 4. #repair -> 0 } asDictionary ].
self assert: (self scrape includesSubstring: 'server_owing{task="backup"} 4 ').
self assert: (self scrape includesSubstring: 'server_owing{task="repair"} 0 ')
]

{ #category : 'tests' }
CanopyDeclaredMetricTest >> testAValueThatIsNotKnownIsLeftOut [
"a gap, not a line that says nil or a zero that says something is known"
branch gauge: #counted description: 'not counted yet' reading: [ nil ].
branch gauge: #owing label: #task description: 'not counted yet' reading: [ nil ].
branch gauge: #known description: 'known' reading: [ 7 ].
self deny: (self scrape includesSubstring: 'server_counted').
self deny: (self scrape includesSubstring: 'server_owing').
self assert: (self scrape includesSubstring: 'server_known 7 ')
]

{ #category : 'tests' }
CanopyDeclaredMetricTest >> testDeclaringAgainReplaces [
branch gauge: #open description: 'open databases' reading: [ 1 ].
branch gauge: #open description: 'open databases' reading: [ 2 ].
self deny: (self scrape includesSubstring: 'server_open 1 ').
self assert: (self scrape includesSubstring: 'server_open 2 ')
]
63 changes: 63 additions & 0 deletions source/Canopy-Metrics/CanopyBranchNode.extension.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
Extension { #name : 'CanopyBranchNode' }

{ #category : '*Canopy-Metrics' }
CanopyBranchNode >> counter: aKey description: aString reading: aBlock [
"A counter under aKey, read by evaluating aBlock at every scrape. See
#metric:at:reading:"
^ self
metric: (CanopyMetric new
type: #counter;
description: aString;
yourself)
at: aKey
reading: aBlock
]

{ #category : '*Canopy-Metrics' }
CanopyBranchNode >> gauge: aKey description: aString reading: aBlock [
"A gauge under aKey, read by evaluating aBlock at every scrape. See
#metric:at:reading:"
^ self
metric: (CanopyMetric new
type: #gauge;
description: aString;
yourself)
at: aKey
reading: aBlock
]

{ #category : '*Canopy-Metrics' }
CanopyBranchNode >> gauge: aKey label: aSymbol description: aString reading: aBlock [
"A gauge with one line per key of the dictionary aBlock answers, the key written under
the label aSymbol. See #metric:at:reading:"
^ self
metric: (CanopyMetricMap new
type: #gauge;
description: aString;
labelName: aSymbol;
yourself)
at: aKey
reading: aBlock
]

{ #category : '*Canopy-Metrics' }
CanopyBranchNode >> metric: aMetric at: aKey reading: aBlock [
"The one shape every runtime metric of the servers had been spelled out in by hand: an
accessor whose block builds the metric and reads its value. Here it is said once, and a
server says only what is its own - the name, what it means, and where the number comes
from.

The metric is copied for every read rather than filled in place: two scrapes at once would
otherwise write into the same holder. A block that answers nil leaves the leaf out of the
scrape - see CanopyMetric>>readingsDo: - and one that raises costs this leaf and not the
scrape, see CanopyPrometheusVisitor>>visitMetric:.

Declaring the same key again replaces the leaf, so a server that is set up twice does not
have to know it was set up before."
^ self
at: aKey
put: (CanopyAccessor reading: [
aMetric copy
value: aBlock value;
yourself ])
]
7 changes: 6 additions & 1 deletion source/Canopy-Metrics/CanopyMetric.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ CanopyMetric >> labels [
{ #category : 'enumerating' }
CanopyMetric >> readingsDo: aBlock [
"One reading: the labels this metric knows itself, and its value. Whatever the tree adds is
merged in by the export, which is the only place that knows both sides."
merged in by the export, which is the only place that knows both sides.

None when there is no value. A number that is not known yet - not counted since the image
started, not read because what it is read from is closed - is a gap in the series, and
writing it would put the word nil on the scrape, or a zero that says something is known."
self value ifNil: [ ^ self ].
aBlock value: labels value: self value
]

Expand Down
4 changes: 3 additions & 1 deletion source/Canopy-Metrics/CanopyMetricMap.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ CanopyMetricMap >> labelName: aSymbol [

{ #category : 'enumerating' }
CanopyMetricMap >> readingsDo: aBlock [
"One reading per key: the labels this metric carries, plus the key under the declared name."
"One reading per key: the labels this metric carries, plus the key under the declared name.
None at all when there is no map, for the reason CanopyMetric>>readingsDo: gives"
self value ifNil: [ ^ self ].
self value keysAndValuesDo: [ :key :each |
aBlock
value: (self labels copy
Expand Down
Loading