-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support for pluggable metric reporting (#81)
- Loading branch information
1 parent
5f966b0
commit f62012d
Showing
9 changed files
with
198 additions
and
3 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
92 changes: 92 additions & 0 deletions
92
core/src/main/scala/akka/persistence/dynamodb/util/SDKClientMetricsProvider.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,92 @@ | ||
/* | ||
* Copyright (C) 2024 Lightbend Inc. <https://www.lightbend.com> | ||
*/ | ||
|
||
package akka.persistence.dynamodb.util | ||
|
||
import akka.actor.ClassicActorSystemProvider | ||
import akka.actor.ExtendedActorSystem | ||
import akka.annotation.ApiMayChange | ||
import akka.annotation.InternalApi | ||
|
||
import software.amazon.awssdk.metrics.MetricCollection | ||
import software.amazon.awssdk.metrics.MetricPublisher | ||
|
||
import scala.jdk.CollectionConverters.ListHasAsScala | ||
|
||
import java.util.concurrent.ConcurrentHashMap | ||
|
||
/** | ||
* Service Provider Interface for injecting AWS SDK MetricPublisher into the underlying DynamoDB client (see | ||
* https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/metrics-list.html). | ||
* | ||
* Implementations must include a single constructor with one argument: an `akka.actor.ClassicActorSystemProvider`. To | ||
* setup your implementation, add a setting to your 'application.conf': | ||
* | ||
* {{{ | ||
* akka.persistence.dynamodb.client.metrics-providers += com.myexample.MyAWSMetricsProvider | ||
* }}} | ||
*/ | ||
@ApiMayChange | ||
trait AWSClientMetricsProvider { | ||
|
||
/** | ||
* Given an overall config path for Akka Persistence DynamoDB (e.g. 'akka.persistence.dynamodb') returns an instance | ||
* of an AWS SDK MetricPublisher which publishes SDK client metrics to the location of this implementation's choosing. | ||
*/ | ||
def metricPublisherFor(configLocation: String): MetricPublisher | ||
} | ||
|
||
/** INTERNAL API */ | ||
@InternalApi | ||
private[dynamodb] object AWSClientMetricsResolver { | ||
def resolve(system: ClassicActorSystemProvider): Option[AWSClientMetricsProvider] = { | ||
val providersPath = "akka.persistence.dynamodb.client.metrics-providers" | ||
val config = system.classicSystem.settings.config | ||
if (!config.hasPath(providersPath)) { | ||
None | ||
} else { | ||
val fqcns = config.getStringList(providersPath) | ||
|
||
fqcns.size match { | ||
case 0 => None | ||
case 1 => Some(createProvider(system, fqcns.get(0))) | ||
case _ => | ||
val providers = fqcns.asScala.toSeq.map(fqcn => createProvider(system, fqcn)) | ||
Some(new EnsembleAWSClientMetricsProvider(providers)) | ||
} | ||
} | ||
} | ||
|
||
def createProvider(system: ClassicActorSystemProvider, fqcn: String): AWSClientMetricsProvider = { | ||
system.classicSystem | ||
.asInstanceOf[ExtendedActorSystem] | ||
.dynamicAccess | ||
.createInstanceFor[AWSClientMetricsProvider](fqcn, List(classOf[ClassicActorSystemProvider] -> system)) | ||
.get | ||
} | ||
|
||
// This technically does not follow the construction convention that would allow it | ||
// to be reflectively constructed, but we don't reflectively construct it | ||
private class EnsembleAWSClientMetricsProvider(providers: Seq[AWSClientMetricsProvider]) | ||
extends AWSClientMetricsProvider { | ||
private val instances = new ConcurrentHashMap[String, MetricPublisher]() | ||
|
||
def metricPublisherFor(configLocation: String): MetricPublisher = | ||
instances.computeIfAbsent( | ||
configLocation, | ||
path => | ||
new MetricPublisher { | ||
private val publishers = providers.map(_.metricPublisherFor(configLocation)) | ||
|
||
def publish(metricCollection: MetricCollection): Unit = { | ||
publishers.foreach(_.publish(metricCollection)) | ||
} | ||
|
||
def close(): Unit = { | ||
publishers.foreach(_.close()) | ||
} | ||
}) | ||
|
||
} | ||
} |
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,29 @@ | ||
# Observability | ||
|
||
This plugin supports injecting an [AWS `MetricPublisher`](https://github.com/aws/aws-sdk-java-v2/blob/master/docs/design/core/metrics/Design.md) into the underlying DynamoDB SDK client. This injection is accomplished by defining a class @scala[extending]@java[implementing] @apidoc[akka.persistence.dynamodb.util.AWSClientMetricsProvider]. | ||
|
||
Your implementation must expose a single constructor with one argument: an `akka.actor.ClassicActorSystemProvider`. Its `metricPublisherFor` method will take the config path to the `client` section of this instance of the plugin @ref:[configuration](config.md#multiple-plugins). | ||
|
||
The AWS SDK provides an implementation of `MetricPublisher` which publishes to [Amazon CloudWatch](https://docs.aws.amazon.com/cloudwatch/). An `AWSClientMetricsProvider` providing [this `MetricPublisher`](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/metrics.html) with defaults would look like: | ||
|
||
Scala | ||
: @@snip [cloudwatch default](/docs/src/test/scala/docs/CloudWatchProvider.scala) { #cloudwatch-default } | ||
|
||
Java | ||
: @@snip [cloudwatch default](/docs/src/test/java/jdocs/CloudWatchWithDefaultConfigurationMetricsProvider.java) { #cloudwatch-default } | ||
|
||
To register your provider implementation with the plugin, add its fully-qualified class name to the configuration path `akka.persistence.dynamodb.client.metrics-providers` (e.g. in `application.conf`): | ||
|
||
``` | ||
akka.persistence.dynamodb.client.metrics-providers += domain.package.CloudWatchWithDefaultConfigurationMetricsProvider | ||
``` | ||
|
||
In a test case, it may be useful to set the entire list of `metrics-providers` explicitly: | ||
|
||
``` | ||
akka.persistence.dynamodb.client.metrics-providers = [ "domain.package.CloudWatchWithDefaultConfigurationMetricsProvider" ] | ||
``` | ||
|
||
If multiple providers are specified, they will automatically be combined into a "meta-provider" which provides a publisher which will publish using _all_ of the specified providers' respective publishers. | ||
|
||
If implementing your own `MetricPublisher`, [Amazon recommends that care be taken to not block the thread calling the methods of the `MetricPublisher`](https://github.com/aws/aws-sdk-java-v2/blob/master/docs/design/core/metrics/Design.md#performance): all I/O and "heavy" computation should be performed asynchronously and control immediately returned to the caller. |
19 changes: 19 additions & 0 deletions
19
docs/src/test/java/jdocs/CloudWatchWithDefaultConfigurationMetricsProvider.java
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,19 @@ | ||
package jdocs; | ||
|
||
// #cloudwatch-default | ||
import akka.actor.ClassicActorSystemProvider; | ||
import akka.persistence.dynamodb.util.AWSClientMetricsProvider; | ||
import software.amazon.awssdk.metrics.MetricPublisher; | ||
import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher; | ||
|
||
public class CloudWatchWithDefaultConfigurationMetricsProvider implements AWSClientMetricsProvider { | ||
public CloudWatchWithDefaultConfigurationMetricsProvider(ClassicActorSystemProvider system) { | ||
} | ||
|
||
@Override | ||
public MetricPublisher metricPublisherFor(String configLocation) { | ||
// These are just the defaults... a more elaborate configuration using its builder is possible | ||
return CloudWatchMetricPublisher.create(); | ||
} | ||
} | ||
// #cloudwatch-default |
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,16 @@ | ||
package docs | ||
|
||
// #cloudwatch-default | ||
import akka.actor.ClassicActorSystemProvider | ||
import akka.persistence.dynamodb.util.AWSClientMetricsProvider | ||
import software.amazon.awssdk.metrics.MetricPublisher | ||
import software.amazon.awssdk.metrics.publishers.cloudwatch.CloudWatchMetricPublisher | ||
|
||
class CloudWatchWithDefaultConfigurationMetricsProvider(system: ClassicActorSystemProvider) | ||
extends AWSClientMetricsProvider { | ||
def metricPublisherFor(configLocation: String): MetricPublisher = { | ||
// These are just the defaults... a more elaborate configuration using its builder is possible | ||
CloudWatchMetricPublisher.create() | ||
} | ||
} | ||
// #cloudwatch-default |
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