Count API calls by authentication status
This commit is contained in:
parent
fbbc4b8b27
commit
9b835633ab
|
@ -34,8 +34,8 @@ import org.whispersystems.textsecuregcm.util.logging.UriInfoUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gathers and reports HTTP request metrics at the Jetty container level, which sits above Jersey. In order to get
|
* Gathers and reports HTTP request metrics at the Jetty container level, which sits above Jersey. In order to get
|
||||||
* templated Jersey request paths, it implements {@link jakarta.ws.rs.container.ContainerResponseFilter}, in order to give
|
* templated Jersey request paths, it implements {@link jakarta.ws.rs.container.ContainerResponseFilter}. It is limited
|
||||||
* itself access to the template. It is limited to {@link TrafficSource#HTTP} requests.
|
* to {@link TrafficSource#HTTP} requests.
|
||||||
* <p>
|
* <p>
|
||||||
* It implements {@link LifeCycle.Listener} without overriding methods, so that it can be an event listener that
|
* It implements {@link LifeCycle.Listener} without overriding methods, so that it can be an event listener that
|
||||||
* Dropwizard will attach to the container—the {@link Container.Listener} implementation is where it attaches
|
* Dropwizard will attach to the container—the {@link Container.Listener} implementation is where it attaches
|
||||||
|
@ -58,6 +58,8 @@ public class MetricsHttpChannelListener implements HttpChannel.Listener, Contain
|
||||||
// Use the same counter namespace as MetricsRequestEventListener for continuity
|
// Use the same counter namespace as MetricsRequestEventListener for continuity
|
||||||
public static final String REQUEST_COUNTER_NAME = MetricsRequestEventListener.REQUEST_COUNTER_NAME;
|
public static final String REQUEST_COUNTER_NAME = MetricsRequestEventListener.REQUEST_COUNTER_NAME;
|
||||||
public static final String REQUESTS_BY_VERSION_COUNTER_NAME = MetricsRequestEventListener.REQUESTS_BY_VERSION_COUNTER_NAME;
|
public static final String REQUESTS_BY_VERSION_COUNTER_NAME = MetricsRequestEventListener.REQUESTS_BY_VERSION_COUNTER_NAME;
|
||||||
|
public static final String REQUESTS_BY_AUTHENTICATION_COUNTER_NAME = MetricsRequestEventListener.REQUESTS_BY_AUTHENTICATION_COUNTER_NAME;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String URI_INFO_PROPERTY_NAME = MetricsHttpChannelListener.class.getName() + ".uriInfo";
|
static final String URI_INFO_PROPERTY_NAME = MetricsHttpChannelListener.class.getName() + ".uriInfo";
|
||||||
|
|
||||||
|
@ -141,9 +143,19 @@ public class MetricsHttpChannelListener implements HttpChannel.Listener, Contain
|
||||||
|
|
||||||
meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment();
|
meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment();
|
||||||
|
|
||||||
|
final Tag clientPlatformTag = UserAgentTagUtil.getPlatformTag(requestInfo.userAgent());
|
||||||
|
|
||||||
UserAgentTagUtil.getClientVersionTag(requestInfo.userAgent(), clientReleaseManager).ifPresent(
|
UserAgentTagUtil.getClientVersionTag(requestInfo.userAgent(), clientReleaseManager).ifPresent(
|
||||||
clientVersionTag -> meterRegistry.counter(REQUESTS_BY_VERSION_COUNTER_NAME,
|
clientVersionTag -> meterRegistry.counter(REQUESTS_BY_VERSION_COUNTER_NAME,
|
||||||
Tags.of(clientVersionTag, UserAgentTagUtil.getPlatformTag(requestInfo.userAgent()))).increment());
|
Tags.of(clientVersionTag, clientPlatformTag)).increment());
|
||||||
|
|
||||||
|
meterRegistry.counter(REQUESTS_BY_AUTHENTICATION_COUNTER_NAME, Tags.of(
|
||||||
|
TRAFFIC_SOURCE_TAG, TrafficSource.HTTP.name().toLowerCase(),
|
||||||
|
PATH_TAG, requestInfo.path(),
|
||||||
|
METHOD_TAG, requestInfo.method(),
|
||||||
|
"authenticated", String.valueOf(request.getHeader(HttpHeaders.AUTHORIZATION) != null))
|
||||||
|
.and(clientPlatformTag))
|
||||||
|
.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,6 +17,8 @@ import org.glassfish.jersey.server.monitoring.RequestEvent;
|
||||||
import org.glassfish.jersey.server.monitoring.RequestEventListener;
|
import org.glassfish.jersey.server.monitoring.RequestEventListener;
|
||||||
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
|
import org.whispersystems.textsecuregcm.storage.ClientReleaseManager;
|
||||||
import org.whispersystems.textsecuregcm.util.logging.UriInfoUtil;
|
import org.whispersystems.textsecuregcm.util.logging.UriInfoUtil;
|
||||||
|
import org.whispersystems.websocket.ReusableAuth;
|
||||||
|
import org.whispersystems.websocket.WebSocketResourceProvider;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -32,6 +34,7 @@ public class MetricsRequestEventListener implements RequestEventListener {
|
||||||
|
|
||||||
public static final String REQUEST_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "request");
|
public static final String REQUEST_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "request");
|
||||||
public static final String REQUESTS_BY_VERSION_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "requestByVersion");
|
public static final String REQUESTS_BY_VERSION_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "requestByVersion");
|
||||||
|
public static final String REQUESTS_BY_AUTHENTICATION_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "requestByAuthentication");
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String PATH_TAG = "path";
|
static final String PATH_TAG = "path";
|
||||||
|
@ -78,17 +81,31 @@ public class MetricsRequestEventListener implements RequestEventListener {
|
||||||
@Nullable final String userAgent;
|
@Nullable final String userAgent;
|
||||||
{
|
{
|
||||||
final List<String> userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT);
|
final List<String> userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT);
|
||||||
userAgent = userAgentValues != null && !userAgentValues.isEmpty() ? userAgentValues.get(0) : null;
|
userAgent = userAgentValues != null && !userAgentValues.isEmpty() ? userAgentValues.getFirst() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.addAll(UserAgentTagUtil.getLibsignalAndPlatformTags(userAgent));
|
tags.addAll(UserAgentTagUtil.getLibsignalAndPlatformTags(userAgent));
|
||||||
|
|
||||||
meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment();
|
meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment();
|
||||||
|
|
||||||
|
final Tag clientPlatformTag = UserAgentTagUtil.getPlatformTag(userAgent);
|
||||||
|
|
||||||
UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager)
|
UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager)
|
||||||
.ifPresent(clientVersionTag -> meterRegistry.counter(REQUESTS_BY_VERSION_COUNTER_NAME,
|
.ifPresent(clientVersionTag -> meterRegistry.counter(REQUESTS_BY_VERSION_COUNTER_NAME,
|
||||||
Tags.of(clientVersionTag, UserAgentTagUtil.getPlatformTag(userAgent)))
|
Tags.of(clientVersionTag, clientPlatformTag))
|
||||||
.increment());
|
.increment());
|
||||||
|
|
||||||
|
final boolean authenticated =
|
||||||
|
event.getContainerRequest().getProperty(WebSocketResourceProvider.REUSABLE_AUTH_PROPERTY) instanceof ReusableAuth<?> reusableAuth &&
|
||||||
|
reusableAuth.ref().isPresent();
|
||||||
|
|
||||||
|
meterRegistry.counter(REQUESTS_BY_AUTHENTICATION_COUNTER_NAME, Tags.of(
|
||||||
|
TRAFFIC_SOURCE_TAG, trafficSource.name().toLowerCase(),
|
||||||
|
PATH_TAG, UriInfoUtil.getPathTemplate(event.getUriInfo()),
|
||||||
|
METHOD_TAG, event.getContainerRequest().getMethod(),
|
||||||
|
"authenticated", String.valueOf(authenticated))
|
||||||
|
.and(clientPlatformTag))
|
||||||
|
.increment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.anyIterable;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -89,6 +89,12 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
private static final DropwizardAppExtension<Configuration> EXTENSION = new DropwizardAppExtension<>(
|
private static final DropwizardAppExtension<Configuration> EXTENSION = new DropwizardAppExtension<>(
|
||||||
MetricsHttpChannelListenerIntegrationTest.TestApplication.class);
|
MetricsHttpChannelListenerIntegrationTest.TestApplication.class);
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpBefore() {
|
||||||
|
when(METER_REGISTRY.counter(anyString(), anyIterable())).thenReturn(mock(Counter.class));
|
||||||
|
when(METER_REGISTRY.counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), anyIterable())).thenReturn(COUNTER);
|
||||||
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void teardown() {
|
void teardown() {
|
||||||
reset(METER_REGISTRY);
|
reset(METER_REGISTRY);
|
||||||
|
@ -104,14 +110,7 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
COUNT_DOWN_LATCH_FUTURE_REFERENCE.set(countDownLatch);
|
COUNT_DOWN_LATCH_FUTURE_REFERENCE.set(countDownLatch);
|
||||||
|
|
||||||
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
final Client client = EXTENSION.client();
|
||||||
when(METER_REGISTRY.counter(anyString(), any(Iterable.class)))
|
|
||||||
.thenAnswer(a -> MetricsHttpChannelListener.REQUEST_COUNTER_NAME.equals(a.getArgument(0, String.class))
|
|
||||||
? COUNTER
|
|
||||||
: mock(Counter.class))
|
|
||||||
.thenReturn(COUNTER);
|
|
||||||
|
|
||||||
Client client = EXTENSION.client();
|
|
||||||
|
|
||||||
final Supplier<String> request = () -> client.target(
|
final Supplier<String> request = () -> client.target(
|
||||||
String.format("http://localhost:%d%s", EXTENSION.getLocalPort(), requestPath))
|
String.format("http://localhost:%d%s", EXTENSION.getLocalPort(), requestPath))
|
||||||
|
@ -140,6 +139,7 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
|
|
||||||
assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS));
|
assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
||||||
verify(METER_REGISTRY).counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), tagCaptor.capture());
|
verify(METER_REGISTRY).counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), tagCaptor.capture());
|
||||||
verify(COUNTER).increment();
|
verify(COUNTER).increment();
|
||||||
|
|
||||||
|
@ -185,13 +185,6 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
COUNT_DOWN_LATCH_FUTURE_REFERENCE.set(countDownLatch);
|
COUNT_DOWN_LATCH_FUTURE_REFERENCE.set(countDownLatch);
|
||||||
|
|
||||||
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
|
||||||
when(METER_REGISTRY.counter(anyString(), any(Iterable.class)))
|
|
||||||
.thenAnswer(a -> MetricsHttpChannelListener.REQUEST_COUNTER_NAME.equals(a.getArgument(0, String.class))
|
|
||||||
? COUNTER
|
|
||||||
: mock(Counter.class))
|
|
||||||
.thenReturn(COUNTER);
|
|
||||||
|
|
||||||
client.connect(new WebSocketListener() {
|
client.connect(new WebSocketListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(final Session session) {
|
public void onWebSocketConnect(final Session session) {
|
||||||
|
@ -202,6 +195,7 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
|
|
||||||
assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS));
|
assertTrue(countDownLatch.await(1000, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
||||||
verify(METER_REGISTRY).counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), tagCaptor.capture());
|
verify(METER_REGISTRY).counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), tagCaptor.capture());
|
||||||
verify(COUNTER).increment();
|
verify(COUNTER).increment();
|
||||||
|
|
||||||
|
@ -306,7 +300,7 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
@Override
|
@Override
|
||||||
public void beanAdded(final Container parent, final Object child) {
|
public void beanAdded(final Container parent, final Object child) {
|
||||||
if (child instanceof Connector connector) {
|
if (child instanceof Connector connector) {
|
||||||
connector.addBean(this);
|
connector.addBean(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +325,7 @@ class MetricsHttpChannelListenerIntegrationTest {
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/greet/{name}")
|
@Path("/greet/{name}")
|
||||||
public String testGreetByName(@PathParam("name") String name, @Context ContainerRequestContext context) {
|
public String testGreetByName(@PathParam("name") String name, @Context ContainerRequestContext ignored) {
|
||||||
|
|
||||||
if ("exception".equals(name)) {
|
if ("exception".equals(name)) {
|
||||||
throw new InternalServerErrorException();
|
throw new InternalServerErrorException();
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.metrics;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyIterable;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -48,12 +49,15 @@ class MetricsHttpChannelListenerTest {
|
||||||
requestCounter = mock(Counter.class);
|
requestCounter = mock(Counter.class);
|
||||||
requestsByVersionCounter = mock(Counter.class);
|
requestsByVersionCounter = mock(Counter.class);
|
||||||
|
|
||||||
when(meterRegistry.counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), any(Iterable.class)))
|
when(meterRegistry.counter(eq(MetricsHttpChannelListener.REQUEST_COUNTER_NAME), anyIterable()))
|
||||||
.thenReturn(requestCounter);
|
.thenReturn(requestCounter);
|
||||||
|
|
||||||
when(meterRegistry.counter(eq(MetricsHttpChannelListener.REQUESTS_BY_VERSION_COUNTER_NAME), any(Iterable.class)))
|
when(meterRegistry.counter(eq(MetricsHttpChannelListener.REQUESTS_BY_VERSION_COUNTER_NAME), anyIterable()))
|
||||||
.thenReturn(requestsByVersionCounter);
|
.thenReturn(requestsByVersionCounter);
|
||||||
|
|
||||||
|
when(meterRegistry.counter(eq(MetricsHttpChannelListener.REQUESTS_BY_AUTHENTICATION_COUNTER_NAME), anyIterable()))
|
||||||
|
.thenReturn(mock(Counter.class));
|
||||||
|
|
||||||
clientReleaseManager = mock(ClientReleaseManager.class);
|
clientReleaseManager = mock(ClientReleaseManager.class);
|
||||||
|
|
||||||
listener = new MetricsHttpChannelListener(meterRegistry, clientReleaseManager, Collections.emptySet());
|
listener = new MetricsHttpChannelListener(meterRegistry, clientReleaseManager, Collections.emptySet());
|
||||||
|
@ -134,8 +138,8 @@ class MetricsHttpChannelListenerTest {
|
||||||
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
|
||||||
verify(meterRegistry).counter(eq(MetricsHttpChannelListener.REQUESTS_BY_VERSION_COUNTER_NAME),
|
verify(meterRegistry).counter(eq(MetricsHttpChannelListener.REQUESTS_BY_VERSION_COUNTER_NAME),
|
||||||
tagCaptor.capture());
|
tagCaptor.capture());
|
||||||
|
|
||||||
final Set<Tag> tags = new HashSet<>();
|
final Set<Tag> tags = new HashSet<>();
|
||||||
tags.clear();
|
|
||||||
for (final Tag tag : tagCaptor.getValue()) {
|
for (final Tag tag : tagCaptor.getValue()) {
|
||||||
tags.add(tag);
|
tags.add(tag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyIterable;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -71,6 +73,8 @@ class MetricsRequestEventListenerTest {
|
||||||
meterRegistry = mock(MeterRegistry.class);
|
meterRegistry = mock(MeterRegistry.class);
|
||||||
counter = mock(Counter.class);
|
counter = mock(Counter.class);
|
||||||
|
|
||||||
|
when(meterRegistry.counter(anyString(), anyIterable())).thenReturn(mock(Counter.class));
|
||||||
|
|
||||||
final ClientReleaseManager clientReleaseManager = mock(ClientReleaseManager.class);
|
final ClientReleaseManager clientReleaseManager = mock(ClientReleaseManager.class);
|
||||||
when(clientReleaseManager.isVersionActive(any(), any())).thenReturn(false);
|
when(clientReleaseManager.isVersionActive(any(), any())).thenReturn(false);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue