Simplify request counter

This commit is contained in:
Jon Chambers 2023-05-09 10:08:19 -04:00 committed by Chris Eager
parent 3214852a41
commit cb72e4f426
4 changed files with 216 additions and 388 deletions

View File

@ -134,7 +134,6 @@ import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges; import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges;
import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge; import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge;
import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener; import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener;
import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager; import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager;
import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge; import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge;
@ -277,9 +276,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
"host", HostnameUtil.getLocalHostname(), "host", HostnameUtil.getLocalHostname(),
"version", WhisperServerVersion.getServerVersion(), "version", WhisperServerVersion.getServerVersion(),
"env", config.getDatadogConfiguration().getEnvironment())) "env", config.getDatadogConfiguration().getEnvironment()))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME))
.meterFilter(MeterFilter.denyNameStartsWith(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME))
.meterFilter(new MeterFilter() { .meterFilter(new MeterFilter() {
@Override @Override
public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) { public DistributionStatisticConfig configure(final Id id, final DistributionStatisticConfig config) {

View File

@ -8,23 +8,16 @@ package org.whispersystems.textsecuregcm.metrics;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.vdurmont.semver4j.Semver;
import com.vdurmont.semver4j.SemverException;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.glassfish.jersey.server.monitoring.RequestEvent; 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.util.logging.UriInfoUtil; import org.whispersystems.textsecuregcm.util.logging.UriInfoUtil;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.util.ua.UserAgent; import java.util.ArrayList;
import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil; import java.util.List;
/** /**
* Gathers and reports request-level metrics. * Gathers and reports request-level metrics.
@ -32,26 +25,15 @@ import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
public class MetricsRequestEventListener implements RequestEventListener { 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 ANDROID_REQUEST_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "androidRequest");
public static final String DESKTOP_REQUEST_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "desktopRequest");
public static final String IOS_REQUEST_COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "iosRequest");
@VisibleForTesting
static final String PATH_TAG = "path"; static final String PATH_TAG = "path";
@VisibleForTesting
static final String STATUS_CODE_TAG = "status"; static final String STATUS_CODE_TAG = "status";
@VisibleForTesting
static final String TRAFFIC_SOURCE_TAG = "trafficSource"; static final String TRAFFIC_SOURCE_TAG = "trafficSource";
static final String OS_TAG = "os";
static final String SDK_TAG = "sdkVersion";
private static final Set<String> ACCEPTABLE_DESKTOP_OS_STRINGS = Set.of("linux", "macos", "windows");
private static final String ANDROID_SDK_PREFIX = "Android/";
private static final int MIN_ANDROID_SDK_VERSION = 19;
private static final int MAX_ANDROID_SDK_VERSION = 50;
private static final String IOS_VERSION_PREFIX = "iOS/";
private static final Pattern LEGACY_IOS_PATTERN = Pattern.compile("^\\(.*iOS ([0-9\\.]+).*\\)$");
private static final Semver MIN_IOS_VERSION = new Semver("8.0", Semver.SemverType.LOOSE);
private static final Semver MAX_IOS_VERSION = new Semver("20.0", Semver.SemverType.LOOSE);
private final TrafficSource trafficSource; private final TrafficSource trafficSource;
private final MeterRegistry meterRegistry; private final MeterRegistry meterRegistry;
@ -70,83 +52,21 @@ public class MetricsRequestEventListener implements RequestEventListener {
public void onEvent(final RequestEvent event) { public void onEvent(final RequestEvent event) {
if (event.getType() == RequestEvent.Type.FINISHED) { if (event.getType() == RequestEvent.Type.FINISHED) {
if (!event.getUriInfo().getMatchedTemplates().isEmpty()) { if (!event.getUriInfo().getMatchedTemplates().isEmpty()) {
final List<Tag> tags = new ArrayList<>(5); final List<Tag> tags = new ArrayList<>(4);
tags.add(Tag.of(PATH_TAG, UriInfoUtil.getPathTemplate(event.getUriInfo()))); tags.add(Tag.of(PATH_TAG, UriInfoUtil.getPathTemplate(event.getUriInfo())));
tags.add(Tag.of(STATUS_CODE_TAG, String.valueOf(event.getContainerResponse().getStatus()))); tags.add(Tag.of(STATUS_CODE_TAG, String.valueOf(event.getContainerResponse().getStatus())));
tags.add(Tag.of(TRAFFIC_SOURCE_TAG, trafficSource.name().toLowerCase())); tags.add(Tag.of(TRAFFIC_SOURCE_TAG, trafficSource.name().toLowerCase()));
@Nullable final String userAgent;
{
final List<String> userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT); final List<String> userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT);
// tags.addAll(UserAgentTagUtil.getUserAgentTags(userAgentValues != null ? userAgentValues.stream().findFirst().orElse(null) : null)); userAgent = userAgentValues != null && !userAgentValues.isEmpty() ? userAgentValues.get(0) : null;
tags.add(UserAgentTagUtil.getPlatformTag(userAgentValues != null ? userAgentValues.stream().findFirst().orElse(null) : null)); }
tags.add(UserAgentTagUtil.getPlatformTag(userAgent));
meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment(); meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment();
try {
final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentValues != null ? userAgentValues.stream().findFirst().orElse(null) : null);
recordDesktopOperatingSystem(userAgent);
recordAndroidSdkVersion(userAgent);
recordIosVersion(userAgent);
} catch (final UnrecognizedUserAgentException ignored) {
} }
} }
} }
}
@VisibleForTesting
void recordDesktopOperatingSystem(final UserAgent userAgent) {
if (userAgent.getPlatform() == ClientPlatform.DESKTOP) {
if (userAgent.getAdditionalSpecifiers().map(String::toLowerCase).map(ACCEPTABLE_DESKTOP_OS_STRINGS::contains).orElse(false)) {
meterRegistry.counter(DESKTOP_REQUEST_COUNTER_NAME, OS_TAG, userAgent.getAdditionalSpecifiers().get().toLowerCase()).increment();
}
}
}
@VisibleForTesting
void recordAndroidSdkVersion(final UserAgent userAgent) {
if (userAgent.getPlatform() == ClientPlatform.ANDROID) {
userAgent.getAdditionalSpecifiers().ifPresent(additionalSpecifiers -> {
if (additionalSpecifiers.startsWith(ANDROID_SDK_PREFIX)) {
try {
final int sdkVersion = Integer.parseInt(additionalSpecifiers, ANDROID_SDK_PREFIX.length(), additionalSpecifiers.length(), 10);
if (sdkVersion >= MIN_ANDROID_SDK_VERSION && sdkVersion <= MAX_ANDROID_SDK_VERSION) {
meterRegistry.counter(ANDROID_REQUEST_COUNTER_NAME, SDK_TAG, String.valueOf(sdkVersion)).increment();
}
} catch (final NumberFormatException ignored) {
}
}
});
}
}
@VisibleForTesting
void recordIosVersion(final UserAgent userAgent) {
if (userAgent.getPlatform() == ClientPlatform.IOS) {
userAgent.getAdditionalSpecifiers().ifPresent(additionalSpecifiers -> {
Semver iosVersion = null;
if (additionalSpecifiers.startsWith(IOS_VERSION_PREFIX)) {
try {
iosVersion = new Semver(additionalSpecifiers.substring(IOS_VERSION_PREFIX.length()), Semver.SemverType.LOOSE);
} catch (final SemverException ignored) {
}
} else {
final Matcher matcher = LEGACY_IOS_PATTERN.matcher(additionalSpecifiers);
if (matcher.matches()) {
try {
iosVersion = new Semver(matcher.group(1), Semver.SemverType.LOOSE);
} catch (final SemverException ignored) {
}
}
}
if (iosVersion != null && iosVersion.isGreaterThanOrEqualTo(MIN_IOS_VERSION) && iosVersion.isLowerThan(MAX_IOS_VERSION)) {
meterRegistry.counter(IOS_REQUEST_COUNTER_NAME, OS_TAG, iosVersion.toString()).increment();
}
});
}
}
} }

View File

@ -11,14 +11,12 @@ 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.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.vdurmont.semver4j.Semver;
import io.dropwizard.jersey.DropwizardResourceConfig; import io.dropwizard.jersey.DropwizardResourceConfig;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider; import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Counter;
@ -33,7 +31,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.RemoteEndpoint;
@ -48,12 +45,7 @@ import org.glassfish.jersey.server.monitoring.RequestEvent;
import org.glassfish.jersey.uri.UriTemplate; import org.glassfish.jersey.uri.UriTemplate;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
import org.whispersystems.textsecuregcm.util.ua.UserAgent;
import org.whispersystems.websocket.WebSocketResourceProvider; import org.whispersystems.websocket.WebSocketResourceProvider;
import org.whispersystems.websocket.auth.WebsocketAuthValueFactoryProvider; import org.whispersystems.websocket.auth.WebsocketAuthValueFactoryProvider;
import org.whispersystems.websocket.logging.WebsocketRequestLog; import org.whispersystems.websocket.logging.WebsocketRequestLog;
@ -86,7 +78,8 @@ class MetricsRequestEventListenerTest {
when(uriInfo.getMatchedTemplates()).thenReturn(Collections.singletonList(new UriTemplate(path))); when(uriInfo.getMatchedTemplates()).thenReturn(Collections.singletonList(new UriTemplate(path)));
final ContainerRequest request = mock(ContainerRequest.class); final ContainerRequest request = mock(ContainerRequest.class);
when(request.getRequestHeader(HttpHeaders.USER_AGENT)).thenReturn(Collections.singletonList("Signal-Android/4.53.7 (Android 8.1)")); when(request.getRequestHeader(HttpHeaders.USER_AGENT)).thenReturn(
Collections.singletonList("Signal-Android/4.53.7 (Android 8.1)"));
final ContainerResponse response = mock(ContainerResponse.class); final ContainerResponse response = mock(ContainerResponse.class);
when(response.getStatus()).thenReturn(statusCode); when(response.getStatus()).thenReturn(statusCode);
@ -98,7 +91,8 @@ class MetricsRequestEventListenerTest {
when(event.getContainerResponse()).thenReturn(response); when(event.getContainerResponse()).thenReturn(response);
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class); final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class)))
.thenReturn(counter);
listener.onEvent(event); listener.onEvent(event);
@ -111,35 +105,38 @@ class MetricsRequestEventListenerTest {
tags.add(tag); tags.add(tag);
} }
// TODO Restore this when we return to detailed metrics and restore the version tag
// assertEquals(5, tags.size());
assertEquals(4, tags.size()); assertEquals(4, tags.size());
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, path))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, path)));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(statusCode)))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(statusCode))));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase())));
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
// assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")));
} }
@Test @Test
void testActualRouteMessageSuccess() throws InvalidProtocolBufferException { void testActualRouteMessageSuccess() throws InvalidProtocolBufferException {
MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); final MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class);
when(applicationEventListener.onRequest(any())).thenReturn(listener); when(applicationEventListener.onRequest(any())).thenReturn(listener);
ResourceConfig resourceConfig = new DropwizardResourceConfig(); final ResourceConfig resourceConfig = new DropwizardResourceConfig();
resourceConfig.register(applicationEventListener); resourceConfig.register(applicationEventListener);
resourceConfig.register(new TestResource()); resourceConfig.register(new TestResource());
resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder());
resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class));
resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper()));
ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); final ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig);
WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); final WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class);
WebSocketResourceProvider<TestPrincipal> provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler, requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000); final WebSocketResourceProvider<TestPrincipal> provider = new WebSocketResourceProvider<>("127.0.0.1",
applicationHandler,
requestLog,
new TestPrincipal("foo"),
new ProtobufWebSocketMessageFactory(),
Optional.empty(),
30000);
Session session = mock(Session.class ); final Session session = mock(Session.class);
RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
UpgradeRequest request = mock(UpgradeRequest.class); final UpgradeRequest request = mock(UpgradeRequest.class);
when(session.getUpgradeRequest()).thenReturn(request); when(session.getUpgradeRequest()).thenReturn(request);
when(session.getRemote()).thenReturn(remoteEndpoint); when(session.getRemote()).thenReturn(remoteEndpoint);
@ -147,15 +144,17 @@ class MetricsRequestEventListenerTest {
when(request.getHeaders()).thenReturn(Map.of(HttpHeaders.USER_AGENT, List.of("Signal-Android/4.53.7 (Android 8.1)"))); when(request.getHeaders()).thenReturn(Map.of(HttpHeaders.USER_AGENT, List.of("Signal-Android/4.53.7 (Android 8.1)")));
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class); final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class)))
.thenReturn(counter);
provider.onWebSocketConnect(session); provider.onWebSocketConnect(session);
byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", new LinkedList<>(), Optional.empty()).toByteArray(); byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello",
new LinkedList<>(), Optional.empty()).toByteArray();
provider.onWebSocketBinary(message, 0, message.length); provider.onWebSocketBinary(message, 0, message.length);
ArgumentCaptor<ByteBuffer> responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); final ArgumentCaptor<ByteBuffer> responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture());
SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor);
@ -171,49 +170,49 @@ class MetricsRequestEventListenerTest {
tags.add(tag); tags.add(tag);
} }
// TODO Restore this when we return to detailed metrics and restore the version tag
// assertEquals(5, tags.size());
assertEquals(4, tags.size()); assertEquals(4, tags.size());
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, "/v1/test/hello"))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, "/v1/test/hello")));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(200)))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(200))));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase())));
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
// assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")));
} }
@Test @Test
void testActualRouteMessageSuccessNoUserAgent() throws InvalidProtocolBufferException { void testActualRouteMessageSuccessNoUserAgent() throws InvalidProtocolBufferException {
MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); final MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class);
when(applicationEventListener.onRequest(any())).thenReturn(listener); when(applicationEventListener.onRequest(any())).thenReturn(listener);
ResourceConfig resourceConfig = new DropwizardResourceConfig(); final ResourceConfig resourceConfig = new DropwizardResourceConfig();
resourceConfig.register(applicationEventListener); resourceConfig.register(applicationEventListener);
resourceConfig.register(new TestResource()); resourceConfig.register(new TestResource());
resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder());
resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class));
resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper()));
ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); final ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig);
WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); final WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class);
WebSocketResourceProvider<TestPrincipal> provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler, requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000); final WebSocketResourceProvider<TestPrincipal> provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler,
requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000);
Session session = mock(Session.class ); final Session session = mock(Session.class);
RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class);
UpgradeRequest request = mock(UpgradeRequest.class); final UpgradeRequest request = mock(UpgradeRequest.class);
when(session.getUpgradeRequest()).thenReturn(request); when(session.getUpgradeRequest()).thenReturn(request);
when(session.getRemote()).thenReturn(remoteEndpoint); when(session.getRemote()).thenReturn(remoteEndpoint);
final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class); final ArgumentCaptor<Iterable<Tag>> tagCaptor = ArgumentCaptor.forClass(Iterable.class);
when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(
counter);
provider.onWebSocketConnect(session); provider.onWebSocketConnect(session);
byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", new LinkedList<>(), Optional.empty()).toByteArray(); final byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello",
new LinkedList<>(), Optional.empty()).toByteArray();
provider.onWebSocketBinary(message, 0, message.length); provider.onWebSocketBinary(message, 0, message.length);
ArgumentCaptor<ByteBuffer> responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); final ArgumentCaptor<ByteBuffer> responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture());
SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor);
@ -229,103 +228,16 @@ class MetricsRequestEventListenerTest {
tags.add(tag); tags.add(tag);
} }
// TODO Restore this when we return to detailed metrics and restore the version tag
// assertEquals(5, tags.size());
assertEquals(4, tags.size()); assertEquals(4, tags.size());
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, "/v1/test/hello"))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PATH_TAG, "/v1/test/hello")));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(200)))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.STATUS_CODE_TAG, String.valueOf(200))));
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase())));
// assertTrue(tags.containsAll(UserAgentTagUtil.UNRECOGNIZED_TAGS));
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized"))); assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")));
} }
@ParameterizedTest private static SubProtocol.WebSocketResponseMessage getResponse(ArgumentCaptor<ByteBuffer> responseCaptor)
@MethodSource throws InvalidProtocolBufferException {
void testRecordDesktopOperatingSystem(final UserAgent userAgent, final String expectedOperatingSystem) {
when(meterRegistry.counter(eq(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME), (String)any())).thenReturn(counter);
listener.recordDesktopOperatingSystem(userAgent);
if (expectedOperatingSystem != null) {
final ArgumentCaptor<String> tagCaptor = ArgumentCaptor.forClass(String.class);
verify(meterRegistry).counter(eq(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME), tagCaptor.capture());
assertEquals(List.of(MetricsRequestEventListener.OS_TAG, expectedOperatingSystem), tagCaptor.getAllValues());
} else {
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME));
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.DESKTOP_REQUEST_COUNTER_NAME), (String)any());
}
}
private static Stream<Arguments> testRecordDesktopOperatingSystem() {
return Stream.of(
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux"), "linux" ),
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "macOS"), "macos" ),
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Windows"), "windows" ),
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3")), null ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25"), null ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)"), null )
);
}
@ParameterizedTest
@MethodSource
void testRecordAndroidSdkVersion(final UserAgent userAgent, final String expectedSdkVersion) {
when(meterRegistry.counter(eq(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME), (String)any())).thenReturn(counter);
listener.recordAndroidSdkVersion(userAgent);
if (expectedSdkVersion != null) {
final ArgumentCaptor<String> tagCaptor = ArgumentCaptor.forClass(String.class);
verify(meterRegistry).counter(eq(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME), tagCaptor.capture());
assertEquals(List.of(MetricsRequestEventListener.SDK_TAG, expectedSdkVersion), tagCaptor.getAllValues());
} else {
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME));
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.ANDROID_REQUEST_COUNTER_NAME), (String)any());
}
}
private static Stream<Arguments> testRecordAndroidSdkVersion() {
return Stream.of(
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/1"), null ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25"), "25" ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/700000"), null ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/"), null ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), null), null ),
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux"), null ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)"), null )
);
}
@ParameterizedTest
@MethodSource
void testRecordIosVersion(final UserAgent userAgent, final String expectedIosVersion) {
when(meterRegistry.counter(eq(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME), (String)any())).thenReturn(counter);
listener.recordIosVersion(userAgent);
if (expectedIosVersion != null) {
final ArgumentCaptor<String> tagCaptor = ArgumentCaptor.forClass(String.class);
verify(meterRegistry).counter(eq(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME), tagCaptor.capture());
assertEquals(List.of(MetricsRequestEventListener.OS_TAG, expectedIosVersion), tagCaptor.getAllValues());
} else {
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME));
verify(meterRegistry, never()).counter(eq(MetricsRequestEventListener.IOS_REQUEST_COUNTER_NAME), (String)any());
}
}
private static Stream<Arguments> testRecordIosVersion() {
return Stream.of(
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "iOS/14.2"), "14.2" ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)"), "12.2" ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0")), null ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "iOS/bogus"), null ),
Arguments.of( new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS bogus; Scale/3.00)"), null ),
Arguments.of( new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25"), null ),
Arguments.of( new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux"), null )
);
}
private static SubProtocol.WebSocketResponseMessage getResponse(ArgumentCaptor<ByteBuffer> responseCaptor) throws InvalidProtocolBufferException {
return SubProtocol.WebSocketMessage.parseFrom(responseCaptor.getValue().array()).getResponse(); return SubProtocol.WebSocketMessage.parseFrom(responseCaptor.getValue().array()).getResponse();
} }