/*
* Copyright 2014-2017 Netflix, Inc.
*
* 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 com.netflix.spectator.api;
import com.netflix.spectator.impl.Config;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.function.ToDoubleFunction;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Registry to manage a set of meters.
*/
public interface Registry extends Iterable<Meter> {
/**
* The clock used by the registry for timing events.
*/
Clock clock();
/**
* Configuration settings used for this registry.
*/
default RegistryConfig config() {
return Config.defaultConfig();
}
/**
* Creates an identifier for a meter. All ids passed into other calls should be created by the
* registry.
*
* @param name
* Description of the measurement that is being collected.
*/
Id createId(String name);
/**
* Creates an identifier for a meter. All ids passed into other calls should be created by the
* registry.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
*/
Id createId(String name, Iterable<Tag> tags);
/**
* Add a custom meter to the registry.
*/
void register(Meter meter);
/**
* Returns a map that can be used to associate state with the registry. Users instrumenting
* their application will most likely never need to use this method.
*
* The primary use case is for building custom meter types that need some additional state
* beyond the core types supported by the registry. This map can be used to store the state
* so that the lifecycle of the data is connected to the registry. For an example, see some
* of the built in patterns such as {@link com.netflix.spectator.api.patterns.LongTaskTimer}.
*/
ConcurrentMap<Id, Object> state();
/**
* Measures the rate of some activity. A counter is for continuously incrementing sources like
* the number of requests that are coming into a server.
*
* @param id
* Identifier created by a call to {@link #createId}
*/
Counter counter(Id id);
/**
* Measures the rate and variation in amount for some activity. For example, it could be used to
* get insight into the variation in response sizes for requests to a server.
*
* @param id
* Identifier created by a call to {@link #createId}
*/
DistributionSummary distributionSummary(Id id);
/**
* Measures the rate and time taken for short running tasks.
*
* @param id
* Identifier created by a call to {@link #createId}
*/
Timer timer(Id id);
/**
* Represents a value sampled from another source. For example, the size of queue. The caller
* is responsible for sampling the value regularly and calling {@link Gauge#set(double)}.
* Registry implementations are free to expire the gauge if it has not been updated in the
* last minute. If you do not want to worry about the sampling, then use one of the helpers
* linked below instead.
*
* @see #gauge(Id, Number)
* @see #gauge(Id, Object, ToDoubleFunction)
* @see #collectionSize(Id, Collection)
* @see #mapSize(Id, Map)
*
* @param id
* Identifier created by a call to {@link #createId}
*/
default Gauge gauge(Id id) {
// Added in 0.45.0. For backwards compatibility we use a default implementation here that
// returns a noop implementation.
return NoopGauge.INSTANCE;
}
/**
* Returns the meter associated with a given id.
*
* @param id
* Identifier for the meter.
* @return
* Instance of the meter or null if there is no match.
*/
Meter get(Id id);
/** Iterator for traversing the set of meters in the registry. */
Iterator<Meter> iterator();
/////////////////////////////////////////////////////////////////
// Additional helper methods below
/**
* Returns the first underlying registry that is an instance of {@code c}.
*/
@SuppressWarnings("unchecked")
default <T extends Registry> T underlying(Class<T> c) {
if (c.isAssignableFrom(getClass())) {
return (T) this;
} else if (this instanceof CompositeRegistry) {
return ((CompositeRegistry) this).find(c);
} else {
return null;
}
}
/**
* Creates an identifier for a meter.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Identifier for a meter.
*/
default Id createId(String name, String... tags) {
return createId(name, Utils.toIterable(tags));
}
/**
* Creates an identifier for a meter.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Identifier for a meter.
*/
default Id createId(String name, Map<String, String> tags) {
return createId(name).withTags(tags);
}
/**
* Measures the rate of some activity.
*
* @param name
* Description of the measurement that is being collected.
* @return
* Counter instance with the corresponding id.
*/
default Counter counter(String name) {
return counter(createId(name));
}
/**
* Measures the rate of some activity.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Counter instance with the corresponding id.
*/
default Counter counter(String name, Iterable<Tag> tags) {
return counter(createId(name, tags));
}
/**
* Measures the rate of some activity.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Counter instance with the corresponding id.
*/
default Counter counter(String name, String... tags) {
return counter(createId(name, Utils.toIterable(tags)));
}
/**
* Measures the sample distribution of events.
*
* @param name
* Description of the measurement that is being collected.
* @return
* Summary instance with the corresponding id.
*/
default DistributionSummary distributionSummary(String name) {
return distributionSummary(createId(name));
}
/**
* Measures the sample distribution of events.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Summary instance with the corresponding id.
*/
default DistributionSummary distributionSummary(String name, Iterable<Tag> tags) {
return distributionSummary(createId(name, tags));
}
/**
* Measures the sample distribution of events.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Summary instance with the corresponding id.
*/
default DistributionSummary distributionSummary(String name, String... tags) {
return distributionSummary(createId(name, Utils.toIterable(tags)));
}
/**
* Measures the time taken for short tasks.
*
* @param name
* Description of the measurement that is being collected.
* @return
* Timer instance with the corresponding id.
*/
default Timer timer(String name) {
return timer(createId(name));
}
/**
* Measures the time taken for short tasks.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Timer instance with the corresponding id.
*/
default Timer timer(String name, Iterable<Tag> tags) {
return timer(createId(name, tags));
}
/**
* Measures the time taken for short tasks.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Timer instance with the corresponding id.
*/
default Timer timer(String name, String... tags) {
return timer(createId(name, Utils.toIterable(tags)));
}
/**
* Measures the time taken for long tasks.
*
* @param id
* Identifier for the metric being registered.
* @return
* Timer instance with the corresponding id.
*/
default LongTaskTimer longTaskTimer(Id id) {
// Note: this method is only included in the registry for historical reasons to
// maintain compatibility. Future patterns should just use the registry not be
// created by the registry.
return com.netflix.spectator.api.patterns.LongTaskTimer.get(this, id);
}
/**
* Measures the time taken for long tasks.
*
* @param name
* Description of the measurement that is being collected.
* @return
* Timer instance with the corresponding id.
*/
default LongTaskTimer longTaskTimer(String name) {
return longTaskTimer(createId(name));
}
/**
* Measures the time taken for long tasks.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Timer instance with the corresponding id.
*/
default LongTaskTimer longTaskTimer(String name, Iterable<Tag> tags) {
return longTaskTimer(createId(name, tags));
}
/**
* Measures the time taken for long tasks.
*
* @param name
* Description of the measurement that is being collected.
* @param tags
* Other dimensions that can be used to classify the measurement.
* @return
* Timer instance with the corresponding id.
*/
default LongTaskTimer longTaskTimer(String name, String... tags) {
return longTaskTimer(createId(name, Utils.toIterable(tags)));
}
/**
* Register a gauge that reports the value of the {@link java.lang.Number}. The registration
* will keep a weak reference to the number so it will not prevent garbage collection.
* The number implementation used should be thread safe. See
* {@link #gauge(Id, Object, ToDoubleFunction)} for more information on gauges.
*
* @param id
* Identifier for the metric being registered.
* @param number
* Thread-safe implementation of {@link Number} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Number> T gauge(Id id, T number) {
return gauge(id, number, Number::doubleValue);
}
/**
* Register a gauge that reports the value of the {@link java.lang.Number}. See
* {@link #gauge(Id, Number)}.
*
* @param name
* Name of the metric being registered.
* @param number
* Thread-safe implementation of {@link Number} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Number> T gauge(String name, T number) {
return gauge(createId(name), number);
}
/**
* Register a gauge that reports the value of the {@link java.lang.Number}. See
* {@link #gauge(Id, Number)}.
*
* @param name
* Name of the metric being registered.
* @param tags
* Sequence of dimensions for breaking down the name.
* @param number
* Thread-safe implementation of {@link Number} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Number> T gauge(String name, Iterable<Tag> tags, T number) {
return gauge(createId(name, tags), number);
}
/**
* Register a gauge that reports the value of the object after the function
* {@code f} is applied. The registration will keep a weak reference to the object so it will
* not prevent garbage collection. Applying {@code f} on the object should be thread safe.
*
* If multiple gauges are registered with the same id, then the values will be aggregated and
* the sum will be reported. For example, registering multiple gauges for active threads in
* a thread pool with the same id would produce a value that is the overall number
* of active threads. For other behaviors, manage it on the user side and avoid multiple
* registrations.
*
* @param id
* Identifier for the metric being registered.
* @param obj
* Object used to compute a value.
* @param f
* Function that is applied on the value for the number.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T> T gauge(Id id, T obj, ToDoubleFunction<T> f) {
register(new ObjectGauge<>(clock(), id, obj, f));
return obj;
}
/**
* Register a gauge that reports the value of the object. See
* {@link #gauge(Id, Object, ToDoubleFunction)}.
*
* @param name
* Name of the metric being registered.
* @param obj
* Object used to compute a value.
* @param f
* Function that is applied on the value for the number.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T> T gauge(String name, T obj, ToDoubleFunction<T> f) {
return gauge(createId(name), obj, f);
}
/**
* Register a gauge that reports the size of the {@link java.util.Collection}. The registration
* will keep a weak reference to the collection so it will not prevent garbage collection.
* The collection implementation used should be thread safe. Note that calling
* {@link java.util.Collection#size()} can be expensive for some collection implementations
* and should be considered before registering.
*
* @param id
* Identifier for the metric being registered.
* @param collection
* Thread-safe implementation of {@link Collection} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Collection<?>> T collectionSize(Id id, T collection) {
return gauge(id, collection, Collection::size);
}
/**
* Register a gauge that reports the size of the {@link java.util.Collection}. The registration
* will keep a weak reference to the collection so it will not prevent garbage collection.
* The collection implementation used should be thread safe. Note that calling
* {@link java.util.Collection#size()} can be expensive for some collection implementations
* and should be considered before registering.
*
* @param name
* Name of the metric being registered.
* @param collection
* Thread-safe implementation of {@link Collection} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Collection<?>> T collectionSize(String name, T collection) {
return collectionSize(createId(name), collection);
}
/**
* Register a gauge that reports the size of the {@link java.util.Map}. The registration
* will keep a weak reference to the collection so it will not prevent garbage collection.
* The collection implementation used should be thread safe. Note that calling
* {@link java.util.Map#size()} can be expensive for some collection implementations
* and should be considered before registering.
*
* @param id
* Identifier for the metric being registered.
* @param collection
* Thread-safe implementation of {@link Map} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Map<?, ?>> T mapSize(Id id, T collection) {
return gauge(id, collection, Map::size);
}
/**
* Register a gauge that reports the size of the {@link java.util.Map}. The registration
* will keep a weak reference to the collection so it will not prevent garbage collection.
* The collection implementation used should be thread safe. Note that calling
* {@link java.util.Map#size()} can be expensive for some collection implementations
* and should be considered before registering.
*
* @param name
* Name of the metric being registered.
* @param collection
* Thread-safe implementation of {@link Map} used to access the value.
* @return
* The number that was passed in so the registration can be done as part of an assignment
* statement.
*/
default <T extends Map<?, ?>> T mapSize(String name, T collection) {
return mapSize(createId(name), collection);
}
/**
* Register a gauge that reports the return value of invoking the method on the object. The
* registration will keep a weak reference to the object so it will not prevent garbage
* collection. The registered method should be thread safe and cheap to invoke. Any potentially
* long running or expensive activity such as IO should not be performed inline.
*
* @param id
* Identifier for the metric being registered.
* @param obj
* Object used to compute a value.
* @param method
* Name of the method to invoke on the object.
*/
default void methodValue(Id id, Object obj, String method) {
final Method m = Utils.getGaugeMethod(this, id, obj, method);
if (m != null) {
gauge(id, obj, Functions.invokeMethod(m));
}
}
/**
* Register a gauge that reports the return value of invoking the method on the object. The
* registration will keep a weak reference to the object so it will not prevent garbage
* collection. The registered method should be thread safe and cheap to invoke. Any potentially
* long running or expensive activity such as IO should not be performed inline.
*
* @param name
* Name of the metric being registered.
* @param obj
* Object used to compute a value.
* @param method
* Name of the method to invoke on the object.
*/
default void methodValue(String name, Object obj, String method) {
methodValue(createId(name), obj, method);
}
/** Returns a stream of all registered meters. */
default Stream<Meter> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* Returns a stream of all registered counters. This operation is mainly used for testing as
* a convenient way to get an aggregated value. For example, to generate a summary of all
* counters with name "foo":
*
* <pre>
* LongSummaryStatistics summary = r.counters()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingLong(Counter::count));
* </pre>
*/
default Stream<Counter> counters() {
return stream().filter(m -> m instanceof Counter).map(m -> (Counter) m);
}
/**
* Returns a stream of all registered distribution summaries. This operation is mainly used for
* testing as a convenient way to get an aggregated value. For example, to generate a summary of
* the counts and total amounts for all distribution summaries with name "foo":
*
* <pre>
* LongSummaryStatistics countSummary = r.distributionSummaries()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingLong(DistributionSummary::count));
*
* LongSummaryStatistics totalSummary = r.distributionSummaries()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingLong(DistributionSummary::totalAmount));
*
* double avgAmount = (double) totalSummary.getSum() / countSummary.getSum();
* </pre>
*/
default Stream<DistributionSummary> distributionSummaries() {
return stream().filter(m -> m instanceof DistributionSummary).map(m -> (DistributionSummary) m);
}
/**
* Returns a stream of all registered timers. This operation is mainly used for testing as a
* convenient way to get an aggregated value. For example, to generate a summary of
* the counts and total amounts for all timers with name "foo":
*
* <pre>
* LongSummaryStatistics countSummary = r.timers()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingLong(Timer::count));
*
* LongSummaryStatistics totalSummary = r.timers()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingLong(Timer::totalTime));
*
* double avgTime = (double) totalSummary.getSum() / countSummary.getSum();
* </pre>
*/
default Stream<Timer> timers() {
return stream().filter(m -> m instanceof Timer).map(m -> (Timer) m);
}
/**
* Returns a stream of all registered gauges. This operation is mainly used for testing as a
* convenient way to get an aggregated value. For example, to generate a summary of
* the values for all gauges with name "foo":
*
* <pre>
* DoubleSummaryStatistics valueSummary = r.gauges()
* .filter(Functions.nameEquals("foo"))
* .collect(Collectors.summarizingDouble(Gauge::value));
*
* double sum = (double) valueSummary.getSum();
* </pre>
*/
default Stream<Gauge> gauges() {
return stream().filter(m -> m instanceof Gauge).map(m -> (Gauge) m);
}
/**
* Log a warning and if enabled propagate the exception {@code t}. As a general rule
* instrumentation code should degrade gracefully and avoid impacting the core application. If
* the user makes a mistake and causes something to break, then it should not impact the
* application unless that mistake triggers a problem outside of the instrumentation code.
* However, in test code it is often better to throw so that mistakes are caught and corrected.
*
* This method is used to handle exceptions internal to the instrumentation code. Propagation
* is controlled by the {@link RegistryConfig#propagateWarnings()} setting. If the setting
* is true, then the exception will be propagated. Otherwise the exception will only get logged
* as a warning.
*
* @param msg
* Message written out to the log.
* @param t
* Exception to log and optionally propagate.
*/
default void propagate(String msg, Throwable t) {
LoggerFactory.getLogger(getClass()).warn(msg, t);
if (config().propagateWarnings()) {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RuntimeException(t);
}
}
}
/**
* Log a warning using the message from the exception and if enabled propagate the
* exception {@code t}. For more information see {@link #propagate(String, Throwable)}.
*
* @param t
* Exception to log and optionally propagate.
*/
default void propagate(Throwable t) {
propagate(t.getMessage(), t);
}
}