diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index af2f523f0..f9bb61b4d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -134,7 +134,6 @@ import org.whispersystems.textsecuregcm.metrics.FreeMemoryGauge; import org.whispersystems.textsecuregcm.metrics.GarbageCollectionGauges; import org.whispersystems.textsecuregcm.metrics.MaxFileDescriptorGauge; import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener; -import org.whispersystems.textsecuregcm.metrics.MetricsRequestEventListener; import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager; import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge; @@ -277,9 +276,6 @@ public class WhisperServerService extends Application ACCEPTABLE_DESKTOP_OS_STRINGS = Set.of("linux", "macos", "windows"); + @VisibleForTesting + static final String STATUS_CODE_TAG = "status"; - 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; + @VisibleForTesting + static final String TRAFFIC_SOURCE_TAG = "trafficSource"; - 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 MeterRegistry meterRegistry; - private final TrafficSource trafficSource; - private final MeterRegistry meterRegistry; + public MetricsRequestEventListener(final TrafficSource trafficSource) { + this(trafficSource, Metrics.globalRegistry); + } - public MetricsRequestEventListener(final TrafficSource trafficSource) { - this(trafficSource, Metrics.globalRegistry); - } + @VisibleForTesting + MetricsRequestEventListener(final TrafficSource trafficSource, final MeterRegistry meterRegistry) { + this.trafficSource = trafficSource; + this.meterRegistry = meterRegistry; + } - @VisibleForTesting - MetricsRequestEventListener(final TrafficSource trafficSource, final MeterRegistry meterRegistry) { - this.trafficSource = trafficSource; - this.meterRegistry = meterRegistry; - } + @Override + public void onEvent(final RequestEvent event) { + if (event.getType() == RequestEvent.Type.FINISHED) { + if (!event.getUriInfo().getMatchedTemplates().isEmpty()) { + final List tags = new ArrayList<>(4); + 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(TRAFFIC_SOURCE_TAG, trafficSource.name().toLowerCase())); - @Override - public void onEvent(final RequestEvent event) { - if (event.getType() == RequestEvent.Type.FINISHED) { - if (!event.getUriInfo().getMatchedTemplates().isEmpty()) { - final List tags = new ArrayList<>(5); - 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(TRAFFIC_SOURCE_TAG, trafficSource.name().toLowerCase())); - - final List userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT); - // tags.addAll(UserAgentTagUtil.getUserAgentTags(userAgentValues != null ? userAgentValues.stream().findFirst().orElse(null) : null)); - tags.add(UserAgentTagUtil.getPlatformTag(userAgentValues != null ? userAgentValues.stream().findFirst().orElse(null) : null)); - - 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) { - } - } + @Nullable final String userAgent; + { + final List userAgentValues = event.getContainerRequest().getRequestHeader(HttpHeaders.USER_AGENT); + userAgent = userAgentValues != null && !userAgentValues.isEmpty() ? userAgentValues.get(0) : null; } + + tags.add(UserAgentTagUtil.getPlatformTag(userAgent)); + + meterRegistry.counter(REQUEST_COUNTER_NAME, tags).increment(); + } } - - @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(); - } - }); - } - } - + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/MetricsRequestEventListenerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/MetricsRequestEventListenerTest.java index aa916249d..f3e54a0be 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/MetricsRequestEventListenerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/MetricsRequestEventListenerTest.java @@ -11,14 +11,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.net.HttpHeaders; import com.google.protobuf.InvalidProtocolBufferException; -import com.vdurmont.semver4j.Semver; import io.dropwizard.jersey.DropwizardResourceConfig; import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider; import io.micrometer.core.instrument.Counter; @@ -33,7 +31,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import javax.ws.rs.GET; import javax.ws.rs.Path; 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.junit.jupiter.api.BeforeEach; 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.whispersystems.textsecuregcm.util.ua.ClientPlatform; -import org.whispersystems.textsecuregcm.util.ua.UserAgent; import org.whispersystems.websocket.WebSocketResourceProvider; import org.whispersystems.websocket.auth.WebsocketAuthValueFactoryProvider; import org.whispersystems.websocket.logging.WebsocketRequestLog; @@ -63,293 +55,213 @@ import org.whispersystems.websocket.session.WebSocketSessionContextValueFactoryP class MetricsRequestEventListenerTest { - private MeterRegistry meterRegistry; - private Counter counter; - private MetricsRequestEventListener listener; + private MeterRegistry meterRegistry; + private Counter counter; + private MetricsRequestEventListener listener; - private static final TrafficSource TRAFFIC_SOURCE = TrafficSource.HTTP; + private static final TrafficSource TRAFFIC_SOURCE = TrafficSource.HTTP; - @BeforeEach - void setup() { - meterRegistry = mock(MeterRegistry.class); - counter = mock(Counter.class); - listener = new MetricsRequestEventListener(TRAFFIC_SOURCE, meterRegistry); + @BeforeEach + void setup() { + meterRegistry = mock(MeterRegistry.class); + counter = mock(Counter.class); + listener = new MetricsRequestEventListener(TRAFFIC_SOURCE, meterRegistry); + } + + @Test + @SuppressWarnings("unchecked") + void testOnEvent() { + final String path = "/test"; + final int statusCode = 200; + + final ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); + when(uriInfo.getMatchedTemplates()).thenReturn(Collections.singletonList(new UriTemplate(path))); + + final ContainerRequest request = mock(ContainerRequest.class); + when(request.getRequestHeader(HttpHeaders.USER_AGENT)).thenReturn( + Collections.singletonList("Signal-Android/4.53.7 (Android 8.1)")); + + final ContainerResponse response = mock(ContainerResponse.class); + when(response.getStatus()).thenReturn(statusCode); + + final RequestEvent event = mock(RequestEvent.class); + when(event.getType()).thenReturn(RequestEvent.Type.FINISHED); + when(event.getUriInfo()).thenReturn(uriInfo); + when(event.getContainerRequest()).thenReturn(request); + when(event.getContainerResponse()).thenReturn(response); + + final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); + when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))) + .thenReturn(counter); + + listener.onEvent(event); + + verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); + + final Iterable tagIterable = tagCaptor.getValue(); + final Set tags = new HashSet<>(); + + for (final Tag tag : tagIterable) { + tags.add(tag); } - @Test - @SuppressWarnings("unchecked") - void testOnEvent() { - final String path = "/test"; - final int statusCode = 200; + assertEquals(4, tags.size()); + 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); + assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); + } - final ExtendedUriInfo uriInfo = mock(ExtendedUriInfo.class); - when(uriInfo.getMatchedTemplates()).thenReturn(Collections.singletonList(new UriTemplate(path))); + @Test + void testActualRouteMessageSuccess() throws InvalidProtocolBufferException { + final MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); + when(applicationEventListener.onRequest(any())).thenReturn(listener); - final ContainerRequest request = mock(ContainerRequest.class); - when(request.getRequestHeader(HttpHeaders.USER_AGENT)).thenReturn(Collections.singletonList("Signal-Android/4.53.7 (Android 8.1)")); + final ResourceConfig resourceConfig = new DropwizardResourceConfig(); + resourceConfig.register(applicationEventListener); + resourceConfig.register(new TestResource()); + resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); + resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); + resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); - final ContainerResponse response = mock(ContainerResponse.class); - when(response.getStatus()).thenReturn(statusCode); + final ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); + final WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); + final WebSocketResourceProvider provider = new WebSocketResourceProvider<>("127.0.0.1", + applicationHandler, + requestLog, + new TestPrincipal("foo"), + new ProtobufWebSocketMessageFactory(), + Optional.empty(), + 30000); - final RequestEvent event = mock(RequestEvent.class); - when(event.getType()).thenReturn(RequestEvent.Type.FINISHED); - when(event.getUriInfo()).thenReturn(uriInfo); - when(event.getContainerRequest()).thenReturn(request); - when(event.getContainerResponse()).thenReturn(response); + final Session session = mock(Session.class); + final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); + final UpgradeRequest request = mock(UpgradeRequest.class); - final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); - when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); + when(session.getUpgradeRequest()).thenReturn(request); + when(session.getRemote()).thenReturn(remoteEndpoint); + when(request.getHeader(HttpHeaders.USER_AGENT)).thenReturn("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)"))); - listener.onEvent(event); + final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); + when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))) + .thenReturn(counter); - verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); + provider.onWebSocketConnect(session); - final Iterable tagIterable = tagCaptor.getValue(); - final Set tags = new HashSet<>(); + byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", + new LinkedList<>(), Optional.empty()).toByteArray(); - for (final Tag tag : tagIterable) { - tags.add(tag); - } + provider.onWebSocketBinary(message, 0, message.length); - // TODO Restore this when we return to detailed metrics and restore the version tag - // assertEquals(5, tags.size()); - assertEquals(4, tags.size()); - 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); - assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); - // assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7"))); + final ArgumentCaptor responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); + verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); + + SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); + + assertThat(response.getStatus()).isEqualTo(200); + + verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); + + final Iterable tagIterable = tagCaptor.getValue(); + final Set tags = new HashSet<>(); + + for (final Tag tag : tagIterable) { + tags.add(tag); } - @Test - void testActualRouteMessageSuccess() throws InvalidProtocolBufferException { - MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); - when(applicationEventListener.onRequest(any())).thenReturn(listener); + assertEquals(4, tags.size()); + 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); + assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); + } - ResourceConfig resourceConfig = new DropwizardResourceConfig(); - resourceConfig.register(applicationEventListener); - resourceConfig.register(new TestResource()); - resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); - resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); - resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); + @Test + void testActualRouteMessageSuccessNoUserAgent() throws InvalidProtocolBufferException { + final MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); + when(applicationEventListener.onRequest(any())).thenReturn(listener); - ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); - WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); - WebSocketResourceProvider provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler, requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000); + final ResourceConfig resourceConfig = new DropwizardResourceConfig(); + resourceConfig.register(applicationEventListener); + resourceConfig.register(new TestResource()); + resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); + resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); + resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); - Session session = mock(Session.class ); - RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); - UpgradeRequest request = mock(UpgradeRequest.class); + final ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); + final WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); + final WebSocketResourceProvider provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler, + requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000); - when(session.getUpgradeRequest()).thenReturn(request); - when(session.getRemote()).thenReturn(remoteEndpoint); - when(request.getHeader(HttpHeaders.USER_AGENT)).thenReturn("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 Session session = mock(Session.class); + final RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); + final UpgradeRequest request = mock(UpgradeRequest.class); - final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); - when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); + when(session.getUpgradeRequest()).thenReturn(request); + when(session.getRemote()).thenReturn(remoteEndpoint); - provider.onWebSocketConnect(session); + final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); + when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn( + counter); - byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", new LinkedList<>(), Optional.empty()).toByteArray(); + provider.onWebSocketConnect(session); - provider.onWebSocketBinary(message, 0, message.length); + final byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", + new LinkedList<>(), Optional.empty()).toByteArray(); - ArgumentCaptor responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); - verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); + provider.onWebSocketBinary(message, 0, message.length); - SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); + final ArgumentCaptor responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); + verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); - assertThat(response.getStatus()).isEqualTo(200); + SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); - verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); + assertThat(response.getStatus()).isEqualTo(200); - final Iterable tagIterable = tagCaptor.getValue(); - final Set tags = new HashSet<>(); + verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); - for (final Tag tag : tagIterable) { - tags.add(tag); - } + final Iterable tagIterable = tagCaptor.getValue(); + final Set tags = new HashSet<>(); - // TODO Restore this when we return to detailed metrics and restore the version tag - // assertEquals(5, tags.size()); - assertEquals(4, tags.size()); - 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); - assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); - // assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7"))); + for (final Tag tag : tagIterable) { + tags.add(tag); } - @Test - void testActualRouteMessageSuccessNoUserAgent() throws InvalidProtocolBufferException { - MetricsApplicationEventListener applicationEventListener = mock(MetricsApplicationEventListener.class); - when(applicationEventListener.onRequest(any())).thenReturn(listener); + assertEquals(4, tags.size()); + 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); + assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized"))); + } - ResourceConfig resourceConfig = new DropwizardResourceConfig(); - resourceConfig.register(applicationEventListener); - resourceConfig.register(new TestResource()); - resourceConfig.register(new WebSocketSessionContextValueFactoryProvider.Binder()); - resourceConfig.register(new WebsocketAuthValueFactoryProvider.Binder<>(TestPrincipal.class)); - resourceConfig.register(new JacksonMessageBodyProvider(new ObjectMapper())); + private static SubProtocol.WebSocketResponseMessage getResponse(ArgumentCaptor responseCaptor) + throws InvalidProtocolBufferException { - ApplicationHandler applicationHandler = new ApplicationHandler(resourceConfig); - WebsocketRequestLog requestLog = mock(WebsocketRequestLog.class); - WebSocketResourceProvider provider = new WebSocketResourceProvider<>("127.0.0.1", applicationHandler, requestLog, new TestPrincipal("foo"), new ProtobufWebSocketMessageFactory(), Optional.empty(), 30000); + return SubProtocol.WebSocketMessage.parseFrom(responseCaptor.getValue().array()).getResponse(); + } - Session session = mock(Session.class ); - RemoteEndpoint remoteEndpoint = mock(RemoteEndpoint.class); - UpgradeRequest request = mock(UpgradeRequest.class); + public static class TestPrincipal implements Principal { - when(session.getUpgradeRequest()).thenReturn(request); - when(session.getRemote()).thenReturn(remoteEndpoint); + private final String name; - final ArgumentCaptor> tagCaptor = ArgumentCaptor.forClass(Iterable.class); - when(meterRegistry.counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), any(Iterable.class))).thenReturn(counter); - - provider.onWebSocketConnect(session); - - byte[] message = new ProtobufWebSocketMessageFactory().createRequest(Optional.of(111L), "GET", "/v1/test/hello", new LinkedList<>(), Optional.empty()).toByteArray(); - - provider.onWebSocketBinary(message, 0, message.length); - - ArgumentCaptor responseBytesCaptor = ArgumentCaptor.forClass(ByteBuffer.class); - verify(remoteEndpoint).sendBytesByFuture(responseBytesCaptor.capture()); - - SubProtocol.WebSocketResponseMessage response = getResponse(responseBytesCaptor); - - assertThat(response.getStatus()).isEqualTo(200); - - verify(meterRegistry).counter(eq(MetricsRequestEventListener.REQUEST_COUNTER_NAME), tagCaptor.capture()); - - final Iterable tagIterable = tagCaptor.getValue(); - final Set tags = new HashSet<>(); - - for (final Tag tag : tagIterable) { - 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()); - 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.TRAFFIC_SOURCE_TAG, TRAFFIC_SOURCE.name().toLowerCase()))); - // assertTrue(tags.containsAll(UserAgentTagUtil.UNRECOGNIZED_TAGS)); - assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized"))); + private TestPrincipal(String name) { + this.name = name; } - @ParameterizedTest - @MethodSource - 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 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()); - } + @Override + public String getName() { + return name; } + } - private static Stream 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 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 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 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 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 responseCaptor) throws InvalidProtocolBufferException { - return SubProtocol.WebSocketMessage.parseFrom(responseCaptor.getValue().array()).getResponse(); - } - - public static class TestPrincipal implements Principal { - - private final String name; - - private TestPrincipal(String name) { - this.name = name; - } - - @Override - public String getName() { - return name; - } - } - - @Path("/v1/test") - public static class TestResource { - - @GET - @Path("/hello") - public String testGetHello() { - return "Hello!"; - } + @Path("/v1/test") + public static class TestResource { + + @GET + @Path("/hello") + public String testGetHello() { + return "Hello!"; } + } }