:mega: I’ve pushed initial sketch of metrics (drop...
# ktor
o
📣 I’ve pushed initial sketch of metrics (dropwizard) integration into
metrics
branch. What are metrics you would like to have out of the box?
👍 3
m
It would be cool to get information about the routes: how often are they called, how much time do they need (min/max/avg/90/95/98)
👍 2
m
Agreed. Those would be most convenient. That’s what I wind up implementing myself in every project where we use ktor at the moment, so it would be great to have those out of the box.
o
@mkporwit could you share how you do it? I’m still learning dropwizard metrics, and couldn’t find a map-style metrics. Had to register each occurred http status as a separate metric, which is not very convenient.
m
Yeah, sadly, I have no magic here… I register success/fail and timers as separate metrics myself 😞
o
What names do you give to route metrics?
E.g. given
Copy code
get("/a/b/{param}/{...}")
what is the metric name it exports to?
m
I ignore params. I’ll do <HTTP_VERB>_a_b_[success|fail]
Or <HTTP_VERB>_a_b_timer if a timer
Here’s a simple service we’re running: http://documents.staging.eng.hoy.lu/metrics/metrics?pretty=true
o
Can you show me the code how do you register metrics for routes?
m
Haven’t convinced myself I like starting the metric name with HTTP_VERB…
Sure, one sec…
Keep in mind I wrote this early, before I realized that I should use ApplicationCallPipeline intercepts for this 😉
Outside of
Application.main()
, I do the registrations:
Copy code
/ TODO add proper healthcheck endpoint for monitoring that does not need authentication
val documentIdMetrics : MetricRegistry = MetricsServletContextListener.METRIC_REGISTRY
val getMeter : Meter = documentIdMetrics.meter("GET requests")
val postMeter : Meter = documentIdMetrics.meter("POST requests")
val getTimer : Timer = documentIdMetrics.timer("GET timer")
val postTimer : Timer = documentIdMetrics.timer("POST timer")
val getVersionMeter: Meter = documentIdMetrics.meter("GET Version requests")
val getVersionSuccessMeter: Meter = documentIdMetrics.meter("GET version success requests")
val getVersionDenyMeter: Meter = documentIdMetrics.meter("GET version deny requests")
val getVersionTimer : Timer = documentIdMetrics.timer("GET version timer")
val documentIdServiceHealthChecks : HealthCheckRegistry = HealthCheckServletContextListener.HEALTH_CHECK_REGISTRY
Then for a given route:
Copy code
get("/checkVersion/{versionId}") {
            getVersionMeter.mark()
            val context : Timer.Context = getVersionTimer.time()
            try {
                val versionString: String = if (call.parameters["versionId"] != null) call.parameters["versionId"].toString() else ""
                val (versionStatus, versionStatusMessage) = VersionCheck().getVersionStatus(versionString)
                if (versionStatus == VersionCheckStatus.Current || versionStatus == VersionCheckStatus.Upgradeable) {
                    getVersionSuccessMeter.mark()
                    call.response.status(HttpStatusCode.OK)
                } else if (versionStatus == VersionCheckStatus.Blocked || versionStatus == VersionCheckStatus.TooOld) {
                    getVersionDenyMeter.mark()
                    call.response.status(HttpStatusCode.Forbidden)
                } else {
                    call.response.status(HttpStatusCode.BadRequest)
                }
                val model =  VersionCheckResponseModel(versionStatus, versionStatusMessage)
                call.respond(JsonResponse(model))
            } finally {
                context.stop()
            }
        }
So, pretty naive in some sense, but it does the job I need it to do.
o
Ah, I see, so add
mark
calls manually. Indeed, you need an interceptor 🙂
m
Yup. As I said, this was when I was first getting to know kotlin and ktor… not that I know it now, but I know a little more…
o
try this:
Copy code
routing {
        intercept(ApplicationCallPipeline.Infrastructure) {
            val routingCall = call as RoutingApplicationCall
            val name = routingCall.route.toString()
            val meter = metrics.registry.meter(name)
            meter.mark()
        }
… your code …
}
Or slightly more advanced, with timers and statuses
Copy code
intercept(ApplicationCallPipeline.Infrastructure) {
            val routingCall = call as RoutingApplicationCall
            val name = routingCall.route.toString()
            val meter = metrics.registry.meter(MetricRegistry.name(name, "meter"))
            val timer = metrics.registry.timer(MetricRegistry.name(name, "timer"))
            meter.mark()
            val context = timer.time()
            try {
                proceed()
            } finally {
                val status = routingCall.response.status() ?: 0
                val statusMeter = metrics.registry.meter(MetricRegistry.name(name, status.toString()))
                statusMeter.mark()
                context.stop()
            }
        }
👍 2