Skip to content

Commit

Permalink
feat: Add labels support to BooleanMetric.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrozev committed Feb 18, 2025
1 parent 40cb759 commit b44005b
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
50 changes: 43 additions & 7 deletions jicoco-metrics/src/main/kotlin/org/jitsi/metrics/BooleanMetric.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,65 @@ class BooleanMetric @JvmOverloads constructor(
/** the namespace (prefix) of this metric */
namespace: String,
/** an optional initial value for this metric */
internal val initialValue: Boolean = false
internal val initialValue: Boolean = false,
/** Label names for this metric. If non-empty, the initial value must be false and all get/update calls MUST
* specify values for the labels. Calls to simply get() or set() will fail with an exception. */
val labelNames: List<String> = emptyList()
) : Metric<Boolean>() {
private val gauge =
Gauge.build(name, help).namespace(namespace).create().apply { set(if (initialValue) 1.0 else 0.0) }
private val gauge = run {
val builder = Gauge.build(name, help).namespace(namespace)
if (labelNames.isNotEmpty()) {
builder.labelNames(*labelNames.toTypedArray())
if (initialValue) {
throw IllegalArgumentException("Cannot set an initial value for a labeled gauge")
}
}
builder.create().apply {
if (initialValue) {
set(1.0)
}
}
}

override val supportsJson: Boolean = labelNames.isEmpty()
override fun get() = gauge.get() != 0.0
fun get(labels: List<String>) = gauge.labels(*labels.toTypedArray()).get() != 0.0

override fun reset() = set(initialValue)
override fun reset() = synchronized(gauge) {
gauge.clear()
if (initialValue) {
gauge.set(1.0)
}
}

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

/**
* Atomically sets the gauge to the given value.
*/
fun set(newValue: Boolean): Unit = synchronized(gauge) { gauge.set(if (newValue) 1.0 else 0.0) }
fun set(newValue: Boolean, labels: List<String> = emptyList()): Unit = synchronized(gauge) {
if (labels.isEmpty()) {
gauge.set(if (newValue) 1.0 else 0.0)
} else {
gauge.labels(*labels.toTypedArray()).set(if (newValue) 1.0 else 0.0)
}
}

/**
* Atomically sets the gauge to the given value, returning the updated value.
*
* @return the updated value
*/
fun setAndGet(newValue: Boolean): Boolean = synchronized(gauge) {
gauge.set(if (newValue) 1.0 else 0.0)
fun setAndGet(newValue: Boolean, labels: List<String> = emptyList()): Boolean = synchronized(gauge) {
set(newValue, labels)
return newValue
}

/** Remove the child with the given labels (the metric with those labels will stop being emitted) */
fun remove(labels: List<String> = emptyList()) = synchronized(gauge) {
if (labels.isNotEmpty()) {
gauge.remove(*labels.toTypedArray())
}
}
internal fun collect() = gauge.collect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,20 @@ open class MetricsContainer @JvmOverloads constructor(
/** the description of the metric */
help: String,
/** the optional initial value of the metric */
initialValue: Boolean = false
initialValue: Boolean = false,
/** 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 set() will fail with an exception. */
labelNames: List<String> = emptyList()
): BooleanMetric {
if (metrics.containsKey(name)) {
if (checkForNameConflicts) {
throw RuntimeException("Could not register metric '$name'. A metric with that name already exists.")
}
return metrics[name] as BooleanMetric
}
return BooleanMetric(name, help, namespace, initialValue).apply { metrics[name] = register(registry) }
return BooleanMetric(name, help, namespace, initialValue, labelNames).apply {
metrics[name] = register(registry)
}
}

/**
Expand Down
38 changes: 38 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 @@ -61,6 +61,44 @@ class MetricTest : ShouldSpec() {
should("return true") { get() shouldBe true }
}
}
context("With labels") {
with(BooleanMetric("testBoolean", "Help", namespace, labelNames = listOf("l1", "l2"))) {
val labels = listOf("A", "A")
val labels2 = listOf("A", "B")
val labels3 = listOf("B", "B")

get(labels) shouldBe false
get(labels2) shouldBe false
get(labels3) shouldBe false

set(true, labels)
get(labels) shouldBe true
get(labels2) shouldBe false
get(labels3) shouldBe false

set(true, labels2)
get(labels) shouldBe true
get(labels2) shouldBe true
get(labels3) shouldBe false

setAndGet(true, labels3) shouldBe true

set(false, labels)
get(labels) shouldBe false
get(labels2) shouldBe true
get(labels3) shouldBe true

collect()[0].samples.size shouldBe 3
remove(labels2)
// Down to two sets of labels
collect()[0].samples.size shouldBe 2
get(labels) shouldBe false
get(labels2) shouldBe false
get(labels3) shouldBe true
// Even a get() will summon a child
collect()[0].samples.size shouldBe 3
}
}
}
context("Creating a DoubleGaugeMetric") {
context("with the default initial value") {
Expand Down

0 comments on commit b44005b

Please sign in to comment.