diff --git a/java/google/registry/model/BUILD b/java/google/registry/model/BUILD
index 108e825e9..34d2c83f8 100644
--- a/java/google/registry/model/BUILD
+++ b/java/google/registry/model/BUILD
@@ -14,6 +14,7 @@ java_library(
visibility = ["//visibility:public"],
deps = [
"//java/google/registry/config",
+ "//java/google/registry/monitoring/metrics",
"//java/google/registry/util",
"//java/google/registry/xml",
"//third_party/java/objectify:objectify-v4_1",
diff --git a/java/google/registry/model/registry/label/DomainLabelMetrics.java b/java/google/registry/model/registry/label/DomainLabelMetrics.java
new file mode 100644
index 000000000..c4b6262cb
--- /dev/null
+++ b/java/google/registry/model/registry/label/DomainLabelMetrics.java
@@ -0,0 +1,127 @@
+// Copyright 2017 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.model.registry.label;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableSet;
+import google.registry.monitoring.metrics.EventMetric;
+import google.registry.monitoring.metrics.IncrementableMetric;
+import google.registry.monitoring.metrics.LabelDescriptor;
+import google.registry.monitoring.metrics.MetricRegistryImpl;
+
+/** Instrumentation for reserved lists. */
+class DomainLabelMetrics {
+
+ @AutoValue
+ abstract static class MetricsReservedListMatch {
+ static MetricsReservedListMatch create(
+ String reservedListName, ReservationType reservationType) {
+ return new AutoValue_DomainLabelMetrics_MetricsReservedListMatch(
+ reservedListName, reservationType);
+ }
+
+ abstract String reservedListName();
+ abstract ReservationType reservationType();
+ }
+
+ /**
+ * Labels attached to {@link #reservedListChecks} and {@link #reservedListProcessingTimes}
+ * metrics.
+ *
+ *
A domain name can be matched by multiple reserved lists. To keep the metrics useful by
+ * emitting only one metric result for each check, while avoiding potential combinatorial
+ * explosion if all the matching lists and reservation types were to be displayed, we store as
+ * labels only the number of matching lists, along with the most severe match found. Note that
+ * "most severe" may not be meaningful, and this should only be treated as "one of the matches
+ * that we found". But we might as well make it as useful as possible.
+ */
+ private static final ImmutableSet RESERVED_LIST_LABEL_DESCRIPTORS =
+ ImmutableSet.of(
+ LabelDescriptor.create("tld", "TLD"),
+ LabelDescriptor.create("reserved_list_count", "Number of matching reserved lists."),
+ LabelDescriptor.create("most_severe_reserved_list", "Reserved list name, if any."),
+ LabelDescriptor.create("most_severe_reservation_type", "Type of reservation found."));
+
+ /** Labels attached to {@link #reservedListHits} metric. */
+ private static final ImmutableSet RESERVED_LIST_HIT_LABEL_DESCRIPTORS =
+ ImmutableSet.of(
+ LabelDescriptor.create("tld", "TLD"),
+ LabelDescriptor.create("reserved_list", "Reserved list name."),
+ LabelDescriptor.create("reservation_type", "Type of reservation found."));
+
+ /** Metric counting the number of times a label was checked against all reserved lists. */
+ @VisibleForTesting
+ static final IncrementableMetric reservedListChecks =
+ MetricRegistryImpl.getDefault()
+ .newIncrementableMetric(
+ "/domain_label/reserved/checks",
+ "Count of reserved list checks",
+ "count",
+ RESERVED_LIST_LABEL_DESCRIPTORS);
+
+ /** Metric recording the amount of time required to check a label against all reserved lists. */
+ @VisibleForTesting
+ static final EventMetric reservedListProcessingTimes =
+ MetricRegistryImpl.getDefault()
+ .newEventMetric(
+ "/domain_label/reserved/processing_time",
+ "Reserved list check processing time",
+ "milliseconds",
+ RESERVED_LIST_LABEL_DESCRIPTORS,
+ EventMetric.DEFAULT_FITTER);
+
+ /**
+ * Metric recording the number of times a label was found in a reserved list.
+ *
+ * Each time a label is checked, and a list associated with the TLD contains that label, that
+ * count is incremented. A label can be found in more than one list, which would result in a
+ * single increment of {@link #reservedListChecks}, but multiple increments of {@link
+ * #reservedListHits}. It can of course also match zero lists, which would still result in a
+ * single increment of {@link #reservedListChecks}, but no increments of {@link
+ * #reservedListHits}.
+ */
+ @VisibleForTesting
+ static final IncrementableMetric reservedListHits =
+ MetricRegistryImpl.getDefault()
+ .newIncrementableMetric(
+ "/domain_label/reserved/hits",
+ "Count of reserved list hits",
+ "count",
+ RESERVED_LIST_HIT_LABEL_DESCRIPTORS);
+
+ /** Update all three reserved list metrics. */
+ static void recordReservedListCheckOutcome(
+ String tld, ImmutableSet matches, double elapsedMillis) {
+ MetricsReservedListMatch mostSevereMatch = null;
+ for (MetricsReservedListMatch match : matches) {
+ reservedListHits.increment(tld, match.reservedListName(), match.reservationType().toString());
+ if ((mostSevereMatch == null)
+ || (match.reservationType().compareTo(mostSevereMatch.reservationType()) > 0)) {
+ mostSevereMatch = match;
+ }
+ }
+ String matchCount = String.valueOf(matches.size());
+ String mostSevereReservedList =
+ matches.isEmpty() ? "(none)" : mostSevereMatch.reservedListName();
+ String mostSevereReservationType =
+ (matches.isEmpty() ? ReservationType.UNRESERVED : mostSevereMatch.reservationType())
+ .toString();
+ reservedListChecks.increment(
+ tld, matchCount, mostSevereReservedList, mostSevereReservationType);
+ reservedListProcessingTimes.record(
+ elapsedMillis, tld, matchCount, mostSevereReservedList, mostSevereReservationType);
+ }
+}
diff --git a/java/google/registry/model/registry/label/ReservedList.java b/java/google/registry/model/registry/label/ReservedList.java
index 15bbc70f7..6ae68df9d 100644
--- a/java/google/registry/model/registry/label/ReservedList.java
+++ b/java/google/registry/model/registry/label/ReservedList.java
@@ -27,6 +27,7 @@ import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_
import static google.registry.model.registry.label.ReservationType.UNRESERVED;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Function;
import com.google.common.base.Optional;
@@ -46,12 +47,14 @@ import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Mapify;
import com.googlecode.objectify.mapper.Mapper;
import google.registry.model.registry.Registry;
+import google.registry.model.registry.label.DomainLabelMetrics.MetricsReservedListMatch;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
+import org.joda.time.DateTime;
/**
* A reserved list entity, persisted to Datastore, that is used to check domain label reservations.
@@ -211,19 +214,27 @@ public final class ReservedList
* no such entry exists.
*/
private static ImmutableSet getReservedListEntries(String label, String tld) {
+ DateTime startTime = DateTime.now(UTC);
Registry registry = Registry.get(checkNotNull(tld, "tld"));
ImmutableSet> reservedLists = registry.getReservedLists();
ImmutableSet lists = loadReservedLists(reservedLists);
- ImmutableSet.Builder entries = new ImmutableSet.Builder<>();
+ ImmutableSet.Builder entriesBuilder = new ImmutableSet.Builder<>();
+ ImmutableSet.Builder metricMatchesBuilder =
+ new ImmutableSet.Builder<>();
// Loop through all reservation lists and add each of them.
for (ReservedList rl : lists) {
if (rl.getReservedListEntries().containsKey(label)) {
- entries.add(rl.getReservedListEntries().get(label));
+ ReservedListEntry entry = rl.getReservedListEntries().get(label);
+ entriesBuilder.add(entry);
+ metricMatchesBuilder.add(
+ MetricsReservedListMatch.create(rl.getName(), entry.reservationType));
}
}
-
- return entries.build();
+ ImmutableSet entries = entriesBuilder.build();
+ DomainLabelMetrics.recordReservedListCheckOutcome(
+ tld, metricMatchesBuilder.build(), DateTime.now(UTC).getMillis() - startTime.getMillis());
+ return entries;
}
private static ImmutableSet loadReservedLists(
diff --git a/javatests/google/registry/model/BUILD b/javatests/google/registry/model/BUILD
index 15538408c..c576b70bf 100644
--- a/javatests/google/registry/model/BUILD
+++ b/javatests/google/registry/model/BUILD
@@ -22,6 +22,7 @@ java_library(
"//java/google/registry/config",
"//java/google/registry/flows",
"//java/google/registry/model",
+ "//java/google/registry/monitoring/metrics/contrib",
"//java/google/registry/util",
"//java/google/registry/xml",
"//javatests/google/registry/testing",
diff --git a/javatests/google/registry/model/registry/label/ReservedListTest.java b/javatests/google/registry/model/registry/label/ReservedListTest.java
index ff7f8b189..01dc1ee3c 100644
--- a/javatests/google/registry/model/registry/label/ReservedListTest.java
+++ b/javatests/google/registry/model/registry/label/ReservedListTest.java
@@ -16,13 +16,19 @@ package google.registry.model.registry.label;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static google.registry.model.registry.label.DomainLabelMetrics.reservedListChecks;
+import static google.registry.model.registry.label.DomainLabelMetrics.reservedListHits;
+import static google.registry.model.registry.label.DomainLabelMetrics.reservedListProcessingTimes;
import static google.registry.model.registry.label.ReservationType.ALLOWED_IN_SUNRISE;
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
+import static google.registry.model.registry.label.ReservationType.MISTAKEN_PREMIUM;
import static google.registry.model.registry.label.ReservationType.NAME_COLLISION;
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
import static google.registry.model.registry.label.ReservationType.UNRESERVED;
import static google.registry.model.registry.label.ReservedList.getReservationTypes;
import static google.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation;
+import static google.registry.monitoring.metrics.contrib.EventMetricSubject.assertThat;
+import static google.registry.monitoring.metrics.contrib.IncrementableMetricSubject.assertThat;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistReservedList;
import static google.registry.testing.DatastoreHelper.persistResource;
@@ -67,6 +73,21 @@ public class ReservedListTest {
public void before() throws Exception {
inject.setStaticField(Ofy.class, "clock", clock);
createTld("tld");
+ reservedListChecks.reset();
+ reservedListProcessingTimes.reset();
+ reservedListHits.reset();
+ }
+
+ private static void verifyUnreservedCheckCount(int unreservedCount) {
+ assertThat(reservedListChecks)
+ .hasValueForLabels(unreservedCount, "tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits).hasNoOtherValues();
}
@Test
@@ -75,11 +96,13 @@ public class ReservedListTest {
assertThat(getReservationTypes("doodle", "tld")).containsExactly(UNRESERVED);
assertThat(getReservationTypes("access", "tld")).containsExactly(UNRESERVED);
assertThat(getReservationTypes("rich", "tld")).containsExactly(UNRESERVED);
+ verifyUnreservedCheckCount(3);
}
@Test
public void testZeroReservedLists_doesNotCauseError() throws Exception {
assertThat(getReservationTypes("doodle", "tld")).containsExactly(UNRESERVED);
+ verifyUnreservedCheckCount(1);
}
@Test
@@ -87,6 +110,7 @@ public class ReservedListTest {
for (String sld : ImmutableList.of("aa", "az", "zz", "91", "1n", "j5")) {
assertThat(getReservationTypes(sld, "tld")).containsExactly(UNRESERVED);
}
+ verifyUnreservedCheckCount(6);
}
@Test
@@ -95,6 +119,7 @@ public class ReservedListTest {
for (char c = 'a'; c <= 'z'; c++) {
assertThat(getReservationTypes("" + c, "tld")).containsExactly(UNRESERVED);
}
+ verifyUnreservedCheckCount(26);
}
@Test
@@ -120,6 +145,22 @@ public class ReservedListTest {
.isFalse();
assertThat(matchesAnchorTenantReservation(InternetDomainName.from("random.tld"), "abcdefg"))
.isFalse();
+ assertThat(reservedListChecks)
+ .hasValueForLabels(1, "tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasValueForLabels(6, "tld", "1", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits)
+ .hasValueForLabels(6, "tld", "reserved1", RESERVED_FOR_ANCHOR_TENANT.toString())
+ .and()
+ .hasNoOtherValues();
}
@Test
@@ -138,6 +179,40 @@ public class ReservedListTest {
assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol3.tld"), "")).isFalse();
assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol4.tld"), "")).isFalse();
assertThat(matchesAnchorTenantReservation(InternetDomainName.from("lol5.tld"), "")).isFalse();
+ assertThat(reservedListChecks)
+ .hasValueForLabels(1, "tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "1", "reserved2", NAME_COLLISION.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "1", "reserved2", MISTAKEN_PREMIUM.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "1", "reserved2", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved2", NAME_COLLISION.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved2", MISTAKEN_PREMIUM.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved2", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits)
+ .hasValueForLabels(1, "tld", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "reserved2", NAME_COLLISION.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "reserved2", MISTAKEN_PREMIUM.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "reserved2", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasNoOtherValues();
}
@Test
@@ -173,6 +248,28 @@ public class ReservedListTest {
assertThat(getReservationTypes("roflcopter", "tld")).containsExactly(FULLY_BLOCKED);
assertThat(getReservationTypes("snowcrash", "tld")).containsExactly(FULLY_BLOCKED);
assertThat(getReservationTypes("doge", "tld")).containsExactly(UNRESERVED);
+ assertThat(reservedListChecks)
+ .hasValueForLabels(1, "tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasValueForLabels(2, "tld", "1", "reserved1", FULLY_BLOCKED.toString())
+ .and()
+ .hasValueForLabels(2, "tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved1", FULLY_BLOCKED.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits)
+ .hasValueForLabels(2, "tld", "reserved1", FULLY_BLOCKED.toString())
+ .and()
+ .hasValueForLabels(2, "tld", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
}
@Test
@@ -207,6 +304,22 @@ public class ReservedListTest {
+ " after unsetting the registry's second reserved list")
.that(getReservationTypes("roflcopter", "tld"))
.containsExactly(UNRESERVED);
+ assertThat(reservedListChecks)
+ .hasValueForLabels(1, "tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "1", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "0", "(none)", UNRESERVED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits)
+ .hasValueForLabels(1, "tld", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
}
@Test
@@ -218,6 +331,26 @@ public class ReservedListTest {
persistResource(Registry.get("tld").asBuilder().setReservedLists(rl1, rl2).build());
assertThat(getReservationTypes("lol", "tld")).containsExactly(FULLY_BLOCKED, NAME_COLLISION);
assertThat(getReservationTypes("roflcopter", "tld")).containsExactly(ALLOWED_IN_SUNRISE);
+ assertThat(reservedListChecks)
+ .hasValueForLabels(1, "tld", "1", "reserved1", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "2", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListProcessingTimes)
+ .hasAnyValueForLabels("tld", "1", "reserved1", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasAnyValueForLabels("tld", "2", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
+ assertThat(reservedListHits)
+ .hasValueForLabels(1, "tld", "reserved1", NAME_COLLISION.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "reserved1", ALLOWED_IN_SUNRISE.toString())
+ .and()
+ .hasValueForLabels(1, "tld", "reserved2", FULLY_BLOCKED.toString())
+ .and()
+ .hasNoOtherValues();
}
@Test