Skip to content

Commit

Permalink
feat: Add labels support to InfoMetric.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrozev committed Feb 18, 2025
1 parent b44005b commit 82c6af5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 7 deletions.
38 changes: 33 additions & 5 deletions jicoco-metrics/src/main/kotlin/org/jitsi/metrics/InfoMetric.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,49 @@ 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 */
help: String,
/** 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<String> = emptyList()
) : Metric<String>() {
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<String> = 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<String> = emptyList()) = synchronized(info) {
if (labels.isNotEmpty()) {
info.remove(*labels.toTypedArray())
}
}

fun set(labels: List<String>, value: String) {
if (labels.isNotEmpty()) {
info.labels(*labels.toTypedArray()).info(name, value)
}
}
internal fun collect() = info.collect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,18 @@ 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<String> = emptyList()
): InfoMetric {
if (metrics.containsKey(name)) {
if (checkForNameConflicts) {
throw RuntimeException("Could not register metric '$name'. A metric with that name already exists.")
}
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(
Expand Down
28 changes: 28 additions & 0 deletions jicoco-metrics/src/test/kotlin/org/jitsi/metrics/MetricTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exception> { 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"
Expand Down

0 comments on commit 82c6af5

Please sign in to comment.