diff --git a/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/InfoMetric.kt b/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/InfoMetric.kt index c101b5d..2a2bd83 100644 --- a/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/InfoMetric.kt +++ b/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/InfoMetric.kt @@ -24,7 +24,7 @@ import io.prometheus.client.Info * In the Prometheus exposition format, these are shown as labels of either a custom metric (OpenMetrics) * or a [Gauge][io.prometheus.client.Gauge] (0.0.4 plain text). */ -class InfoMetric( +class InfoMetric @JvmOverloads constructor( /** the name of this metric */ override val name: String, /** the description of this metric */ @@ -32,13 +32,41 @@ class InfoMetric( /** the namespace (prefix) of this metric */ namespace: String, /** the value of this info metric */ - internal val value: String + internal val value: String = "", + /** Label names for this metric */ + val labelNames: List = emptyList() ) : Metric() { - private val info = Info.build(name, help).namespace(namespace).create().apply { info(name, value) } + private val info = run { + val builder = Info.build(name, help).namespace(namespace) + if (labelNames.isNotEmpty()) { + builder.labelNames(*labelNames.toTypedArray()) + } + builder.create().apply { + if (labelNames.isEmpty()) { + info(name, value) + } + } + } - override fun get() = value + override fun get() = if (labelNames.isEmpty()) value else throw UnsupportedOperationException() + fun get(labels: List = emptyList()) = + if (labels.isEmpty()) value else info.labels(*labels.toTypedArray()).get()[name] - override fun reset() = info.info(name, value) + override fun reset() = if (labelNames.isEmpty()) info.info(name, value) else info.clear() override fun register(registry: CollectorRegistry) = this.also { registry.register(info) } + + /** Remove the child with the given labels (the metric with those labels will stop being emitted) */ + fun remove(labels: List = emptyList()) = synchronized(info) { + if (labels.isNotEmpty()) { + info.remove(*labels.toTypedArray()) + } + } + + fun set(labels: List, value: String) { + if (labels.isNotEmpty()) { + info.labels(*labels.toTypedArray()).info(name, value) + } + } + internal fun collect() = info.collect() } diff --git a/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/MetricsContainer.kt b/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/MetricsContainer.kt index 9d0912b..c9511e1 100644 --- a/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/MetricsContainer.kt +++ b/jicoco-metrics/src/main/kotlin/org/jitsi/metrics/MetricsContainer.kt @@ -238,7 +238,10 @@ open class MetricsContainer @JvmOverloads constructor( /** the description of the metric */ help: String, /** the value of the metric */ - value: String + value: String, + /** Label names for this metric. If non-empty, the initial value must be 0 and all get/update calls MUST + * specify values for the labels. Calls to simply get() or inc() will fail with an exception. */ + labelNames: List = emptyList() ): InfoMetric { if (metrics.containsKey(name)) { if (checkForNameConflicts) { @@ -246,7 +249,7 @@ open class MetricsContainer @JvmOverloads constructor( } return metrics[name] as InfoMetric } - return InfoMetric(name, help, namespace, value).apply { metrics[name] = register(registry) } + return InfoMetric(name, help, namespace, value, labelNames).apply { metrics[name] = register(registry) } } fun registerHistogram( diff --git a/jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt b/jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt index 4f6c05f..8c0b99e 100644 --- a/jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt +++ b/jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt @@ -325,6 +325,34 @@ class MetricTest : ShouldSpec() { should("return the correct value") { get() shouldBe value } } } + context("With labels") { + with(InfoMetric("testInfo", "Help", namespace, labelNames = listOf("l1", "l2"))) { + val labels = listOf("A", "A") + val labels2 = listOf("A", "B") + val labels3 = listOf("B", "B") + + shouldThrow { get() } + + get(labels) shouldBe null + get(labels2) shouldBe null + + set(labels, "AA") + get(labels) shouldBe "AA" + get(labels2) shouldBe null + + set(labels3, "BB") + get(labels) shouldBe "AA" + get(labels3) shouldBe "BB" + + collect()[0].samples.size shouldBe 3 + remove(labels2) + // Down to two sets of labels + collect()[0].samples.size shouldBe 2 + get(labels) shouldBe "AA" + get(labels2) shouldBe null + get(labels3) shouldBe "BB" + } + } } context("HistogramMetric") { val namespace = "namespace"