Skip to content

Commit 40cb759

Browse files
committed
feat: Add labels support to LongGaugeMetric.
1 parent fe677b3 commit 40cb759

File tree

3 files changed

+113
-13
lines changed

3 files changed

+113
-13
lines changed

jicoco-metrics/src/main/kotlin/org/jitsi/metrics/LongGaugeMetric.kt

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,52 +32,109 @@ class LongGaugeMetric @JvmOverloads constructor(
3232
/** the namespace (prefix) of this metric */
3333
namespace: String,
3434
/** an optional initial value for this metric */
35-
internal val initialValue: Long = 0L
35+
internal val initialValue: Long = 0L,
36+
/** Label names for this metric. If non-empty, the initial value must be 0 and all get/update calls MUST
37+
* specify values for the labels. Calls to simply get() or set() will fail with an exception. */
38+
val labelNames: List<String> = emptyList()
3639
) : Metric<Long>() {
37-
private val gauge = Gauge.build(name, help).namespace(namespace).create().apply { set(initialValue.toDouble()) }
40+
private val gauge = run {
41+
val builder = Gauge.build(name, help).namespace(namespace)
42+
if (labelNames.isNotEmpty()) {
43+
builder.labelNames(*labelNames.toTypedArray())
44+
if (initialValue != 0L) {
45+
throw IllegalArgumentException("Cannot set an initial value for a labeled gauge")
46+
}
47+
}
48+
builder.create().apply {
49+
if (initialValue != 0L) {
50+
set(initialValue.toDouble())
51+
}
52+
}
53+
}
3854

55+
/** When we have labels [get()] throws an exception and the JSON format is not supported. */
56+
override val supportsJson: Boolean = labelNames.isEmpty()
3957
override fun get() = gauge.get().toLong()
58+
fun get(labels: List<String>) = gauge.labels(*labels.toTypedArray()).get().toLong()
4059

41-
override fun reset() = set(initialValue)
60+
override fun reset() = synchronized(gauge) {
61+
gauge.clear()
62+
if (initialValue != 0L) {
63+
gauge.inc(initialValue.toDouble())
64+
}
65+
}
4266

4367
override fun register(registry: CollectorRegistry) = this.also { registry.register(gauge) }
4468

4569
/**
4670
* Atomically sets the gauge to the given value.
4771
*/
48-
fun set(newValue: Long): Unit = synchronized(gauge) { gauge.set(newValue.toDouble()) }
72+
fun set(newValue: Long, labels: List<String> = emptyList()): Unit = synchronized(gauge) {
73+
if (labels.isEmpty()) {
74+
gauge.set(newValue.toDouble())
75+
} else {
76+
gauge.labels(*labels.toTypedArray()).set(newValue.toDouble())
77+
}
78+
}
4979

5080
/**
5181
* Atomically increments the value of this gauge by one.
5282
*/
53-
fun inc() = synchronized(gauge) { gauge.inc() }
83+
fun inc(labels: List<String> = emptyList()) = synchronized(gauge) {
84+
if (labels.isEmpty()) {
85+
gauge.inc()
86+
} else {
87+
gauge.labels(*labels.toTypedArray()).inc()
88+
}
89+
}
5490

5591
/**
5692
* Atomically decrements the value of this gauge by one.
5793
*/
58-
fun dec() = synchronized(gauge) { gauge.dec() }
94+
fun dec(labels: List<String> = emptyList()) = synchronized(gauge) {
95+
if (labels.isEmpty()) {
96+
gauge.dec()
97+
} else {
98+
gauge.labels(*labels.toTypedArray()).dec()
99+
}
100+
}
59101

60102
/**
61103
* Atomically adds the given value to this gauge, returning the updated value.
62104
*
63105
* @return the updated value
64106
*/
65-
fun addAndGet(delta: Long): Long = synchronized(gauge) {
66-
gauge.inc(delta.toDouble())
67-
return gauge.get().toLong()
107+
fun addAndGet(delta: Long, labels: List<String> = emptyList()): Long = synchronized(gauge) {
108+
return if (labels.isEmpty()) {
109+
gauge.inc(delta.toDouble())
110+
gauge.get().toLong()
111+
} else {
112+
with(gauge.labels(*labels.toTypedArray())) {
113+
inc(delta.toDouble())
114+
get().toLong()
115+
}
116+
}
68117
}
69118

70119
/**
71120
* Atomically increments the value of this gauge by one, returning the updated value.
72121
*
73122
* @return the updated value
74123
*/
75-
fun incAndGet() = addAndGet(1)
124+
fun incAndGet(labels: List<String> = emptyList()) = addAndGet(1, labels)
76125

77126
/**
78127
* Atomically decrements the value of this gauge by one, returning the updated value.
79128
*
80129
* @return the updated value
81130
*/
82-
fun decAndGet() = addAndGet(-1)
131+
fun decAndGet(labels: List<String> = emptyList()) = addAndGet(-1, labels)
132+
133+
/** Remove the child with the given labels (the metric with those labels will stop being emitted) */
134+
fun remove(labels: List<String> = emptyList()) = synchronized(gauge) {
135+
if (labels.isNotEmpty()) {
136+
gauge.remove(*labels.toTypedArray())
137+
}
138+
}
139+
internal fun collect() = gauge.collect()
83140
}

jicoco-metrics/src/main/kotlin/org/jitsi/metrics/MetricsContainer.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,20 @@ open class MetricsContainer @JvmOverloads constructor(
178178
/** the description of the metric */
179179
help: String,
180180
/** the optional initial value of the metric */
181-
initialValue: Long = 0
181+
initialValue: Long = 0,
182+
/** Label names for this metric. If non-empty, the initial value must be 0 and all get/update calls MUST
183+
* specify values for the labels. Calls to simply get() or set() will fail with an exception. */
184+
labelNames: List<String> = emptyList()
182185
): LongGaugeMetric {
183186
if (metrics.containsKey(name)) {
184187
if (checkForNameConflicts) {
185188
throw RuntimeException("Could not register metric '$name'. A metric with that name already exists.")
186189
}
187190
return metrics[name] as LongGaugeMetric
188191
}
189-
return LongGaugeMetric(name, help, namespace, initialValue).apply { metrics[name] = register(registry) }
192+
return LongGaugeMetric(name, help, namespace, initialValue, labelNames).apply {
193+
metrics[name] = register(registry)
194+
}
190195
}
191196

192197
/**

jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,44 @@ class MetricTest : ShouldSpec() {
241241
should("return the initial value") { get() shouldBe initialValue }
242242
}
243243
}
244+
context("With labels") {
245+
with(LongGaugeMetric("testLongGauge", "Help", namespace, labelNames = listOf("l1", "l2"))) {
246+
val labels = listOf("A", "A")
247+
val labels2 = listOf("A", "B")
248+
val labels3 = listOf("B", "B")
249+
250+
get(labels) shouldBe 0
251+
get(labels2) shouldBe 0
252+
get(labels3) shouldBe 0
253+
254+
addAndGet(3, labels) shouldBe 3
255+
get(labels) shouldBe 3
256+
get(labels2) shouldBe 0
257+
get(labels3) shouldBe 0
258+
259+
incAndGet(labels2)
260+
get(labels) shouldBe 3
261+
get(labels2) shouldBe 1
262+
get(labels3) shouldBe 0
263+
264+
incAndGet(labels3) shouldBe 1
265+
266+
addAndGet(2, labels)
267+
get(labels) shouldBe 5
268+
get(labels2) shouldBe 1
269+
get(labels3) shouldBe 1
270+
271+
collect()[0].samples.size shouldBe 3
272+
remove(labels2)
273+
// Down to two sets of labels
274+
collect()[0].samples.size shouldBe 2
275+
get(labels) shouldBe 5
276+
get(labels2) shouldBe 0
277+
get(labels3) shouldBe 1
278+
// Even a get() will summon a child
279+
collect()[0].samples.size shouldBe 3
280+
}
281+
}
244282
}
245283
context("Creating an InfoMetric") {
246284
context("with a value different from its name") {

0 commit comments

Comments
 (0)