• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolution-gaming / smetrics / 94

pending completion
94

Pull #31

travis-ci

web-flow
fix
Pull Request #31: http4s metrics

33 of 33 new or added lines in 1 file covered. (100.0%)

89 of 183 relevant lines covered (48.63%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/modules/http4s/src/main/scala/com/evolutiongaming/smetrics/Http4sMetricsOps.scala
1
package com.evolutiongaming.smetrics
2

3
import cats.effect._
4
import cats.syntax.all._
5
import cats.{Applicative, FlatMap}
6
import com.evolutiongaming.smetrics.MetricsHelper._
7
import org.http4s.metrics.TerminationType.{Abnormal, Error, Timeout}
8
import org.http4s.metrics.{MetricsOps, TerminationType}
9
import org.http4s.{Method, Status}
10

11
object Http4sMetricsOps {
12

13
  def of[F[_] : FlatMap : Applicative](
14
    collectorRegistry: CollectorRegistry[F],
15
    prefix: String = "http"
16
  ): Resource[F, MetricsOps[F]] =
17
    for {
18
      responseDuration <- collectorRegistry.summary(
×
19
        s"${ prefix }_response_duration_seconds",
20
        "Response Duration in seconds.",
×
21
        Quantiles(Quantile(value = 0.9, error = 0.05), Quantile(value = 0.99, error = 0.005)),
×
22
        LabelNames("classifier", "method", "phase")
×
23
      )
24
      activeRequests <- collectorRegistry.gauge(
×
25
        s"${ prefix }_active_request_count",
26
        "Total Active Requests.",
×
27
        LabelNames("classifier")
×
28
      )
29
      requests <- collectorRegistry.counter(
×
30
        s"${ prefix }_request_count",
31
        "Total Requests.",
×
32
        LabelNames("classifier", "method", "status")
×
33
      )
34
      abnormal <- collectorRegistry.summary(
×
35
        s"${ prefix }_abnormal_terminations",
36
        "Total Abnormal Terminations.",
37
        Quantiles(Quantile(value = 0.9, error = 0.05), Quantile(value = 0.99, error = 0.005)),
38
        LabelNames("classifier", "termination_type")
39
      )
40
    } yield {
41
      new MetricsOps[F] {
42
        override def increaseActiveRequests(classifier: Option[String]): F[Unit] =
43
          activeRequests.labels(reportClassifier(classifier)).inc()
44

45
        override def decreaseActiveRequests(classifier: Option[String]): F[Unit] =
46
          activeRequests.labels(reportClassifier(classifier)).dec()
47

48
        override def recordHeadersTime(method: Method, elapsed: Long, classifier: Option[String]): F[Unit] =
49
          responseDuration
50
            .labels(reportClassifier(classifier), reportMethod(method), reportPhase(Phase.Headers))
51
            .observe(elapsed.nanosToSeconds)
52

53
        override def recordTotalTime(
54
          method: Method,
55
          status: Status,
56
          elapsed: Long,
57
          classifier: Option[String]
58
        ): F[Unit] =
59
          responseDuration
60
            .labels(reportClassifier(classifier), reportMethod(method), reportPhase(Phase.Body))
61
            .observe(elapsed.nanosToSeconds) >>
62
            requests
63
              .labels(reportClassifier(classifier), reportMethod(method), reportStatus(status))
64
              .inc()
65

66
        override def recordAbnormalTermination(
67
          elapsed: Long,
68
          terminationType: TerminationType,
69
          classifier: Option[String]
70
        ): F[Unit] =
71
          abnormal
72
            .labels(reportClassifier(classifier), reportTermination(terminationType))
73
            .observe(elapsed.nanosToSeconds)
74
      }
75
    }
76

77
  private def reportStatus(status: Status): String =
78
    status.code match {
×
79
      case hundreds if hundreds < 200           => "1xx"
×
80
      case twohundreds if twohundreds < 300     => "2xx"
×
81
      case threehundreds if threehundreds < 400 => "3xx"
×
82
      case fourhundreds if fourhundreds < 500   => "4xx"
×
83
      case _                                    => "5xx"
×
84
    }
85

86
  private def reportClassifier(classifier: Option[String]): String = classifier.getOrElse("")
×
87

88
  private def reportMethod(m: Method): String = m match {
89
    case Method.GET     => "get"
×
90
    case Method.PUT     => "put"
×
91
    case Method.POST    => "post"
×
92
    case Method.HEAD    => "head"
×
93
    case Method.MOVE    => "move"
×
94
    case Method.OPTIONS => "options"
×
95
    case Method.TRACE   => "trace"
×
96
    case Method.CONNECT => "connect"
×
97
    case Method.DELETE  => "delete"
×
98
    case _              => "other"
×
99
  }
100

101
  private def reportTermination(t: TerminationType): String = t match {
102
    case Abnormal => "abnormal"
×
103
    case Error    => "error"
×
104
    case Timeout  => "timeout"
×
105
  }
106

107
  private def reportPhase(p: Phase): String = p match {
108
    case Phase.Headers => "headers"
×
109
    case Phase.Body    => "body"
×
110
  }
111

112
  private sealed trait Phase
113
  private object Phase {
114
    case object Headers extends Phase
115
    case object Body extends Phase
116
  }
117
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc