From 6d7af1ad8e43006abf52d5c8d268d45fb2c4fb2a Mon Sep 17 00:00:00 2001 From: David Byron Date: Thu, 28 Oct 2021 17:24:52 -0700 Subject: [PATCH] add a mechanism to tag metrics from the handler context to distinguish metrics from multiple clients --- spectator-ext-aws/README.md | 37 +++++++++++++++++++ .../aws/SpectatorRequestMetricCollector.java | 36 +++++++++++++++++- .../SpectatorRequestMetricCollectorTest.java | 20 ++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/spectator-ext-aws/README.md b/spectator-ext-aws/README.md index 18562e7ec..e8bee18d3 100644 --- a/spectator-ext-aws/README.md +++ b/spectator-ext-aws/README.md @@ -79,3 +79,40 @@ aws.request.httpClientPoolPendingCount | AWSRequestMetrics.Field.HttpClientPoo Any throttling exception that occurs is tracked in a timer `aws.request.throttling` with the same set of tags as the other metrics, and one additional tag `throttleException` containing the exception that caused the throttling to occur. + +# Tag via handler context + +To help distinguish metrics from multiple clients, it's possible to specify a +[HandlerContextKey](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/handlers/HandlerContextKey.html) +for a SpectatorRequestMetricCollector. When specified, the +SpectatorRequestMetricCollector looks for a value from each request's handler +context for the given key, and, if there's a value for that key, adds a tag to +each metric of the form + +`handlerContextKey.getName()`:value + +One way to put information into the handler context that's the same for all +requests from a particular client is: + +`````java +class MyRequestHandler extends RequestHandler2 { + @Override + public void beforeRequest(Request request) { + request.addHandlerContext(HandlerContextKey.OPERATION_NAME, "myValue"); + } +} +````` +and then, for an s3 client for example: + +`````java +MyRequestHandler myRequestHandler = new MyRequestHandler(); +AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard(); +builder.setRequestHandlers(myRequestHandler); +````` + +Each request metric from the resulting AmazonS3 client willl have the tag +`HandlerContextKey.OPERATION_NAME.getName()`:myValue. + +Note that a custom handler context key (e.g. new +HandlerContextKey("myContextKey") works in addition to the predefined +ones in HandlerContextKey. diff --git a/spectator-ext-aws/src/main/java/com/netflix/spectator/aws/SpectatorRequestMetricCollector.java b/spectator-ext-aws/src/main/java/com/netflix/spectator/aws/SpectatorRequestMetricCollector.java index dd5065533..0f7c82d83 100644 --- a/spectator-ext-aws/src/main/java/com/netflix/spectator/aws/SpectatorRequestMetricCollector.java +++ b/spectator-ext-aws/src/main/java/com/netflix/spectator/aws/SpectatorRequestMetricCollector.java @@ -17,6 +17,7 @@ import com.amazonaws.Request; import com.amazonaws.Response; +import com.amazonaws.handlers.HandlerContextKey; import com.amazonaws.metrics.RequestMetricCollector; import com.amazonaws.util.AWSRequestMetrics; import com.amazonaws.util.AWSRequestMetrics.Field; @@ -91,6 +92,7 @@ public class SpectatorRequestMetricCollector extends RequestMetricCollector { private final Registry registry; private final Map customTags; + private final HandlerContextKey handlerContextKey; /** * Constructs a new instance. @@ -100,10 +102,29 @@ public SpectatorRequestMetricCollector(Registry registry) { } /** - * Constructs a new instance. Custom tags provided by the user will be applied to every metric. - * Overriding built-in tags is not allowed. + * Constructs a new instance using the specified handler context key and no + * custom tags. + */ + public SpectatorRequestMetricCollector(Registry registry, HandlerContextKey handlerContextKey) { + this(registry, Collections.emptyMap(), handlerContextKey); + } + + /** + * Constructs a new instance using no handler context key and custom tags. */ public SpectatorRequestMetricCollector(Registry registry, Map customTags) { + this(registry, customTags, null); + } + + /** + * Constructs a new instance using the optional handler context key. If + * present, and a request context has a value for this key, the key/value pair + * is an additional tag on every metric for that request. Custom tags + * provided by the user will be applied to every metric. Overriding built-in + * tags is not allowed. + */ + public SpectatorRequestMetricCollector(Registry registry, Map customTags, + HandlerContextKey handlerContextKey) { super(); this.registry = Preconditions.checkNotNull(registry, "registry"); Preconditions.checkNotNull(customTags, "customTags"); @@ -116,6 +137,13 @@ public SpectatorRequestMetricCollector(Registry registry, Map cu this.customTags.put(key, value); } }); + this.handlerContextKey = handlerContextKey; + if ((this.handlerContextKey != null) + && ALL_DEFAULT_TAGS.contains(this.handlerContextKey.getName())) { + registry.propagate(new IllegalArgumentException("Invalid handler context key " + + this.handlerContextKey.getName() + + " - cannot override built-in tag")); + } } @Override @@ -167,6 +195,10 @@ private Map getAllTags(Request request) { allTags.put(tag.getName(), tag.getValue(metrics).orElse(UNKNOWN)); } allTags.put(TAG_REQUEST_TYPE, request.getOriginalRequest().getClass().getSimpleName()); + String contextTagValue = request.getHandlerContext(handlerContextKey); + if (contextTagValue != null) { + allTags.put(handlerContextKey.getName(), contextTagValue); + } final boolean error = isError(metrics); if (error) { for (TagField tag : ERRORS) { diff --git a/spectator-ext-aws/src/test/java/com/netflix/spectator/aws/SpectatorRequestMetricCollectorTest.java b/spectator-ext-aws/src/test/java/com/netflix/spectator/aws/SpectatorRequestMetricCollectorTest.java index 574374460..5a2539e48 100644 --- a/spectator-ext-aws/src/test/java/com/netflix/spectator/aws/SpectatorRequestMetricCollectorTest.java +++ b/spectator-ext-aws/src/test/java/com/netflix/spectator/aws/SpectatorRequestMetricCollectorTest.java @@ -18,6 +18,7 @@ import com.amazonaws.DefaultRequest; import com.amazonaws.Request; import com.amazonaws.Response; +import com.amazonaws.handlers.HandlerContextKey; import com.amazonaws.http.HttpResponse; import com.amazonaws.services.cloudwatch.model.ListMetricsRequest; import com.amazonaws.util.AWSRequestMetrics; @@ -49,6 +50,12 @@ public void setUp() { } private void execRequest(String endpoint, int status) { + execRequest(endpoint, status, null, null); + } + + private void execRequest(String endpoint, int status, + HandlerContextKey handlerContextKey, + String handlerContextValue) { AWSRequestMetrics metrics = new AWSRequestMetricsFullSupport(); metrics.addProperty(AWSRequestMetrics.Field.ServiceName, "AmazonCloudWatch"); metrics.addProperty(AWSRequestMetrics.Field.ServiceEndpoint, endpoint); @@ -68,6 +75,9 @@ private void execRequest(String endpoint, int status) { req.setAWSRequestMetrics(metrics); req.setEndpoint(URI.create(endpoint)); + if ((handlerContextKey != null) && (handlerContextValue != null)) { + req.addHandlerContext(handlerContextKey, handlerContextValue); + } HttpResponse hr = new HttpResponse(req, new HttpPost(endpoint)); hr.setStatusCode(status); Response resp = new Response<>(null, new HttpResponse(req, new HttpPost(endpoint))); @@ -175,4 +185,14 @@ public void testCustomTags_overrideDefault() { assertThrows(IllegalArgumentException.class, () -> new SpectatorRequestMetricCollector(new DefaultRegistry(Clock.SYSTEM, config), customTags)); } + + @Test + public void testHandlerContextKey() { + String contextKeyName = "myContextKey"; + HandlerContextKey handlerContextKey = new HandlerContextKey<>(contextKeyName); + collector = new SpectatorRequestMetricCollector(registry, handlerContextKey); + String handlerContextValue = "some-value"; + execRequest("http://monitoring", 503, handlerContextKey, handlerContextValue); + assertEquals(set(handlerContextValue), valueSet(contextKeyName)); + } }