// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.metrics.simple; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableMap; import com.yahoo.concurrent.ThreadLocalDirectory; /** * The reception point for measurements. This is the class users should inject * in constructors for declaring instances of {@link Counter} and {@link Gauge} * for the actual measurement of metrics. * * @author Steinar Knutsen */ @Beta public class MetricReceiver { public static final MetricReceiver nullImplementation = new NullReceiver(); private final ThreadLocalDirectory metricsCollection; private final AtomicReference currentSnapshot; // metricSettings is volatile for reading, the lock is for updates private final Object histogramDefinitionsLock = new Object(); private volatile Map metricSettings; private static final class NullCounter extends Counter { NullCounter() { super(null, null, null); } @Override public void add() { } @Override public void add(long n) { } @Override public void add(Point p) { } @Override public void add(long n, Point p) { } @Override public PointBuilder builder() { return super.builder(); } } private static final class NullGauge extends Gauge { NullGauge() { super(null, null, null); } @Override public void sample(double x) { } @Override public void sample(double x, Point p) { } @Override public PointBuilder builder() { return super.builder(); } } private static final class NullReceiver extends MetricReceiver { NullReceiver() { super(null, null); } @Override public void update(Sample s) { } @Override public Counter declareCounter(String name) { return new NullCounter(); } @Override public Counter declareCounter(String name, Point boundDimensions) { return new NullCounter(); } @Override public Gauge declareGauge(String name) { return new NullGauge(); } @Override public Gauge declareGauge(String name, Point boundDimensions) { return new NullGauge(); } @Override public Gauge declareGauge(String name, Optional boundDimensions, MetricSettings customSettings) { return null; } @Override public PointBuilder pointBuilder() { return null; } @Override public Bucket getSnapshot() { return null; } @Override void addMetricDefinition(String metricName, MetricSettings definition) { } @Override MetricSettings getMetricDefinition(String metricName) { return null; } } public MetricReceiver(ThreadLocalDirectory metricsCollection, AtomicReference currentSnapshot) { this.metricsCollection = metricsCollection; this.currentSnapshot = currentSnapshot; metricSettings = new ImmutableMap.Builder().build(); } /** * Update a metric. This API is not intended for clients for the * simplemetrics API, declare a Counter or a Gauge using * {@link #declareCounter(String)}, {@link #declareCounter(String, Point)}, * {@link #declareGauge(String)}, or {@link #declareGauge(String, Point)} * instead. * * @param s * a single simple containing all meta data necessary to update a * metric */ public void update(Sample s) { // pass around the receiver instead of histogram settings to avoid reading any volatile if unnecessary s.setReceiver(this); metricsCollection.update(s); } /** * Declare a counter metric without setting any default position. * * @param name * the name of the metric * @return a thread-safe counter */ public Counter declareCounter(String name) { return declareCounter(name, null); } /** * Declare a counter metric, with default dimension values as given. Create * the point argument by using a builder from {@link #pointBuilder()}. * * @param name * the name of the metric * @param boundDimensions * dimensions which have a fixed value in the life cycle of the * metric object or null * @return a thread-safe counter with given default values */ public Counter declareCounter(String name, Point boundDimensions) { return new Counter(name, boundDimensions, this); } /** * Declare a gauge metric with any default position. * * @param name * the name of the metric * @return a thread-safe gauge instance */ public Gauge declareGauge(String name) { return declareGauge(name, null); } /** * Declare a gauge metric, with default dimension values as given. Create * the point argument by using a builder from {@link #pointBuilder()}. * * @param name * the name of the metric * @param boundDimensions * dimensions which have a fixed value in the life cycle of the * metric object or null * @return a thread-safe gauge metric */ public Gauge declareGauge(String name, Point boundDimensions) { Optional optionalOfBoundDimensions; if (boundDimensions == null) { optionalOfBoundDimensions = Optional.empty(); } else { optionalOfBoundDimensions = Optional.of(boundDimensions); } return declareGauge(name, optionalOfBoundDimensions, null); } /** * Declare a gauge metric, with default dimension values as given. Create * the point argument by using a builder from {@link #pointBuilder()}. * MetricSettings instances are built using * {@link MetricSettings.Builder}. * * @param name * the name of the metric * @param boundDimensions * an optional of dimensions which have a fixed value in the life * cycle of the metric object * @param customSettings * any optional settings * @return a thread-safe gauge metric */ public Gauge declareGauge(String name, Optional boundDimensions, MetricSettings customSettings) { if (customSettings != null) { addMetricDefinition(name, customSettings); } Point defaultDimensions = null; if (boundDimensions.isPresent()) { defaultDimensions = boundDimensions.get(); } return new Gauge(name, defaultDimensions, this); } /** * Create a PointBuilder instance with no default settings. PointBuilder * instances are not thread-safe. * * @return an "empty" point builder instance */ public PointBuilder pointBuilder() { return new PointBuilder(); } /** * Fetch the latest metric values, aggregated over all threads for the * configured sample history (by default five minutes). The values will be * less than 1 second old, and this method has only a memory barrier as side * effect. * * @return the latest five minutes of metrics */ public Bucket getSnapshot() { return currentSnapshot.get(); } /** * Add how to build a histogram for a given metric. * *

* Do note, this is not part of the public API. *

* * @param metricName * the metric where samples should be put in a histogram * @param definition * settings for a histogram */ void addMetricDefinition(String metricName, MetricSettings definition) { synchronized (histogramDefinitionsLock) { // read the volatile _after_ acquiring the lock Map oldMetricDefinitions = metricSettings; Map builderMap = new HashMap<>(oldMetricDefinitions.size() + 1); builderMap.putAll(oldMetricDefinitions); builderMap.put(metricName, definition); metricSettings = ImmutableMap.copyOf(builderMap); } } /** * Get how to build a histogram for a given metric, or null if no histogram * should be created. * *

* Do note, this is not part of the public API. *

* * @param metricName * the name of an arbitrary metric * @return the corresponding histogram definition or null */ MetricSettings getMetricDefinition(String metricName) { return metricSettings.get(metricName); } }