Move UA tag extraction into its own utility class.
This commit is contained in:
parent
eede4e50ca
commit
9ba5ee8043
|
@ -8,36 +8,21 @@ import io.micrometer.core.instrument.Tag;
|
||||||
import org.glassfish.jersey.server.ExtendedUriInfo;
|
import org.glassfish.jersey.server.ExtendedUriInfo;
|
||||||
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.Pair;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gathers and reports request-level metrics.
|
* Gathers and reports request-level metrics.
|
||||||
*/
|
*/
|
||||||
class MetricsRequestEventListener implements RequestEventListener {
|
class MetricsRequestEventListener implements RequestEventListener {
|
||||||
|
|
||||||
static final int MAX_VERSIONS = 10_000;
|
static final String COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "request");
|
||||||
|
|
||||||
static final String COUNTER_NAME = MetricRegistry.name(MetricsRequestEventListener.class, "request");
|
static final String PATH_TAG = "path";
|
||||||
|
static final String STATUS_CODE_TAG = "status";
|
||||||
|
|
||||||
static final String PATH_TAG = "path";
|
private final MeterRegistry meterRegistry;
|
||||||
static final String STATUS_CODE_TAG = "status";
|
|
||||||
static final String PLATFORM_TAG = "platform";
|
|
||||||
static final String VERSION_TAG = "clientVersion";
|
|
||||||
|
|
||||||
static final List<Tag> OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow"));
|
|
||||||
static final List<Tag> UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"), Tag.of(VERSION_TAG, "unrecognized"));
|
|
||||||
|
|
||||||
private static final Pattern USER_AGENT_PATTERN = Pattern.compile("Signal-([^ ]+) ([^ ]+).*$", Pattern.CASE_INSENSITIVE);
|
|
||||||
|
|
||||||
private final MeterRegistry meterRegistry;
|
|
||||||
private final Set<Pair<String, String>> seenVersions = new HashSet<>();
|
|
||||||
|
|
||||||
public MetricsRequestEventListener() {
|
public MetricsRequestEventListener() {
|
||||||
this(Metrics.globalRegistry);
|
this(Metrics.globalRegistry);
|
||||||
|
@ -59,7 +44,7 @@ class MetricsRequestEventListener implements RequestEventListener {
|
||||||
event.getContainerRequest().getRequestHeader("User-Agent")
|
event.getContainerRequest().getRequestHeader("User-Agent")
|
||||||
.stream()
|
.stream()
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(this::getUserAgentTags)
|
.map(UserAgentTagUtil::getUserAgentTags)
|
||||||
.ifPresent(tags::addAll);
|
.ifPresent(tags::addAll);
|
||||||
|
|
||||||
meterRegistry.counter(COUNTER_NAME, tags).increment();
|
meterRegistry.counter(COUNTER_NAME, tags).increment();
|
||||||
|
@ -77,26 +62,4 @@ class MetricsRequestEventListener implements RequestEventListener {
|
||||||
|
|
||||||
return pathBuilder.toString();
|
return pathBuilder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
List<Tag> getUserAgentTags(final String userAgent) {
|
|
||||||
final Matcher matcher = USER_AGENT_PATTERN.matcher(userAgent);
|
|
||||||
final List<Tag> tags;
|
|
||||||
|
|
||||||
if (matcher.matches()) {
|
|
||||||
final Pair<String, String> platformAndVersion = new Pair<>(matcher.group(1).toLowerCase(), matcher.group(2));
|
|
||||||
|
|
||||||
final boolean allowVersion;
|
|
||||||
|
|
||||||
synchronized (seenVersions) {
|
|
||||||
allowVersion = seenVersions.contains(platformAndVersion) || (seenVersions.size() < MAX_VERSIONS && seenVersions.add(platformAndVersion));
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = allowVersion ? List.of(Tag.of(PLATFORM_TAG, platformAndVersion.first()), Tag.of(VERSION_TAG, platformAndVersion.second())) : OVERFLOW_TAGS;
|
|
||||||
} else {
|
|
||||||
tags = UNRECOGNIZED_TAGS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for extracting platform/version metrics tags from User-Agent strings.
|
||||||
|
*/
|
||||||
|
public class UserAgentTagUtil {
|
||||||
|
|
||||||
|
static final int MAX_VERSIONS = 10_000;
|
||||||
|
|
||||||
|
public static final String PLATFORM_TAG = "platform";
|
||||||
|
public static final String VERSION_TAG = "clientVersion";
|
||||||
|
|
||||||
|
static final List<Tag> OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow"));
|
||||||
|
static final List<Tag> UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"), Tag.of(VERSION_TAG, "unrecognized"));
|
||||||
|
|
||||||
|
private static final Pattern USER_AGENT_PATTERN = Pattern.compile("Signal-([^ ]+) ([^ ]+).*$", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static final Set<Pair<String, String>> SEEN_VERSIONS = new HashSet<>();
|
||||||
|
|
||||||
|
private UserAgentTagUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Tag> getUserAgentTags(final String userAgent) {
|
||||||
|
final Matcher matcher = USER_AGENT_PATTERN.matcher(userAgent);
|
||||||
|
final List<Tag> tags;
|
||||||
|
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final Pair<String, String> platformAndVersion = new Pair<>(matcher.group(1).toLowerCase(), matcher.group(2));
|
||||||
|
|
||||||
|
final boolean allowVersion;
|
||||||
|
|
||||||
|
synchronized (SEEN_VERSIONS) {
|
||||||
|
allowVersion = SEEN_VERSIONS.contains(platformAndVersion) || (SEEN_VERSIONS.size() < MAX_VERSIONS && SEEN_VERSIONS.add(platformAndVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = allowVersion ? List.of(Tag.of(PLATFORM_TAG, platformAndVersion.first()), Tag.of(VERSION_TAG, platformAndVersion.second())) : OVERFLOW_TAGS;
|
||||||
|
} else {
|
||||||
|
tags = UNRECOGNIZED_TAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,8 +81,8 @@ public class MetricsRequestEventListenerTest {
|
||||||
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.PLATFORM_TAG, "android")));
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
|
||||||
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.VERSION_TAG, "4.53.7")));
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -96,32 +96,4 @@ public class MetricsRequestEventListenerTest {
|
||||||
|
|
||||||
assertEquals("/first/second/{param}/{moreDifferentParam}", MetricsRequestEventListener.getPathTemplate(uriInfo));
|
assertEquals("/first/second/{param}/{moreDifferentParam}", MetricsRequestEventListener.getPathTemplate(uriInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUserAgentTags() {
|
|
||||||
assertEquals(MetricsRequestEventListener.UNRECOGNIZED_TAGS,
|
|
||||||
listener.getUserAgentTags("This is obviously not a reasonable User-Agent string."));
|
|
||||||
|
|
||||||
final List<Tag> tags = listener.getUserAgentTags("Signal-Android 4.53.7 (Android 8.1)");
|
|
||||||
|
|
||||||
assertEquals(2, tags.size());
|
|
||||||
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PLATFORM_TAG, "android")));
|
|
||||||
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.VERSION_TAG, "4.53.7")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetUserAgentTagsFlooded() {
|
|
||||||
for (int i = 0; i < MetricsRequestEventListener.MAX_VERSIONS; i++) {
|
|
||||||
listener.getUserAgentTags(String.format("Signal-Android 1.0.%d (Android 8.1)", i));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(MetricsRequestEventListener.OVERFLOW_TAGS,
|
|
||||||
listener.getUserAgentTags("Signal-Android 2.0.0 (Android 8.1)"));
|
|
||||||
|
|
||||||
final List<Tag> tags = listener.getUserAgentTags("Signal-Android 1.0.0 (Android 8.1)");
|
|
||||||
|
|
||||||
assertEquals(2, tags.size());
|
|
||||||
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.PLATFORM_TAG, "android")));
|
|
||||||
assertTrue(tags.contains(Tag.of(MetricsRequestEventListener.VERSION_TAG, "1.0.0")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.whispersystems.textsecuregcm.metrics;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class UserAgentTagUtilTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserAgentTags() {
|
||||||
|
assertEquals(UserAgentTagUtil.UNRECOGNIZED_TAGS,
|
||||||
|
UserAgentTagUtil.getUserAgentTags("This is obviously not a reasonable User-Agent string."));
|
||||||
|
|
||||||
|
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 4.53.7 (Android 8.1)");
|
||||||
|
|
||||||
|
assertEquals(2, tags.size());
|
||||||
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
|
||||||
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserAgentTagsFlooded() {
|
||||||
|
for (int i = 0; i < UserAgentTagUtil.MAX_VERSIONS; i++) {
|
||||||
|
UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android 1.0.%d (Android 8.1)", i));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(UserAgentTagUtil.OVERFLOW_TAGS,
|
||||||
|
UserAgentTagUtil.getUserAgentTags("Signal-Android 2.0.0 (Android 8.1)"));
|
||||||
|
|
||||||
|
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 1.0.0 (Android 8.1)");
|
||||||
|
|
||||||
|
assertEquals(2, tags.size());
|
||||||
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
|
||||||
|
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.0.0")));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue