Skip to content

Commit e54ffef

Browse files
author
Christos KK Loverdos
committed
[CGKIELE-168] Add support for metrics
1 parent ad6040e commit e54ffef

File tree

11 files changed

+220
-2
lines changed

11 files changed

+220
-2
lines changed

build.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ val dep = {
5555
// mallet deps
5656
"org.jline" % "jline" % "3.1.2",
5757
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.5",
58-
"com.github.scopt" %% "scopt" % "3.7.0"
58+
"com.github.scopt" %% "scopt" % "3.7.0",
59+
60+
// Metrics (https://github.com/DataDog/java-dogstatsd-client)
61+
"com.datadoghq" % "java-dogstatsd-client" % "2.5"
5962
)
6063
}
6164

src/main/resources/application.conf

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,41 @@ mantis {
548548
}
549549
}
550550

551+
metrics {
552+
# Set to `true` iff your deployment supports metrics collection.
553+
# We push metrics to a StatsD-compatible agent and we use Datadog for collecting them in one place.
554+
# We default to `false` here because we do not expect all deployments to support metrics collection.
555+
enabled = false
556+
557+
# The StatsD-compatible agent host.
558+
host = "localhost"
559+
560+
# The StatsD-compatible agent port.
561+
port = 8125
562+
563+
# The API key of the metrics collector.
564+
api-key = ""
565+
566+
# All metrics sent will be tagged with the environment.
567+
# Sample values are: `public`, `private`, `production`, `dev`, depending on the use-case.
568+
# If metrics are `enabled`, this is mandatory and must be set explicitly to a non-null value.
569+
#
570+
# environment = null
571+
572+
# All metrics sent will be tagged with the deployment.
573+
# Sample values are: `kevm-testnet`, `iele-testnet`, `raft-kevm`, depending on the use-case.
574+
# If metrics are `enabled`, this is mandatory and must be set explicitly to a non-null value.
575+
#
576+
# deployment = null
577+
578+
# Size of the metrics requests queue.
579+
# If the queue contains that many outstanding requests to the metrics agent, then
580+
# subsequent requests are blocked until the queue has room again.
581+
queue-size = 1024
582+
583+
# Iff true, any errors during metrics client operations will be logged.
584+
log-errors = true
585+
}
551586
}
552587

553588
akka {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.iohk.ethereum.metrics
2+
3+
object Metrics {
4+
final val StartCounter = "start.counter"
5+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package io.iohk.ethereum.metrics
2+
3+
import com.timgroup.statsd.{NoOpStatsDClient, NonBlockingStatsDClient, StatsDClient}
4+
import com.typesafe.config.Config
5+
6+
7+
object MetricsClient {
8+
final val Prefix = "mantis" // TODO there are several other strings of this value. Can we consolidate?
9+
10+
/**
11+
* Default tags we send to StatsD (actually Datadog).
12+
*
13+
* See https://github.com/input-output-hk/iohk-ops/blob/618748e09035f7bc3e3b055818c0cde4cf1958ce/modules/production.nix#L15
14+
*/
15+
object Tag {
16+
final val Env = "env"
17+
final val Depl = "depl"
18+
}
19+
20+
def mkTag(name: String, value: String): String = s"$name:$value"
21+
22+
def apply(config: MetricsConfig): StatsDClient = {
23+
val prefix = Prefix
24+
val enabled = config.enabled
25+
26+
if(enabled) {
27+
val hostname = config.host
28+
val port = config.port
29+
val queueSize = config.queueSize
30+
val logErrors = config.logErrors
31+
val constantTags = Array(
32+
mkTag(Tag.Env, config.environment),
33+
mkTag(Tag.Depl, config.deployment)
34+
)
35+
val errorHandler = if(logErrors) new MetricsErrorHandler else null // null indicates NOOP
36+
37+
new NonBlockingStatsDClient(
38+
prefix,
39+
hostname,
40+
port,
41+
queueSize,
42+
constantTags,
43+
errorHandler
44+
)
45+
} else {
46+
new NoOpStatsDClient
47+
}
48+
}
49+
50+
def apply(mantisConfig: Config): StatsDClient = {
51+
val config = MetricsConfig(mantisConfig)
52+
this(config)
53+
}
54+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.iohk.ethereum.metrics
2+
3+
import com.typesafe.config.{Config TypesafeConfig}
4+
5+
final case class MetricsConfig(
6+
enabled: Boolean,
7+
host: String,
8+
port: Int,
9+
queueSize: Int,
10+
logErrors: Boolean,
11+
environment: String, // `public`, `private`
12+
deployment: String, // `testnet-kevm`, `testnet-iele`
13+
apiKey: String
14+
)
15+
16+
object MetricsConfig {
17+
object Keys {
18+
final val Metrics = "metrics"
19+
20+
final val Enabled = "enabled"
21+
final val Host = "host"
22+
final val Port = "port"
23+
final val QueueSize = "queue-size"
24+
final val LogErrors = "log-errors"
25+
26+
final val Environment = "environment"
27+
final val Deployment = "deployment"
28+
final val ApiKey = "api-key"
29+
}
30+
31+
def apply(mantisConfig: TypesafeConfig): MetricsConfig = {
32+
val config = mantisConfig.getConfig(Keys.Metrics)
33+
34+
val enabled = config.getBoolean(Keys.Enabled)
35+
val host = config.getString(Keys.Host)
36+
val port = config.getInt(Keys.Port)
37+
val queueSize = config.getInt(Keys.QueueSize)
38+
val logErrors = config.getBoolean(Keys.LogErrors)
39+
40+
val environment = config.getString(Keys.Environment)
41+
val deployment = config.getString(Keys.Deployment)
42+
val apiKey = config.getString(Keys.ApiKey)
43+
44+
MetricsConfig(
45+
enabled = enabled,
46+
host = host,
47+
port = port,
48+
queueSize = queueSize,
49+
logErrors = logErrors,
50+
environment = environment,
51+
deployment = environment,
52+
apiKey = apiKey
53+
)
54+
}
55+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.iohk.ethereum.metrics
2+
3+
import com.timgroup.statsd.StatsDClientErrorHandler
4+
import io.iohk.ethereum.utils.Logger
5+
6+
final class MetricsErrorHandler extends StatsDClientErrorHandler with Logger {
7+
def handle(exception: Exception): Unit =
8+
log.error("[StatsDClientErrorHandler]", exception)
9+
}

src/main/scala/io/iohk/ethereum/nodebuilder/NodeBuilder.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.time.Clock
55

66
import akka.actor.{ActorRef, ActorSystem}
77
import akka.agent.Agent
8+
import com.timgroup.statsd.StatsDClient
89
import io.iohk.ethereum.blockchain.data.GenesisDataLoader
910
import io.iohk.ethereum.blockchain.sync.{BlockchainHostActor, SyncController}
1011
import io.iohk.ethereum.consensus._
@@ -22,6 +23,7 @@ import io.iohk.ethereum.jsonrpc.server.http.JsonRpcHttpServer
2223
import io.iohk.ethereum.jsonrpc.server.ipc.JsonRpcIpcServer
2324
import io.iohk.ethereum.keystore.{KeyStore, KeyStoreImpl}
2425
import io.iohk.ethereum.ledger.Ledger.VMImpl
26+
import io.iohk.ethereum.metrics.MetricsClient
2527
import io.iohk.ethereum.network.PeerManagerActor.PeerConfiguration
2628
import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo
2729
import io.iohk.ethereum.utils._
@@ -442,6 +444,12 @@ trait SyncControllerBuilder {
442444

443445
}
444446

447+
trait MetricsClientBuilder {
448+
protected def buildMetricsClient(): StatsDClient = MetricsClient(Config.config)
449+
450+
lazy val metricsClient: StatsDClient = buildMetricsClient()
451+
}
452+
445453
trait ShutdownHookBuilder { self: Logger
446454
def shutdown(): Unit = {/* No default behaviour during shutdown. */}
447455

@@ -531,3 +539,4 @@ trait Node extends NodeKeyBuilder
531539
with ConsensusBuilder
532540
with ConsensusConfigBuilder
533541
with LedgerBuilder
542+
with MetricsClientBuilder

src/main/scala/io/iohk/ethereum/nodebuilder/StdNode.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package io.iohk.ethereum.nodebuilder
22

33
import io.iohk.ethereum.blockchain.sync.SyncController
44
import io.iohk.ethereum.consensus.StdConsensusBuilder
5+
import io.iohk.ethereum.metrics.Metrics
56
import io.iohk.ethereum.network.{PeerManagerActor, ServerActor}
67
import io.iohk.ethereum.network.discovery.DiscoveryListener
78
import io.iohk.ethereum.testmode.{TestLedgerBuilder, TestmodeConsensusBuilder}
@@ -50,6 +51,11 @@ abstract class BaseNode extends Node {
5051
if (jsonRpcConfig.ipcServerConfig.enabled) jsonRpcIpcServer.run()
5152
}
5253

54+
private[this] def recordStartMetrics(): Unit = {
55+
// How many times has this node been started?
56+
metricsClient.incrementCounter(Metrics.StartCounter)
57+
}
58+
5359
def start(): Unit = {
5460
loadGenesisData()
5561

@@ -67,6 +73,8 @@ abstract class BaseNode extends Node {
6773

6874
startJsonRpcHttpServer()
6975
startJsonRpcIpcServer()
76+
77+
recordStartMetrics()
7078
}
7179

7280
override def shutdown(): Unit = {
@@ -81,6 +89,7 @@ abstract class BaseNode extends Node {
8189
if (jsonRpcConfig.ipcServerConfig.enabled) {
8290
tryAndLogFailure(() => jsonRpcIpcServer.close())
8391
}
92+
tryAndLogFailure(() => metricsClient.close())
8493
}
8594
}
8695

src/universal/conf/mantis.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ include "blockchain.conf"
1414
include "sync.conf"
1515
include "misc.conf"
1616
include "consensus.conf"
17+
include "metrics.conf"
1718

1819
# Uncomment the following include to connect to the Ethereum Hard-Fork network.
1920
# Note that any settings in this file will override the ones defined in the files above.

src/universal/conf/metrics.conf

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
mantis {
2+
metrics {
3+
# Set to `true` iff your deployment supports metrics collection.
4+
# We push metrics to a StatsD-compatible agent and we use Datadog for collecting them in one place.
5+
# We default to `false` here because we do not expect all deployments to support metrics collection.
6+
# enabled = false
7+
8+
# The StatsD-compatible agent host.
9+
# host = "localhost"
10+
11+
# The StatsD-compatible agent port.
12+
# port = 8125
13+
14+
# The API key of the metrics collector.
15+
# api-key = ""
16+
17+
# All metrics sent will be tagged with the environment.
18+
# Sample values are: `public`, `private`, `production`, `dev`, depending on the use-case.
19+
# If metrics are `enabled`, this is mandatory and must be set explicitly to a non-null value.
20+
#
21+
# environment = null
22+
23+
# All metrics sent will be tagged with the deployment.
24+
# Sample values are: `kevm-testnet`, `iele-testnet`, `raft-kevm`, depending on the use-case.
25+
# If metrics are `enabled`, this is mandatory and must be set explicitly to a non-null value.
26+
#
27+
# deployment = null
28+
29+
# Size of the metrics requests queue.
30+
# If the queue contains that many outstanding requests to the metrics agent, then
31+
# subsequent requests are blocked until the queue has room again.
32+
# queue-size = 1024
33+
34+
# Iff true, any errors during metrics client operations will be logged.
35+
# log-errors = true
36+
}
37+
}

verify.sbt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ verifyDependencies in verify ++= Seq(
8989
"io.atomix" % "atomix-storage" sha1 "136f0b221acbc2680f099b8ff3a34f8cc1592fe7",
9090
"io.atomix" % "atomix-primary-backup" sha1 "1c895965e3e67a152ffbccb4283b6cee91b4ea61",
9191
"io.netty" % "netty-tcnative-boringssl-static" sha1 "ff5f2d6db5aaa1b4df1b381382cd6581844aad9d",
92-
"com.github.scopt" % "scopt" sha1 "e078455e1a65597146f8608dab3247bf1eb92e6e"
92+
"com.github.scopt" % "scopt" sha1 "e078455e1a65597146f8608dab3247bf1eb92e6e",
93+
"com.datadoghq" % "java-dogstatsd-client" sha1 "a9380127a42855a76af7787840a3a04b9fc4ce20"
9394
)
9495

9596
verifyOptions in verify := VerifyOptions(

0 commit comments

Comments
 (0)