Define Stackdriver metrics for premium list checks

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=150349564
This commit is contained in:
mountford 2017-03-16 11:59:44 -07:00 committed by Ben McIlwain
parent 443e260f91
commit c2cbb9ea5a
5 changed files with 251 additions and 34 deletions

View file

@ -25,6 +25,24 @@ import google.registry.monitoring.metrics.MetricRegistryImpl;
/** Instrumentation for reserved lists. */
class DomainLabelMetrics {
/** Possible premium list check outcomes. */
enum PremiumListCheckOutcome {
/** Bloom filter knows it is not premium */
BLOOM_FILTER_NEGATIVE,
/** Bloom filter thinks it might be premium, but it was already negatively cached */
CACHED_NEGATIVE,
/** Bloom filter thinks it might be premium, and it is, and was already in the cache */
CACHED_POSITIVE,
/** Bloom filter thinks it might be premium, but it is not (though wasn't in the cache) */
UNCACHED_NEGATIVE,
/** Bloom filter thinks it might be premium, and it is, but wasn't in the cache */
UNCACHED_POSITIVE
}
@AutoValue
abstract static class MetricsReservedListMatch {
static MetricsReservedListMatch create(
@ -62,6 +80,15 @@ class DomainLabelMetrics {
LabelDescriptor.create("reserved_list", "Reserved list name."),
LabelDescriptor.create("reservation_type", "Type of reservation found."));
/**
* Labels attached to {@link #premiumListChecks} and {@link #premiumListProcessingTime} metrics.
*/
private static final ImmutableSet<LabelDescriptor> PREMIUM_LIST_LABEL_DESCRIPTORS =
ImmutableSet.of(
LabelDescriptor.create("tld", "TLD"),
LabelDescriptor.create("premium_list", "Premium list name."),
LabelDescriptor.create("outcome", "Outcome of the premium list check."));
/** Metric counting the number of times a label was checked against all reserved lists. */
@VisibleForTesting
static final IncrementableMetric reservedListChecks =
@ -102,6 +129,28 @@ class DomainLabelMetrics {
"count",
RESERVED_LIST_HIT_LABEL_DESCRIPTORS);
/** Metric recording the result of each premium list check. */
@VisibleForTesting
static final IncrementableMetric premiumListChecks =
MetricRegistryImpl.getDefault()
.newIncrementableMetric(
"/domain_label/premium/checks",
"Count of premium list checks",
"count",
PREMIUM_LIST_LABEL_DESCRIPTORS);
/** Metric recording the time required to process each premium list check. */
@VisibleForTesting
static final EventMetric premiumListProcessingTime =
MetricRegistryImpl.getDefault()
.newEventMetric(
"/domain_label/premium/processing_time",
"Premium list check processing time",
"milliseconds",
PREMIUM_LIST_LABEL_DESCRIPTORS,
EventMetric.DEFAULT_FITTER);
/** Update all three reserved list metrics. */
static void recordReservedListCheckOutcome(
String tld, ImmutableSet<MetricsReservedListMatch> matches, double elapsedMillis) {
@ -124,4 +173,11 @@ class DomainLabelMetrics {
reservedListProcessingTime.record(
elapsedMillis, tld, matchCount, mostSevereReservedList, mostSevereReservationType);
}
/** Update both premium list metrics. */
static void recordPremiumListCheckOutcome(
String tld, String premiumList, PremiumListCheckOutcome outcome, double elapsedMillis) {
premiumListChecks.increment(tld, premiumList, outcome.name());
premiumListProcessingTime.record(elapsedMillis, tld, premiumList, outcome.name());
}
}

View file

@ -43,6 +43,7 @@ import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.registry.Registry;
import google.registry.util.NonFinalForTesting;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@ -83,7 +84,17 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
* checked for existence. Otherwise, we know it's not premium, and no Datastore load is
* required.
*/
BloomFilter<String> probablePremiumLabels;
private BloomFilter<String> probablePremiumLabels;
/**
* Get the bloom filter.
*
* <p>Note that this is not a copy, but the mutable object itself, because copying would be
* expensive. You probably should not modify the filter unless you know what you're doing.
*/
public BloomFilter<String> getProbablePremiumLabels() {
return probablePremiumLabels;
}
/**
* The maximum size of the bloom filter.
@ -182,24 +193,32 @@ public final class PremiumList extends BaseDomainLabelList<Money, PremiumList.Pr
* that exist, as well as those that might exist according to the bloom filter, must be cached).
* The entries judged least likely to be accessed again will be evicted first.
*/
@NonFinalForTesting
@VisibleForTesting
static final LoadingCache<Key<PremiumListEntry>, Optional<PremiumListEntry>>
cachePremiumListEntries =
CacheBuilder.newBuilder()
.expireAfterWrite(getSingletonCachePersistDuration().getMillis(), MILLISECONDS)
.maximumSize(getStaticPremiumListMaxCachedEntries())
.build(
new CacheLoader<Key<PremiumListEntry>, Optional<PremiumListEntry>>() {
@Override
public Optional<PremiumListEntry> load(final Key<PremiumListEntry> entryKey) {
return ofy()
.doTransactionless(
new Work<Optional<PremiumListEntry>>() {
@Override
public Optional<PremiumListEntry> run() {
return Optional.fromNullable(ofy().load().key(entryKey).now());
}});
}});
static LoadingCache<Key<PremiumListEntry>, Optional<PremiumListEntry>> cachePremiumListEntries =
createCachePremiumListEntries(getSingletonCachePersistDuration().getMillis());
@VisibleForTesting
static LoadingCache<Key<PremiumListEntry>, Optional<PremiumListEntry>>
createCachePremiumListEntries(long cachePersistDurationMillis) {
return CacheBuilder.newBuilder()
.expireAfterWrite(cachePersistDurationMillis, MILLISECONDS)
.maximumSize(getStaticPremiumListMaxCachedEntries())
.build(
new CacheLoader<Key<PremiumListEntry>, Optional<PremiumListEntry>>() {
@Override
public Optional<PremiumListEntry> load(final Key<PremiumListEntry> entryKey) {
return ofy()
.doTransactionless(
new Work<Optional<PremiumListEntry>>() {
@Override
public Optional<PremiumListEntry> run() {
return Optional.fromNullable(ofy().load().key(entryKey).now());
}
});
}
});
}
@VisibleForTesting
public Key<PremiumListRevision> getRevisionKey() {

View file

@ -18,10 +18,17 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.partition;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.BLOOM_FILTER_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.CACHED_POSITIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_NEGATIVE;
import static google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome.UNCACHED_POSITIVE;
import static google.registry.model.registry.label.PremiumList.cachePremiumListEntries;
import static google.registry.model.registry.label.PremiumList.cachePremiumListRevisions;
import static google.registry.model.registry.label.PremiumList.cachePremiumLists;
import static org.joda.time.DateTimeZone.UTC;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
@ -33,6 +40,7 @@ import com.googlecode.objectify.Key;
import com.googlecode.objectify.VoidWork;
import com.googlecode.objectify.Work;
import google.registry.model.registry.Registry;
import google.registry.model.registry.label.DomainLabelMetrics.PremiumListCheckOutcome;
import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import java.util.List;
@ -47,6 +55,17 @@ public final class PremiumListUtils {
/** The number of premium list entry entities that are created and deleted per batch. */
static final int TRANSACTION_BATCH_SIZE = 200;
/** Value type class used by {@link #checkStatus} to return the results of a premiumness check. */
@AutoValue
abstract static class CheckResults {
static CheckResults create(PremiumListCheckOutcome checkOutcome, Optional<Money> premiumPrice) {
return new AutoValue_PremiumListUtils_CheckResults(checkOutcome, premiumPrice);
}
abstract PremiumListCheckOutcome checkOutcome();
abstract Optional<Money> premiumPrice();
}
/**
* Returns the premium price for the specified label and registry, or absent if the label is not
* premium.
@ -54,8 +73,9 @@ public final class PremiumListUtils {
public static Optional<Money> getPremiumPrice(String label, Registry registry) {
// If the registry has no configured premium list, then no labels are premium.
if (registry.getPremiumList() == null) {
return Optional.<Money> absent();
return Optional.<Money>absent();
}
DateTime startTime = DateTime.now(UTC);
String listName = registry.getPremiumList().getName();
Optional<PremiumList> optionalPremiumList = PremiumList.get(listName);
checkState(optionalPremiumList.isPresent(), "Could not load premium list '%s'", listName);
@ -68,21 +88,45 @@ public final class PremiumListUtils {
"Could not load premium list revision " + premiumList.getRevisionKey(), e);
}
checkState(
revision.probablePremiumLabels != null,
revision.getProbablePremiumLabels() != null,
"Probable premium labels bloom filter is null on revision '%s'",
premiumList.getRevisionKey());
if (revision.probablePremiumLabels.mightContain(label)) {
Key<PremiumListEntry> entryKey =
Key.create(premiumList.getRevisionKey(), PremiumListEntry.class, label);
try {
Optional<PremiumListEntry> entry = cachePremiumListEntries.get(entryKey);
return (entry.isPresent()) ? Optional.of(entry.get().getValue()) : Optional.<Money>absent();
} catch (InvalidCacheLoadException | ExecutionException e) {
throw new RuntimeException("Could not load premium list entry " + entryKey, e);
CheckResults checkResults = checkStatus(revision, label);
DomainLabelMetrics.recordPremiumListCheckOutcome(
registry.getTldStr(),
listName,
checkResults.checkOutcome(),
DateTime.now(UTC).getMillis() - startTime.getMillis());
return checkResults.premiumPrice();
}
private static CheckResults checkStatus(PremiumListRevision premiumListRevision, String label) {
if (!premiumListRevision.getProbablePremiumLabels().mightContain(label)) {
return CheckResults.create(BLOOM_FILTER_NEGATIVE, Optional.<Money>absent());
}
Key<PremiumListEntry> entryKey =
Key.create(Key.create(premiumListRevision), PremiumListEntry.class, label);
try {
// getIfPresent() returns null if the key is not in the cache
Optional<PremiumListEntry> entry = cachePremiumListEntries.getIfPresent(entryKey);
if (entry != null) {
if (entry.isPresent()) {
return CheckResults.create(CACHED_POSITIVE, Optional.of(entry.get().getValue()));
} else {
return CheckResults.create(CACHED_NEGATIVE, Optional.<Money>absent());
}
}
} else {
return Optional.<Money>absent();
entry = cachePremiumListEntries.get(entryKey);
if (entry.isPresent()) {
return CheckResults.create(UNCACHED_POSITIVE, Optional.of(entry.get().getValue()));
} else {
return CheckResults.create(UNCACHED_NEGATIVE, Optional.<Money>absent());
}
} catch (InvalidCacheLoadException | ExecutionException e) {
throw new RuntimeException("Could not load premium list entry " + entryKey, e);
}
}