Metrics with Micrometer metrics

Estimated reading time: 3 minutes

The Metrics feature allows you to configure the Metrics to get useful information about the server and incoming requests. This implementation uses Micrometer Metrics which requires a JRE 8 or higher.

This feature is defined in the class io.ktor.metrics.DropwizardMetrics in the artifact io.ktor:ktor-metrics-micrometer:$ktor_version.
dependencies { implementation "io.ktor:ktor-metrics-micrometer:$ktor_version" }
dependencies { implementation("io.ktor:ktor-metrics-micrometer:$ktor_version") }
<project> ... <dependencies> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-metrics-micrometer</artifactId> <version>${ktor.version}</version> <scope>compile</scope> </dependency> </dependencies> </project>

Exposed Metrics

Depending on your backing timeline database, the names of this metrics my vary to follow the naming conventions.

ktor.http.server.requests.active

The active gauge counts the amount of concurrent http requests to the server. There are no tags for this metric.

ktor.http.server.requests

This timer measures the time of each request. This feature provides the follwing tags for this timer:

  • address: <host>:<port> of the url requested by the client
  • method: the http methog (e.g. GET or POST)
  • route: the ktor route handling the requests path (e.g. /vehicles/{id}/tires/{tire} for the path /vehicles/23847/tires/frontright).
  • status: The http status code of the response sent to the client (e.g. 200 or 404).
  • exception: When the handler throws an Exception or Throwable before responding to the client, the class name of the exception or n/a otherwise. Exceptions that are thrown by the handler after responding to the client are not recognized by this feature.

Installing

The Metrics feature requires you to specify a MeterRegistry at installation. For test purposes you can use the SimpleMeterRegistry, for more productive environments you can choose any registry depending on your timeline database vendor.

install(MicrometerMetrics) {
   registry = SimpleMeterRegistry()
}

Meter Binders

Micrometer provides some low level metrics. These are provided via MeterBinders. By default this feature installs a list of Metrics but you can add your own or provide an empty list if you don’t want to install this feature install any MeterBinder.

install(MicrometerMetrics) {
   registry = SimpleMeterRegistry()
   meterBinders = listOf(
            ClassLoaderMetrics(),
            JvmMemoryMetrics(),
            JvmGcMetrics(),
            ProcessorMetrics(),
            JvmThreadMetrics(),
            FileDescriptorMetrics()
   )
}

Distribution Statistic Configuration

Micrometer provides various ways to configure and expose histograms. You can expose either (client side) percentile or the histogram counters (and the timeline database calculates the percentile on the server side). While percentile are supported by all backends, they are more expensive in memory footprint) and cannot be aggregated over several dimensions. Histogram counters can be aggregated but not all backends support them. The full documentation can be found here.

By default the timers provided by this feature expose the 50%, 90%, 95% and 99% percentile. To change this configuration you can configure a DistributionStatisticConfig that is applied to all timers of this feature.

install(MicrometerMeterFeature) {
    registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
    
    distributionStatisticConfig = DistributionStatisticConfig.Builder()
                .percentilesHistogram(true)
                .maximumExpectedValue(Duration.ofSeconds(20).toNanos())
                .sla(
                    Duration.ofMillis(100).toNanos(),
                    Duration.ofMillis(500).toNanos()
                )
                .build()
}

Customizing Timers

To customize the tags for each timer, you can configure a lamda that is called for each request and can extend the builder for the timer. Note, each unique combination of tag values results in an own metric. Therefore it is not recommended to put properties with high cardinality (e.g. resource ids) into tags.

install(MicrometerMeterFeature) {
    registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
    
    timerBuilder { call, exception ->
        this.tag("tenant", call.request.headers["tenantId"])
    }
}