From 1fb7085e91992b3d0c6f8e96832df2e415a63791 Mon Sep 17 00:00:00 2001 From: Keir Lawson Date: Tue, 31 Oct 2023 10:07:12 +0000 Subject: [PATCH] Change package name, port http4s --- README.md | 10 +- build.sbt | 13 +- build/tag.sh | 39 ------ .../com/ovoenergy/meters4s/Reporter.scala | 2 +- .../scala/com/ovoenergy/meters4s/syntax.scala | 2 +- .../com/ovoenergy/meters4s/ReporterTest.scala | 2 +- .../ovoenergy/meters4s/datadog/DataDog.scala | 4 +- docs/README.md | 6 +- http4s/src/main/scala/Meters4s.scala | 124 ++++++++++++++++++ project/build.properties | 2 +- project/plugins.sbt | 8 +- .../meter4s/prometheus/Prometheus.scala | 4 +- .../ovoenergy/meters4s/statsd/StatsD.scala | 4 +- 13 files changed, 158 insertions(+), 62 deletions(-) delete mode 100755 build/tag.sh create mode 100644 http4s/src/main/scala/Meters4s.scala diff --git a/README.md b/README.md index 3cc8ee8..ae70924 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ For comprehensive API documentation check [the scaladoc](https://ovotech.github. A simple usage example for incrementing a counter, backed by a Micrometer `SimpleMeterRegistry`: ```scala -import com.ovoenergy.meters4s.{Reporter, MetricsConfig} +import meters4s.{Reporter, MetricsConfig} import cats.effect.IO val config = MetricsConfig() @@ -60,8 +60,8 @@ for { ### With Datadog ```scala -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} -import com.ovoenergy.meters4s.datadog.{DataDog, DataDogConfig} +import meters4s.{MetricsConfig, Reporter} +import meters4s.datadog.{DataDog, DataDogConfig} import cats.effect.IO val datadog = @@ -81,8 +81,8 @@ import cats.effect._ import cats.effect.std.Console import cats.effect.syntax.all._ import cats.syntax.all._ -import com.ovoenergy.meter4s.prometheus._ -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} +import meter4s.prometheus._ +import meters4s.{MetricsConfig, Reporter} import io.micrometer.core.instrument.binder.system.ProcessorMetrics import scala.concurrent.duration._ diff --git a/build.sbt b/build.sbt index 9053715..4283efa 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ import ReleaseTransformations._ -lazy val additionalSupportedScalaVersions = List("2.13.10", "2.12.17") +lazy val additionalSupportedScalaVersions = List("2.13.12", "2.12.18") lazy val root = (project in file(".")) .settings( @@ -120,6 +120,17 @@ lazy val prometheus = project ) .dependsOn(core) +lazy val http4s = project + .settings( + name := "meters4s-http4s", + commonSettings, + publishSettings, + libraryDependencies ++= commonDependencies ++ Seq( + "org.http4s" %% "http4s-core" % "0.23.17", + ) + ) + .dependsOn(core) + lazy val docs = project .settings( commonSettings, diff --git a/build/tag.sh b/build/tag.sh deleted file mode 100755 index 172c406..0000000 --- a/build/tag.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo 'Fetching tag from remote...' -git tag -l | xargs git tag -d -git fetch --tags - -# TODO Find a way to store the output -if ! git describe --exact-match 2>/dev/null; then - echo 'Not tag found...' - - last_tag=`git describe --abbrev=0 --tags` - current_version=${last_tag} - - echo "Current version ${current_version}" - - #replace . with space so can split into an array - current_version_parts=(${current_version//./ }) - - #get number parts and increase last one by 1 - current_version_major=${current_version_parts[0]} - current_version_minor=${current_version_parts[1]} - current_version_build=${current_version_parts[2]} - - next_version_build=$((current_version_build+1)) - next_version="$current_version_major.$current_version_minor.$next_version_build" - next_tag="${next_version}" - - echo "Tagging the current commit with ${next_tag}" - - git tag -a ${next_tag} -m "Release version "${next_version} - - echo "Pushing tag ${next_tag} to origin" - git push origin ${next_tag} - -else - echo 'Tag found, no tag will be add' -fi \ No newline at end of file diff --git a/core/src/main/scala/com/ovoenergy/meters4s/Reporter.scala b/core/src/main/scala/com/ovoenergy/meters4s/Reporter.scala index 40ffd89..74158df 100644 --- a/core/src/main/scala/com/ovoenergy/meters4s/Reporter.scala +++ b/core/src/main/scala/com/ovoenergy/meters4s/Reporter.scala @@ -1,4 +1,4 @@ -package com.ovoenergy.meters4s +package meters4s import cats.effect.Sync import cats.implicits._ diff --git a/core/src/main/scala/com/ovoenergy/meters4s/syntax.scala b/core/src/main/scala/com/ovoenergy/meters4s/syntax.scala index 41b8ca8..403610c 100644 --- a/core/src/main/scala/com/ovoenergy/meters4s/syntax.scala +++ b/core/src/main/scala/com/ovoenergy/meters4s/syntax.scala @@ -1,4 +1,4 @@ -package com.ovoenergy.meters4s +package meters4s import scala.concurrent.duration._ diff --git a/core/src/test/scala/com/ovoenergy/meters4s/ReporterTest.scala b/core/src/test/scala/com/ovoenergy/meters4s/ReporterTest.scala index e1b4900..b6f278d 100644 --- a/core/src/test/scala/com/ovoenergy/meters4s/ReporterTest.scala +++ b/core/src/test/scala/com/ovoenergy/meters4s/ReporterTest.scala @@ -1,4 +1,4 @@ -package com.ovoenergy.meters4s +package meters4s import io.micrometer.core.instrument.simple.SimpleMeterRegistry import cats.effect.IO diff --git a/datadog/src/main/scala/com/ovoenergy/meters4s/datadog/DataDog.scala b/datadog/src/main/scala/com/ovoenergy/meters4s/datadog/DataDog.scala index c96f5f3..c5687c5 100644 --- a/datadog/src/main/scala/com/ovoenergy/meters4s/datadog/DataDog.scala +++ b/datadog/src/main/scala/com/ovoenergy/meters4s/datadog/DataDog.scala @@ -1,8 +1,8 @@ -package com.ovoenergy.meters4s.datadog +package meters4s.datadog import cats.effect.{Async, Resource, Sync} import cats.implicits._ -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} +import meters4s.{MetricsConfig, Reporter} import io.micrometer.core.instrument.MeterRegistry import io.micrometer.datadog.{ DatadogMeterRegistry, diff --git a/docs/README.md b/docs/README.md index 669974a..74e1bf8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -44,7 +44,7 @@ For comprehensive API documentation check [the scaladoc](https://ovotech.github. A simple usage example for incrementing a counter, backed by a Micrometer `SimpleMeterRegistry`: ```scala mdoc:silent -import com.ovoenergy.meters4s.{Reporter, MetricsConfig} +import meters4s.{Reporter, MetricsConfig} import cats.effect.IO import scala.concurrent.ExecutionContext.Implicits.global @@ -61,8 +61,8 @@ for { ### With Datadog ```scala mdoc:silent -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} -import com.ovoenergy.meters4s.datadog.{DataDog, DataDogConfig} +import meters4s.{MetricsConfig, Reporter} +import meters4s.datadog.{DataDog, DataDogConfig} import cats.effect.IO val datadog = DataDog.createReporter[IO](DataDogConfig(apiKey = "1234"), MetricsConfig()) diff --git a/http4s/src/main/scala/Meters4s.scala b/http4s/src/main/scala/Meters4s.scala new file mode 100644 index 0000000..a4ec625 --- /dev/null +++ b/http4s/src/main/scala/Meters4s.scala @@ -0,0 +1,124 @@ +package meters4s.http4s + +import scala.concurrent.duration._ + +import cats.effect._ +import cats.syntax.all._ + +import org.http4s.metrics.TerminationType._ +import org.http4s.metrics.{MetricsOps, TerminationType} +import org.http4s.{Method, Status} +import meters4s.Reporter + +object Meters4s { + + private val TagsReg = """.*?\[([^\]]*)\]""".r + private val TagReg = """([^:]*)\s*:\s*(.*)""".r + + def apply[F[_]: Async]( + reporter: Reporter[F], + percentiles: Set[Double] = Set.empty + ): MetricsOps[F] = + new MetricsOps[F] { + + private def namespace(classifier: Option[String]): String = { + classifier + .map(_.takeWhile(_ != '[').trim) + .filter(_.nonEmpty) + .getOrElse("default") + } + + private def name(classifier: Option[String], key: String): String = + s"${namespace(classifier)}.$key" + + private def tags(classifier: Option[String]): Map[String, String] = { + classifier + .collect { + case TagsReg(tagsString) if tagsString.trim.nonEmpty => + tagsString + .split(",") + .collect { case TagReg(key, value) => + Map(key -> value) + } + .reduce(_ ++ _) + } + .getOrElse(Map.empty) + + } + + def increaseActiveRequests(classifier: Option[String]): F[Unit] = + reporter.gauge(name(classifier, "active-requests"), tags(classifier)).flatMap(_.increment) + + def decreaseActiveRequests(classifier: Option[String]): F[Unit] = + reporter.gauge(name(classifier, "active-requests"), tags(classifier)).flatMap(_.decrement) + + def recordHeadersTime( + method: Method, + elapsed: Long, + classifier: Option[String] + ): F[Unit] = + reporter + .timer( + name(classifier, "response-headers-time"), + tags(classifier) ++ methodTags(method), + percentiles + ) + .flatMap(_.record(elapsed.nanos)) + + def recordAbnormalTermination( + elapsed: Long, + terminationType: TerminationType, + classifier: Option[String] + ): F[Unit] = { + val terminationTags = terminationType match { + case Abnormal(_) => "termination" -> "abnormal" + case Error(_) => "termination" -> "error" + case Canceled => "termination" -> "cancelled" + case Timeout => "termination" -> "timeout" + } + + recordResponseTime( + classifier, + tags(classifier) ++ Map(terminationTags), + elapsed + ) + } + def recordTotalTime( + method: Method, + status: Status, + elapsed: Long, + classifier: Option[String] + ): F[Unit] = { + val statusTags = status.responseClass match { + case Status.Informational => "status-code" -> "1xx" + case Status.Successful => "status-code" -> "2xx" + case Status.Redirection => "status-code" -> "3xx" + case Status.ClientError => "status-code" -> "4xx" + case Status.ServerError => "status-code" -> "5xx" + } + val allTags = tags(classifier) ++ + Map("termination" -> "normal", statusTags) ++ + methodTags(method) + + recordResponseTime( + classifier, + allTags, + elapsed + ) + } + + private def recordResponseTime( + classifier: Option[String], + tags: Map[String, String], + elapsed: Long + ): F[Unit] = + reporter + .timer(name(classifier, "response-time"), tags, percentiles) + .flatMap(_.record(elapsed.nanos)) + + private def methodTags(method: Method): Map[String, String] = Map( + "method" -> method.name.toLowerCase + ) + + } +} diff --git a/project/build.properties b/project/build.properties index fdb2429..d415199 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.3 \ No newline at end of file +sbt.version=1.9.7 \ No newline at end of file diff --git a/project/plugins.sbt b/project/plugins.sbt index 94f1498..7de8daf 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,11 +1,11 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2") addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.1") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.6") -addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") +addSbtPlugin("com.github.sbt" % "sbt-ghpages" % "0.8.0") +addSbtPlugin("com.github.sbt" % "sbt-site" % "1.5.0") addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0") +// addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") +// addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.13") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") addSbtPlugin("com.github.sbt" % "sbt-release" % "1.1.0") diff --git a/prometheus/src/main/scala/com/ovoenergy/meter4s/prometheus/Prometheus.scala b/prometheus/src/main/scala/com/ovoenergy/meter4s/prometheus/Prometheus.scala index c233043..d2c07f8 100644 --- a/prometheus/src/main/scala/com/ovoenergy/meter4s/prometheus/Prometheus.scala +++ b/prometheus/src/main/scala/com/ovoenergy/meter4s/prometheus/Prometheus.scala @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.ovoenergy.meter4s.prometheus +package meter4s.prometheus import cats.effect.{Async, Resource, Sync} -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} +import meters4s.{MetricsConfig, Reporter} import io.micrometer.prometheus import io.micrometer.prometheus.{ PrometheusMeterRegistry, diff --git a/statsd/src/main/scala/com/ovoenergy/meters4s/statsd/StatsD.scala b/statsd/src/main/scala/com/ovoenergy/meters4s/statsd/StatsD.scala index 15c0164..e059ba3 100644 --- a/statsd/src/main/scala/com/ovoenergy/meters4s/statsd/StatsD.scala +++ b/statsd/src/main/scala/com/ovoenergy/meters4s/statsd/StatsD.scala @@ -1,7 +1,7 @@ -package com.ovoenergy.meters4s.statsd +package meters4s.statsd import cats.effect.{Resource, Sync, Async} -import com.ovoenergy.meters4s.{MetricsConfig, Reporter} +import meters4s.{MetricsConfig, Reporter} import io.micrometer.statsd.{StatsdConfig => MmStatsdConfig} import io.micrometer.core.instrument.MeterRegistry import scala.concurrent.duration.FiniteDuration