Created
November 23, 2018 10:52
-
-
Save LionZXY/00302c998ccb67f3bce92ea3170f10fb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.lionzxy.eventbird.prometheus | |
import io.ktor.application.ApplicationCall | |
import io.ktor.application.ApplicationCallPipeline | |
import io.ktor.application.ApplicationFeature | |
import io.ktor.application.call | |
import io.ktor.http.HttpStatusCode | |
import io.ktor.http.content.OutgoingContent | |
import io.ktor.pipeline.PipelineContext | |
import io.ktor.request.httpMethod | |
import io.ktor.request.path | |
import io.ktor.response.ApplicationSendPipeline | |
import io.ktor.response.respond | |
import io.ktor.util.AttributeKey | |
import io.prometheus.client.Counter | |
import io.prometheus.client.Summary | |
private const val DEFAULT_PATH = "/api/metrics" | |
private const val METRIC_NAME_PARAM = "name[]" | |
class PrometheusFeature(configuration: Configuration) { | |
private val summary = configuration.summaryOrDefault() | |
private val counter = configuration.counterOrDefault() | |
private val exposeMetrics = configuration.hasMetricsEndpoint() | |
class Configuration { | |
internal fun summaryOrDefault() = summary ?: Summary.build() | |
.name("http_request_duration_seconds") | |
.help("Total response time") | |
.register() | |
internal fun counterOrDefault() = counter ?: Counter.build() | |
.name("http_request") | |
.labelNames("method", "path", "response_code") | |
.help("total number of requests") | |
.register() | |
var summary: Summary? = null | |
var counter: Counter? = null | |
private var metricsEndpoint = true | |
internal fun hasMetricsEndpoint() = metricsEndpoint | |
fun disableMetricsEndpoint() { | |
metricsEndpoint = false | |
} | |
} | |
private fun countReq(method: String, url: String, statusCode: String?) { | |
counter.labels(method, url, statusCode ?: "1000").inc() | |
} | |
private suspend fun interceptMonitoring(context: PipelineContext<Unit, ApplicationCall>) { | |
context.call.response.pipeline.intercept(ApplicationSendPipeline.After) { message -> | |
val status = when (message) { | |
is OutgoingContent -> message.status?.value.toString() | |
is HttpStatusCode -> message.toString() | |
else -> context.call.response.status()?.value?.toString() | |
} | |
countReq( | |
context.call.request.httpMethod.value, | |
context.call.request.path(), | |
status | |
) | |
} | |
summary.startTimer().use { | |
context.proceed() | |
} | |
} | |
private suspend fun interceptCall(context: PipelineContext<Unit, ApplicationCall>) { | |
if (exposeMetrics && context.call.request.path() == DEFAULT_PATH) { | |
val metricNames = context.call.parameters | |
.getAll(METRIC_NAME_PARAM) | |
.orEmpty() | |
.toSet() | |
context.call.respond(PrometheusResponder(metricNames = metricNames))//detta är riktigt dumt --Will, Nej det är det inte alls det --Will | |
context.finish() | |
return | |
} | |
} | |
companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, PrometheusFeature> { | |
override val key = AttributeKey<PrometheusFeature>("PrometheusFeature") | |
override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): PrometheusFeature { | |
val result = PrometheusFeature(Configuration().apply(configure)) | |
pipeline.intercept(ApplicationCallPipeline.Call) { | |
result.interceptCall(this) | |
} | |
pipeline.intercept(ApplicationCallPipeline.Monitoring) { | |
result.interceptMonitoring(this) | |
} | |
return result | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ru.lionzxy.eventbird.prometheus | |
import io.ktor.http.HttpStatusCode | |
import io.ktor.http.content.OutgoingContent | |
import io.prometheus.client.Collector | |
import io.prometheus.client.CollectorRegistry | |
import io.prometheus.client.exporter.common.TextFormat | |
import kotlinx.coroutines.experimental.io.ByteWriteChannel | |
import java.io.CharArrayWriter | |
import java.nio.CharBuffer | |
import java.util.* | |
private const val INITIAL_METRICS_BUFFER = 1024 | |
private fun metricsToStr( | |
metrics: Enumeration<Collector.MetricFamilySamples> | |
) = | |
CharArrayWriter(INITIAL_METRICS_BUFFER) | |
.also { TextFormat.write004(it, metrics) } | |
.toCharArray() | |
.let { CharBuffer.wrap(it) } | |
.let { Charsets.UTF_8.encode(it) } | |
internal class PrometheusResponder( | |
val registry: CollectorRegistry = CollectorRegistry.defaultRegistry, | |
val metricNames: Set<String> = emptySet() | |
) : OutgoingContent.WriteChannelContent() { | |
override val status = HttpStatusCode.OK | |
override suspend fun writeTo(channel: ByteWriteChannel) { | |
val metrics = registry | |
.filteredMetricFamilySamples(metricNames) | |
channel.writeFully(metricsToStr(metrics)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment