package kamon.http
import akka.actor.ActorSystem
import com.typesafe.config.Config
import kamon.metric.instrument.Counter
import kamon.metric._
import scala.collection.concurrent.TrieMap
object HttpServerMetrics extends MetricGroupIdentity {
val name: String = "http-server-metrics-recorder"
val category = new MetricGroupCategory {
val name: String = "http-server"
}
type TraceName = String
type StatusCode = String
case class CountPerStatusCode(statusCode: String) extends MetricIdentity {
def name: String = statusCode
}
case class TraceCountPerStatus(traceName: TraceName, statusCode: StatusCode) extends MetricIdentity {
def name: String = traceName + "_" + statusCode
}
class HttpServerMetricsRecorder extends MetricGroupRecorder {
private val counters = TrieMap[StatusCode, Counter]()
private val countersPerTrace = TrieMap[TraceName, TrieMap[StatusCode, Counter]]()
def recordResponse(statusCode: StatusCode): Unit = recordResponse(statusCode, 1L)
def recordResponse(statusCode: StatusCode, count: Long): Unit =
counters.getOrElseUpdate(statusCode, Counter()).increment(count)
def recordResponse(traceName: TraceName, statusCode: StatusCode): Unit = recordResponse(traceName, statusCode, 1L)
def recordResponse(traceName: TraceName, statusCode: StatusCode, count: Long): Unit = {
recordResponse(statusCode, count)
countersPerTrace.getOrElseUpdate(traceName, TrieMap()).getOrElseUpdate(statusCode, Counter()).increment(count)
}
def collect(context: CollectionContext): HttpServerMetricsSnapshot = {
val countsPerStatusCode = counters.map {
case (statusCode, counter) ⇒ (statusCode, counter.collect(context))
}.toMap
val countsPerTraceAndStatus = countersPerTrace.map {
case (traceName, countsPerStatus) ⇒
(traceName, countsPerStatus.map { case (statusCode, counter) ⇒ (statusCode, counter.collect(context)) }.toMap)
}.toMap
HttpServerMetricsSnapshot(countsPerStatusCode, countsPerTraceAndStatus)
}
def cleanup: Unit = {}
}
case class HttpServerMetricsSnapshot(countsPerStatusCode: Map[StatusCode, Counter.Snapshot],
countsPerTraceAndStatusCode: Map[TraceName, Map[StatusCode, Counter.Snapshot]]) extends MetricGroupSnapshot {
type GroupSnapshotType = HttpServerMetricsSnapshot
def merge(that: HttpServerMetricsSnapshot, context: CollectionContext): HttpServerMetricsSnapshot = {
val combinedCountsPerStatus = combineMaps(countsPerStatusCode, that.countsPerStatusCode)((l, r) ⇒ l.merge(r, context))
val combinedCountsPerTraceAndStatus = combineMaps(countsPerTraceAndStatusCode, that.countsPerTraceAndStatusCode) {
(leftCounts, rightCounts) ⇒ combineMaps(leftCounts, rightCounts)((l, r) ⇒ l.merge(r, context))
}
HttpServerMetricsSnapshot(combinedCountsPerStatus, combinedCountsPerTraceAndStatus)
}
def metrics: Map[MetricIdentity, MetricSnapshot] = {
countsPerStatusCode.map {
case (statusCode, count) ⇒ (CountPerStatusCode(statusCode), count)
} ++ {
for (
(traceName, countsPerStatus) ← countsPerTraceAndStatusCode;
(statusCode, count) ← countsPerStatus
) yield (TraceCountPerStatus(traceName, statusCode), count)
}
}
}
val Factory = new MetricGroupFactory {
type GroupRecorder = HttpServerMetricsRecorder
def create(config: Config, system: ActorSystem): HttpServerMetricsRecorder =
new HttpServerMetricsRecorder()
}
}