Skip to content

Commit

Permalink
Create a new singleton metric with an API_NAME tag (apache#431)
Browse files Browse the repository at this point in the history
* Emit metrics with the API name as a tag

* Add tests

* slight cleanup

* Improve comments/readability

* Switch to snake_case, start deprecation

* Remove debug line

* Spotless toggle for deprecated tags

* Slightly scope down spotless toggle

* Fix test assumption on snowman principal name

* Fix RateLimiterFilterTest to use TestMetricsUtil

---------

Co-authored-by: Eric Maynard <[email protected]>
  • Loading branch information
andrew4699 and eric-maynard authored Nov 15, 2024
1 parent c10ba13 commit 877959f
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 26 deletions.
1 change: 1 addition & 0 deletions build-logic/src/main/kotlin/polaris-java.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ spotless {
licenseHeaderFile(rootProject.file("codestyle/copyright-header-java.txt"))
endWithNewline()
custom("disallowWildcardImports", disallowWildcardImports)
toggleOffOn()
}
kotlinGradle {
ktfmt().googleStyle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,53 @@

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.apache.polaris.core.resource.TimedApi;
import org.jetbrains.annotations.VisibleForTesting;

/**
* Wrapper around the Micrometer {@link MeterRegistry} providing additional metric management
* functions for the Polaris application. Implements in-memory caching of timers and counters.
* Records two metrics for each instrument with one tagged by the realm ID (realm-specific metric)
* and one without. The realm-specific metric is suffixed with ".realm".
*
* <p>Uppercase tag names are now deprecated. Prefer snake_casing instead. Old metrics are emitted
* with both variations but the uppercase versions may eventually be removed. New methods for tag
* emission (those that allow you to pass in an Iterable<Tag>) only emit the snake_case version.
*/
public class PolarisMetricRegistry {
private final MeterRegistry meterRegistry;
private final ConcurrentMap<String, Timer> timers = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Counter> counters = new ConcurrentHashMap<>();
private static final String TAG_REALM = "REALM_ID";
private static final String TAG_RESP_CODE = "HTTP_RESPONSE_CODE";
private static final String SUFFIX_COUNTER = ".count";
private static final String SUFFIX_ERROR = ".error";
private static final String SUFFIX_REALM = ".realm";

/**
* @deprecated See class Javadoc.
*/
public static final String TAG_REALM_DEPRECATED = "REALM_ID";

public static final String TAG_REALM = "realm_id";

/**
* @deprecated See class Javadoc.
*/
public static final String TAG_RESP_CODE_DEPRECATED = "HTTP_RESPONSE_CODE";

public static final String TAG_RESP_CODE = "http_response_code";

public static final String SUFFIX_COUNTER = ".count";
public static final String SUFFIX_ERROR = ".error";
public static final String SUFFIX_REALM = ".realm";

public PolarisMetricRegistry(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
Expand All @@ -61,6 +81,12 @@ public MeterRegistry getMeterRegistry() {
return meterRegistry;
}

@VisibleForTesting
public void clear() {
meterRegistry.clear();
counters.clear();
}

public void init(Class<?>... classes) {
for (Class<?> clazz : classes) {
Method[] methods = clazz.getDeclaredMethods();
Expand All @@ -75,8 +101,12 @@ public void init(Class<?>... classes) {

// Error counters contain the HTTP response code in a tag, thus caching them would not be
// meaningful.
Counter.builder(metric + SUFFIX_ERROR).tags(TAG_RESP_CODE, "400").register(meterRegistry);
Counter.builder(metric + SUFFIX_ERROR).tags(TAG_RESP_CODE, "500").register(meterRegistry);
Counter.builder(metric + SUFFIX_ERROR)
.tags(TAG_RESP_CODE, "400", TAG_RESP_CODE_DEPRECATED, "400")
.register(meterRegistry);
Counter.builder(metric + SUFFIX_ERROR)
.tags(TAG_RESP_CODE, "500", TAG_RESP_CODE_DEPRECATED, "500")
.register(meterRegistry);
}
}
}
Expand All @@ -93,10 +123,15 @@ public void recordTimer(String metric, long elapsedTimeMs, String realmId) {
m ->
Timer.builder(metric + SUFFIX_REALM)
.tag(TAG_REALM, realmId)
.tag(TAG_REALM_DEPRECATED, realmId)
.register(meterRegistry));
timerRealm.record(elapsedTimeMs, TimeUnit.MILLISECONDS);
}

/**
* Increments metric.count and metric.count.realm. The realmId is tagged on the latter metric.
* Counters are cached.
*/
public void incrementCounter(String metric, String realmId) {
String counterMetric = metric + SUFFIX_COUNTER;
Counter counter =
Expand All @@ -110,21 +145,73 @@ public void incrementCounter(String metric, String realmId) {
m ->
Counter.builder(counterMetric + SUFFIX_REALM)
.tag(TAG_REALM, realmId)
.tag(TAG_REALM_DEPRECATED, realmId)
.register(meterRegistry));
counterRealm.increment();
}

/**
* Increments metric.error and metric.error.realm. The realmId is tagged on the latter metric.
* Both metrics are tagged with the statusCode and counters are not cached.
*/
public void incrementErrorCounter(String metric, int statusCode, String realmId) {
String errorMetric = metric + SUFFIX_ERROR;
Counter.builder(errorMetric)
.tag(TAG_RESP_CODE, String.valueOf(statusCode))
.tag(TAG_RESP_CODE_DEPRECATED, String.valueOf(statusCode))
.register(meterRegistry)
.increment();

Counter.builder(errorMetric + SUFFIX_REALM)
.tag(TAG_RESP_CODE, String.valueOf(statusCode))
.tag(TAG_RESP_CODE_DEPRECATED, String.valueOf(statusCode))
.tag(TAG_REALM, realmId)
.tag(TAG_REALM_DEPRECATED, realmId)
.register(meterRegistry)
.increment();
}

/**
* Increments metric.count and metric.count.realm. The realmId is tagged on the latter metric.
* Arbitrary tags can be specified and counters are not cached.
*/
public void incrementCounter(String metric, String realmId, Iterable<Tag> tags) {
Counter.builder(metric + SUFFIX_COUNTER).tags(tags).register(meterRegistry).increment();

Counter.builder(metric + SUFFIX_COUNTER + SUFFIX_REALM)
.tags(tags)
.tag(TAG_REALM, realmId)
.register(meterRegistry)
.increment();
}

/**
* Increments metric.count and metric.count.realm. The realmId is tagged on the latter metric.
* Arbitrary tags can be specified and counters are not cached.
*/
public void incrementCounter(String metric, String realmId, Tag tag) {
incrementCounter(metric, realmId, Collections.singleton(tag));
}

/**
* Increments metric.error and metric.error.realm. The realmId is tagged on the latter metric.
* Arbitrary tags can be specified and counters are not cached.
*/
public void incrementErrorCounter(String metric, String realmId, Iterable<Tag> tags) {
Counter.builder(metric + SUFFIX_ERROR).tags(tags).register(meterRegistry).increment();

Counter.builder(metric + SUFFIX_ERROR + SUFFIX_REALM)
.tags(tags)
.tag(TAG_REALM, realmId)
.register(meterRegistry)
.increment();
}

/**
* Increments metric.error and metric.error.realm. The realmId is tagged on the latter metric.
* Arbitrary tags can be specified and counters are not cached.
*/
public void incrementErrorCounter(String metric, String realmId, Tag tag) {
incrementErrorCounter(metric, realmId, Collections.singleton(tag));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
*/
package org.apache.polaris.service;

import static org.apache.polaris.core.monitor.PolarisMetricRegistry.TAG_RESP_CODE;

import com.google.common.base.Stopwatch;
import io.micrometer.core.instrument.Tag;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.ext.Provider;
import org.apache.polaris.core.context.CallContext;
Expand All @@ -29,6 +33,7 @@
import org.glassfish.jersey.server.monitoring.ApplicationEventListener;
import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.server.monitoring.RequestEventListener;
import org.jetbrains.annotations.VisibleForTesting;

/**
* An ApplicationEventListener that supports timing and error counting of Jersey resource methods
Expand All @@ -38,13 +43,26 @@
@Provider
public class TimedApplicationEventListener implements ApplicationEventListener {

/**
* Each API will increment a common counter (SINGLETON_METRIC_NAME) but have its API name tagged
* (TAG_API_NAME).
*/
public static final String SINGLETON_METRIC_NAME = "polaris.api";

public static final String TAG_API_NAME = "api_name";

// The PolarisMetricRegistry instance used for recording metrics and error counters.
private final PolarisMetricRegistry polarisMetricRegistry;

public TimedApplicationEventListener(PolarisMetricRegistry polarisMetricRegistry) {
this.polarisMetricRegistry = polarisMetricRegistry;
}

@VisibleForTesting
public PolarisMetricRegistry getMetricRegistry() {
return polarisMetricRegistry;
}

@Override
public void onEvent(ApplicationEvent event) {}

Expand Down Expand Up @@ -72,7 +90,11 @@ public void onEvent(RequestEvent event) {
if (method.isAnnotationPresent(TimedApi.class)) {
TimedApi timedApi = method.getAnnotation(TimedApi.class);
metric = timedApi.value();

// Increment both the counter with the API name in the metric name and a common metric
polarisMetricRegistry.incrementCounter(metric, realmId);
polarisMetricRegistry.incrementCounter(
SINGLETON_METRIC_NAME, realmId, Tag.of(TAG_API_NAME, metric));
}
} else if (event.getType() == RequestEvent.Type.RESOURCE_METHOD_START && metric != null) {
sw = Stopwatch.createStarted();
Expand All @@ -82,7 +104,14 @@ public void onEvent(RequestEvent event) {
polarisMetricRegistry.recordTimer(metric, sw.elapsed(TimeUnit.MILLISECONDS), realmId);
} else {
int statusCode = event.getContainerResponse().getStatus();

// Increment both the counter with the API name in the metric name and a common metric
polarisMetricRegistry.incrementErrorCounter(metric, statusCode, realmId);
polarisMetricRegistry.incrementErrorCounter(
SINGLETON_METRIC_NAME,
realmId,
List.of(
Tag.of(TAG_API_NAME, metric), Tag.of(TAG_RESP_CODE, String.valueOf(statusCode))));
}
}
}
Expand Down
Loading

0 comments on commit 877959f

Please sign in to comment.