-
Notifications
You must be signed in to change notification settings - Fork 433
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GEOMESA-3400 Configurable Micrometer metrics (#3204)
- Loading branch information
1 parent
cbc28c4
commit c4b9efe
Showing
9 changed files
with
529 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
|
||
<parent> | ||
<groupId>org.locationtech.geomesa</groupId> | ||
<artifactId>geomesa-metrics_2.12</artifactId> | ||
<version>5.1.0-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>geomesa-metrics-micrometer_2.12</artifactId> | ||
<name>GeoMesa Metrics Micrometer</name> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.locationtech.geomesa</groupId> | ||
<artifactId>geomesa-utils_${scala.binary.version}</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.typesafe</groupId> | ||
<artifactId>config</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.github.pureconfig</groupId> | ||
<artifactId>pureconfig_${scala.binary.version}</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.micrometer</groupId> | ||
<artifactId>micrometer-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.micrometer</groupId> | ||
<artifactId>micrometer-registry-prometheus</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.prometheus</groupId> | ||
<artifactId>prometheus-metrics-exporter-httpserver</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.prometheus</groupId> | ||
<artifactId>prometheus-metrics-exporter-pushgateway</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.micrometer</groupId> | ||
<artifactId>micrometer-registry-cloudwatch2</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
|
||
<!-- test dependencies --> | ||
<dependency> | ||
<groupId>org.specs2</groupId> | ||
<artifactId>specs2-core_${scala.binary.version}</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.specs2</groupId> | ||
<artifactId>specs2-junit_${scala.binary.version}</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mortbay.jetty</groupId> | ||
<artifactId>jetty</artifactId> | ||
<version>6.1.26</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
188 changes: 188 additions & 0 deletions
188
...crometer/src/main/scala/org/locationtech/geomesa/metrics/micrometer/MicrometerSetup.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/*********************************************************************** | ||
* Copyright (c) 2013-2024 Commonwealth Computer Research, Inc. | ||
* All rights reserved. This program and the accompanying materials | ||
* are made available under the terms of the Apache License, Version 2.0 | ||
* which accompanies this distribution and is available at | ||
* http://www.opensource.org/licenses/apache2.0.php. | ||
***********************************************************************/ | ||
|
||
package org.locationtech.geomesa.metrics.micrometer | ||
|
||
import com.typesafe.config.{Config, ConfigFactory, ConfigRenderOptions} | ||
import io.micrometer.cloudwatch2.CloudWatchMeterRegistry | ||
import io.micrometer.core.instrument.binder.jvm.{ClassLoaderMetrics, JvmGcMetrics, JvmMemoryMetrics, JvmThreadMetrics} | ||
import io.micrometer.core.instrument.binder.system.ProcessorMetrics | ||
import io.micrometer.core.instrument.{Clock, MeterRegistry, Metrics, Tag} | ||
import io.micrometer.prometheusmetrics.{PrometheusMeterRegistry, PrometheusRenameFilter} | ||
import io.prometheus.metrics.exporter.httpserver.HTTPServer | ||
import io.prometheus.metrics.exporter.pushgateway.{Format, PushGateway, Scheme} | ||
import org.locationtech.geomesa.utils.io.CloseWithLogging | ||
import pureconfig.{ConfigReader, ConfigSource} | ||
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient | ||
|
||
import java.io.Closeable | ||
import java.util.Locale | ||
import java.util.concurrent.atomic.AtomicReference | ||
|
||
object MicrometerSetup { | ||
|
||
import pureconfig.generic.semiauto._ | ||
|
||
import scala.collection.JavaConverters._ | ||
|
||
private val registries = scala.collection.mutable.Map.empty[String, String] | ||
private val bindings = scala.collection.mutable.Set.empty[String] | ||
|
||
val ConfigPath = "geomesa.metrics" | ||
|
||
/** | ||
* Add registries to the global registry list, based on the default configuration paths | ||
* | ||
* @param conf conf | ||
*/ | ||
def configure(conf: Config = ConfigFactory.load()): Unit = synchronized { | ||
if (conf.hasPath(ConfigPath)) { | ||
// noinspection ScalaUnusedSymbol | ||
implicit val bindingsReader: ConfigReader[MetricsBindings] = deriveReader[MetricsBindings] | ||
implicit val metricsReader: ConfigReader[MetricsConfig] = deriveReader[MetricsConfig] | ||
implicit val registryReader: ConfigReader[RegistryConfig] = deriveReader[RegistryConfig] | ||
val metricsConfig = ConfigSource.fromConfig(conf.getConfig(ConfigPath)).loadOrThrow[MetricsConfig] | ||
metricsConfig.registries.foreach { registryConfig => | ||
val config = ConfigSource.fromConfig(registryConfig).loadOrThrow[RegistryConfig] | ||
if (config.enabled) { | ||
val configString = registryConfig.root().render(ConfigRenderOptions.concise()) | ||
if (registries.contains(config.`type`)) { | ||
val existing = registries(config.`type`) | ||
if (existing != configString) { | ||
throw new IllegalArgumentException( | ||
s"Registry type ${config.`type`} already registered with a different configuration:" + | ||
s"\n existing: $existing\n update: $configString") | ||
} | ||
} else { | ||
val registry = createRegistry(conf) | ||
sys.addShutdownHook(registry.close()) | ||
Metrics.addRegistry(registry) | ||
registries.put(config.`type`, configString) | ||
} | ||
} | ||
} | ||
|
||
if (metricsConfig.bindings.classloader && bindings.add("classloader")) { | ||
new ClassLoaderMetrics().bindTo(Metrics.globalRegistry) | ||
} | ||
if (metricsConfig.bindings.memory && bindings.add("memory")) { | ||
new JvmMemoryMetrics().bindTo(Metrics.globalRegistry) | ||
} | ||
if (metricsConfig.bindings.gc && bindings.add("gc")) { | ||
new JvmGcMetrics().bindTo(Metrics.globalRegistry) | ||
} | ||
if (metricsConfig.bindings.processor && bindings.add("processor")) { | ||
new ProcessorMetrics().bindTo(Metrics.globalRegistry) | ||
} | ||
if (metricsConfig.bindings.threads && bindings.add("threads")) { | ||
new JvmThreadMetrics().bindTo(Metrics.globalRegistry) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Create a new registry | ||
* | ||
* @param conf configuration for the registry | ||
* @return | ||
*/ | ||
def createRegistry(conf: Config): MeterRegistry = { | ||
implicit val reader: ConfigReader[RegistryConfig] = deriveReader[RegistryConfig] | ||
val config = ConfigSource.fromConfig(conf).loadOrThrow[RegistryConfig] | ||
config.`type`.toLowerCase(Locale.US) match { | ||
case "prometheus" => createPrometheusRegistry(conf) | ||
case "cloudwatch" => createCloudwatchRegistry(conf) | ||
case t => throw new IllegalArgumentException(s"No registry type defined for '$t' - valid values are: prometheus, cloudwatch") | ||
} | ||
} | ||
|
||
private def createPrometheusRegistry(conf: Config): PrometheusMeterRegistry = { | ||
// noinspection ScalaUnusedSymbol | ||
implicit val gatewayReader: ConfigReader[PushGatewayConfig] = deriveReader[PushGatewayConfig] | ||
implicit val prometheusReader: ConfigReader[PrometheusConfig] = deriveReader[PrometheusConfig] | ||
val config = ConfigSource.fromConfig(conf).loadOrThrow[PrometheusConfig] | ||
val dependentClose = new AtomicReference[Closeable]() | ||
val registry = new PrometheusMeterRegistry(k => config.properties.getOrElse(k, null)) { | ||
override def close(): Unit = { | ||
CloseWithLogging(Option(dependentClose.get())) | ||
super.close() | ||
} | ||
} | ||
registry.throwExceptionOnRegistrationFailure() | ||
if (config.rename) { | ||
registry.config().meterFilter(new PrometheusRenameFilter()) | ||
} | ||
if (config.commonTags.nonEmpty) { | ||
val tags = config.commonTags.map { case (k, v) => Tag.of(k, v) } | ||
registry.config.commonTags(tags.asJava) | ||
} | ||
config.pushGateway match { | ||
case None => | ||
val server = | ||
HTTPServer.builder() | ||
.port(config.port) | ||
.registry(registry.getPrometheusRegistry) | ||
.buildAndStart() | ||
dependentClose.set(server) | ||
|
||
case Some(pg) => | ||
val builder = PushGateway.builder().registry(registry.getPrometheusRegistry).address(pg.host) | ||
pg.job.foreach(builder.job) | ||
pg.format.foreach(v => builder.format(Format.valueOf(v.toUpperCase(Locale.US)))) | ||
pg.scheme.foreach(v => builder.scheme(Scheme.fromString(v.toLowerCase(Locale.US)))) | ||
val pushGateway = builder.build() | ||
dependentClose.set(() => pushGateway.pushAdd()) | ||
} | ||
registry | ||
} | ||
|
||
private def createCloudwatchRegistry(conf: Config): CloudWatchMeterRegistry = { | ||
implicit val reader: ConfigReader[CloudwatchConfig] = deriveReader[CloudwatchConfig] | ||
val config = ConfigSource.fromConfig(conf).loadOrThrow[CloudwatchConfig] | ||
new CloudWatchMeterRegistry(k => config.properties.getOrElse(k, null), Clock.SYSTEM, CloudWatchAsyncClient.create()) | ||
} | ||
|
||
private case class MetricsConfig( | ||
registries: Seq[Config], | ||
bindings: MetricsBindings | ||
) | ||
|
||
private case class MetricsBindings( | ||
classloader: Boolean = false, | ||
memory: Boolean = false, | ||
gc: Boolean = false, | ||
processor: Boolean = false, | ||
threads: Boolean = false, | ||
) | ||
|
||
private case class RegistryConfig( | ||
`type`: String, | ||
enabled: Boolean = true, | ||
) | ||
|
||
private case class PrometheusConfig( | ||
rename: Boolean = false, | ||
commonTags: Map[String, String] = Map.empty, | ||
port: Int = 9090, | ||
// additional config can also be done via sys props - see https://prometheus.github.io/client_java/config/config/ | ||
properties: Map[String, String] = Map.empty, | ||
pushGateway: Option[PushGatewayConfig], | ||
) | ||
|
||
private case class PushGatewayConfig( | ||
host: String, | ||
scheme: Option[String], | ||
job: Option[String], | ||
format: Option[String], | ||
) | ||
|
||
private case class CloudwatchConfig( | ||
namespace: String = "geomesa", | ||
properties: Map[String, String] = Map.empty | ||
) | ||
} |
Oops, something went wrong.