Skip to content

Commit

Permalink
feat: suppress metrics for unreachable devices instead reporting NaN
Browse files Browse the repository at this point in the history
  • Loading branch information
easimon committed Apr 13, 2023
1 parent 0dfc48c commit 638e413
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 1 deletion.
1 change: 0 additions & 1 deletion tado-exporter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import click.dobel.tado.api.Zone
import click.dobel.tado.api.ZoneState
import click.dobel.tado.exporter.apiclient.TadoConfigurationProperties
import click.dobel.tado.exporter.metrics.TadoMeterFactory
import click.dobel.tado.exporter.metrics.ValueFilteringCollectorRegistry
import click.dobel.tado.util.aop.CallLoggingInterceptor
import com.github.benmanes.caffeine.cache.Caffeine
import io.micrometer.core.instrument.Meter
import io.micrometer.core.instrument.config.MeterFilter
import io.micrometer.core.instrument.config.MeterFilterReply
import io.prometheus.client.CollectorRegistry
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.EnableCaching
Expand Down Expand Up @@ -72,4 +74,9 @@ class TadoExporterConfiguration {

@Bean
fun aopLogger() = CallLoggingInterceptor()

@Bean
fun collectorRegistry(configProperties: TadoConfigurationProperties): CollectorRegistry {
return ValueFilteringCollectorRegistry(Double.NaN, true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package click.dobel.tado.exporter.metrics

import io.prometheus.client.Collector.MetricFamilySamples
import io.prometheus.client.CollectorRegistry
import io.prometheus.client.Predicate
import java.util.Enumeration

class ValueFilteringCollectorRegistry(
private val blockedValue: Double,
autoDescribe: Boolean = false,
) : CollectorRegistry(autoDescribe) {

private val blockNaNs = blockedValue.isNaN()
private val blockInfinite = blockedValue.isInfinite()
internal val isValid: (Double) -> Boolean = {
// special cases for NaN and Infinity: can't be compared using equality.
!(blockInfinite && it.isInfinite()) &&
!(blockNaNs && it.isNaN()) &&
(blockedValue != it)
}

override fun metricFamilySamples(): Enumeration<MetricFamilySamples> {
return super.metricFamilySamples().filterValidValues()
}

override fun filteredMetricFamilySamples(includedNames: MutableSet<String>?): Enumeration<MetricFamilySamples> {
return super.filteredMetricFamilySamples(includedNames).filterValidValues()
}

override fun filteredMetricFamilySamples(sampleNameFilter: Predicate<String>?): Enumeration<MetricFamilySamples> {
return super.filteredMetricFamilySamples(sampleNameFilter).filterValidValues()
}

private fun Enumeration<MetricFamilySamples>.filterValidValues(): Enumeration<MetricFamilySamples> {
val iterator = asSequence()
.map { MetricFamilySamples(it.name, it.unit, it.type, it.help, it.samples.filterValid()) }
.filterNonEmpty()
.iterator()

return object : Enumeration<MetricFamilySamples> {
override fun hasMoreElements(): Boolean {
return iterator.hasNext()
}

override fun nextElement(): MetricFamilySamples {
return iterator.next()
}
}
}

private fun Sequence<MetricFamilySamples>.filterNonEmpty(): Sequence<MetricFamilySamples> {
return this.filter {
it.samples.isNotEmpty()
}
}

private fun List<MetricFamilySamples.Sample>.filterValid(): List<MetricFamilySamples.Sample> {
return this.filter {
isValid(it.value)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package click.dobel.tado.exporter.metrics

import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe

class ValueFilteringCollectorRegistryTest : FreeSpec({

"ValueFilteringCollectorRegistry" - {

"configured with NaN as invalid value" - {
val registry = ValueFilteringCollectorRegistry(Double.NaN)

"should not accept NaN" { registry.isValid(Double.NaN) shouldBe false }
"should accept 0.0" { registry.isValid(0.0) shouldBe true }
"should accept 5.0" { registry.isValid(5.0) shouldBe true }
"should accept Infinity" { registry.isValid(Double.POSITIVE_INFINITY) shouldBe true }
"should accept -Infinity" { registry.isValid(Double.NEGATIVE_INFINITY) shouldBe true }
}

"configured with 0.0 as invalid value" - {
val registry = ValueFilteringCollectorRegistry(0.0)

"should not accept 0.0" { registry.isValid(0.0) shouldBe false }
"should accept 5.0" { registry.isValid(5.0) shouldBe true }
"should accept NaN" { registry.isValid(Double.NaN) shouldBe true }
"should accept Infinity" { registry.isValid(Double.POSITIVE_INFINITY) shouldBe true }
"should accept -Infinity" { registry.isValid(Double.NEGATIVE_INFINITY) shouldBe true }
}
"configured with Infinity as invalid value" - {
val registry = ValueFilteringCollectorRegistry(Double.NEGATIVE_INFINITY)

"should not accept Infinity" { registry.isValid(Double.POSITIVE_INFINITY) shouldBe false }
"should not accept -Infinity" { registry.isValid(Double.NEGATIVE_INFINITY) shouldBe false }
"should accept 0.0" { registry.isValid(0.0) shouldBe true }
"should accept 5.0" { registry.isValid(5.0) shouldBe true }
"should accept NaN" { registry.isValid(Double.NaN) shouldBe true }
}
}
})

0 comments on commit 638e413

Please sign in to comment.