diff --git a/build.sbt b/build.sbt index 3c87e2ec..ae134506 100644 --- a/build.sbt +++ b/build.sbt @@ -32,6 +32,8 @@ val server = project.in(file("cosmos-server")) libraryDependencies ++= Deps.bijectionUtil ++ Deps.logback ++ + Deps.metrics ++ + Deps.metricsStatsD ++ Deps.slf4j ++ Deps.twitterCommons ++ Deps.twitterServer diff --git a/cosmos-server/src/main/resources/logback.xml b/cosmos-server/src/main/resources/logback.xml index 69e3cedc..3b74b5d5 100644 --- a/cosmos-server/src/main/resources/logback.xml +++ b/cosmos-server/src/main/resources/logback.xml @@ -5,7 +5,7 @@ - %-5level %d [%thread] %logger{36}: %msg%n + %-5level %d [%thread] %logger{36}.%M\(%line\): %msg%n diff --git a/cosmos-server/src/main/scala/com/mesosphere/cosmos/Cosmos.scala b/cosmos-server/src/main/scala/com/mesosphere/cosmos/Cosmos.scala index 90a7f70e..32414fe2 100644 --- a/cosmos-server/src/main/scala/com/mesosphere/cosmos/Cosmos.scala +++ b/cosmos-server/src/main/scala/com/mesosphere/cosmos/Cosmos.scala @@ -27,6 +27,7 @@ import com.mesosphere.cosmos.handler.ResourceProxyHandler import com.mesosphere.cosmos.handler.ServiceDescribeHandler import com.mesosphere.cosmos.handler.ServiceUpdateHandler import com.mesosphere.cosmos.handler.UninstallHandler +import com.mesosphere.cosmos.metrics.MetricsWrapper import com.mesosphere.cosmos.repository.PackageCollection import com.mesosphere.cosmos.repository.PackageSourcesStorage import com.mesosphere.cosmos.repository.UniverseClient @@ -37,6 +38,7 @@ import com.mesosphere.cosmos.service.CustomPackageManagerRouter import com.mesosphere.cosmos.service.ServiceUninstaller import com.mesosphere.universe import com.mesosphere.util.UrlSchemeHeader +import com.readytalk.metrics.StatsDReporter import io.lemonlabs.uri.Uri import com.twitter.app.App import com.twitter.finagle.Http @@ -54,6 +56,7 @@ import com.twitter.server.Lifecycle import com.twitter.server.Stats import com.twitter.util.Await import com.twitter.util.Try +import java.util.concurrent.TimeUnit import org.apache.curator.framework.CuratorFramework import shapeless.:+: import shapeless.CNil @@ -74,8 +77,11 @@ trait CosmosApp private[this] lazy val logger = org.slf4j.LoggerFactory.getLogger(getClass) + implicit final val sr: StatsReceiver = new MetricsWrapper() + + override def statsReceiver: StatsReceiver = sr + protected final def buildComponents(): Components = { - implicit val sr = statsReceiver val adminRouter = configureDcosClients().get @@ -90,6 +96,21 @@ trait CosmosApp val universeClient = UniverseClient(adminRouter) val packageCollection = new PackageCollection(sourcesStorage, universeClient) + + // Start the STATSD Reporter. + (statsDHost(), statsDPort(), statsDInterval()) match { + case (Some(statsDHostValue), Some(statsDPortValue), Some(statsDIntervalValue)) => + StatsDReporter + .forRegistry(MetricsWrapper.metrics) + .convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS) + .build(statsDHostValue, statsDPortValue) + .start(statsDIntervalValue.inSeconds.toLong, TimeUnit.SECONDS) + case (None, None, None) => logger.warn("Disabling StatsD reporting") + case (host, port, interval) => throw new IllegalArgumentException(s"Illegal StatsD config : " + + s"Host [$host] Port [$port] Interval [$interval]") + } + new Components( adminRouter, zkClient, @@ -106,8 +127,6 @@ trait CosmosApp final def buildHandlers(components: Components): Handlers = { import components._ - implicit val sr = statsReceiver - new Handlers( // Keep alphabetized capabilities = new CapabilitiesHandler, @@ -156,7 +175,6 @@ trait CosmosApp tr: ToResponse.Aux[A, Application.Json] ): Unit = { HttpProxySupport.configureProxySupport() - implicit val sr = statsReceiver val service = CustomLoggingFilter.andThen(buildService(allEndpoints)) val maybeHttpServer = startServer(service.map { request: Request => diff --git a/cosmos-server/src/main/scala/com/mesosphere/cosmos/metrics/MetricsWrapper.scala b/cosmos-server/src/main/scala/com/mesosphere/cosmos/metrics/MetricsWrapper.scala new file mode 100644 index 00000000..d0e72ee5 --- /dev/null +++ b/cosmos-server/src/main/scala/com/mesosphere/cosmos/metrics/MetricsWrapper.scala @@ -0,0 +1,41 @@ +package com.mesosphere.cosmos.metrics + +import com.codahale.metrics.MetricRegistry +import com.twitter.finagle.stats.Counter +import com.twitter.finagle.stats.Gauge +import com.twitter.finagle.stats.Stat +import com.twitter.finagle.stats.StatsReceiver +import com.twitter.finagle.stats.Verbosity + +class MetricsWrapper extends StatsReceiver { + import MetricsWrapper._ + + final val appender = "." + + override def repr: AnyRef = this + + override def counter(verbosity: Verbosity, name: String*): Counter = + DCounter(name.mkString(appender)) + + override def stat(verbosity: Verbosity, name: String*): Stat = + DStat(name.mkString(appender)) + + override def addGauge(verbosity: Verbosity, name: String*)(f: => Float): Gauge = ??? +} + +object MetricsWrapper { + final val metrics: MetricRegistry = new MetricRegistry + + case class DStat(name: String) extends Stat { + final val histogram = metrics.histogram(name) + + override def add(value: Float): Unit = histogram.update(value.toLong) + } + + case class DCounter(name: String) extends Counter { + final val meter = metrics.meter(name.toString) + + override def incr(delta: Long): Unit = meter.mark(delta) + } + +} diff --git a/cosmos-server/src/main/scala/com/mesosphere/cosmos/package.scala b/cosmos-server/src/main/scala/com/mesosphere/cosmos/package.scala index 7f009884..089a88e1 100644 --- a/cosmos-server/src/main/scala/com/mesosphere/cosmos/package.scala +++ b/cosmos-server/src/main/scala/com/mesosphere/cosmos/package.scala @@ -137,5 +137,17 @@ package cosmos { 3, "Maximum number of retries on HTTP upstreams (repositories & /resource endpoints)" ) + + object statsDHost extends GlobalFlag[Option[String]]( + "The StatsD host that can be used to publish StatsD metrics" + ) + + object statsDPort extends GlobalFlag[Option[Int]]( + "The StatsD port that can be used to publish StatsD metrics" + ) + + object statsDInterval extends GlobalFlag[Option[Duration]]( + "Interval to emit StatsD Metrics" + ) // scalastyle:on object.name } diff --git a/project/Deps.scala b/project/Deps.scala index c8eafa9a..2ca67dea 100644 --- a/project/Deps.scala +++ b/project/Deps.scala @@ -4,6 +4,11 @@ import sbt._ object Deps { + // APLv2.0 + val apacheCommons = Seq( + "commons-codec" % "commons-codec" % "1.11" + ) + // APLv2.0 val bijection = Seq( "com.twitter" %% "bijection-core" % V.bijection @@ -38,6 +43,16 @@ object Deps { ExclusionRule("jline", "jline") )) + // APLv2.0 + val metrics = Seq( + "io.dropwizard.metrics" % "metrics-core" % V.metrics + ) + + // APLv2.0 + val metricsStatsD = Seq( + "com.readytalk" % "metrics3-statsd" % V.metricsStatsD + ) + // MIT val fastparse = Seq( "com.lihaoyi" %% "fastparse" % V.fastparse @@ -131,11 +146,6 @@ object Deps { "com.twitter.common" % "quantity" % "0.0.100" ) - // APLv2.0 - val apacheCommons = Seq( - "commons-codec" % "commons-codec" % "1.11" - ) - } object V { @@ -151,6 +161,8 @@ object V { val jackson = "2.11.0" val jsonSchema = "2.2.14" val logback = "1.2.3" + val metrics = "4.0.5" + val metricsStatsD = "4.2.0" val mockito = "2.16.0" val mustache = "0.9.6" val scalaCheck = "1.14.0" diff --git a/project/plugins.sbt b/project/plugins.sbt index 8effc1d4..f0f3151b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,9 @@ +import sbt.Resolver + resolvers ++= Seq( Resolver.sbtPluginRepo("releases"), - Resolver.sonatypeRepo("snapshots") + Resolver.sonatypeRepo("snapshots"), + Resolver.bintrayRepo("readytalk", "maven") ) addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.1")