Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright 2014 Groupon.com
*
* 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.arpnetworking.metrics.impl;
import com.arpnetworking.metrics.Counter;
import com.arpnetworking.metrics.Metrics;
import com.arpnetworking.metrics.Quantity;
import com.arpnetworking.metrics.Sink;
import com.arpnetworking.metrics.Timer;
import com.arpnetworking.metrics.Unit;
import com.arpnetworking.metrics.Units;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import javax.annotation.Nullable;
/**
* Default implementation of Metrics that publishes metrics as
* time series data (TSD).
*
* This class does not throw exceptions if it is used improperly or if the
* underlying IO subsystem fails to write the metrics. An example of improper
* use would be if the user invokes stop on a timer without calling start. To
* prevent breaking the client application no exception is thrown; instead a
* warning is logged using the SLF4J LoggerFactory for this class.
*
* Another example would be if the disk is full or fails to record the metrics
* when close is invoked the library will not throw an exception.
* However, it will attempt to write a warning using the SLF4J
* LoggerFactory for this class; although this is likely to fail
* if the underlying hardware is experiencing problems.
*
* If clients desire an intrusive failure propagation strategy or prefer to
* implement their own failure handling via callback we are open to implementing
* such an alternative strategy. Please contact us with your feature request.
*
* For more information about the semantics of this class and its methods please
* refer to the Metrics interface documentation. To create an
* instance of this class use TsdMetricsFactory.
*
* This class is thread safe; however, it makes no effort to order
* operations on the same data. For example, it is safe for two threads to
* start and stop separate timers but if the threads start and stop the same
* timer than it is up to the caller to ensure that start is called before
* stop.
*
* The library does attempt to detect incorrect usage, for example modifying
* metrics after closing and starting but never stopping a timer; however, in
* a multithreaded environment it is not guaranteed that these warnings are
* emitted. It is up to clients to ensure that multithreaded use of the same
* TsdMetrics instance is correct.
*
* @author Ville Koskela (vkoskela at groupon dot com)
*/
public class TsdMetrics implements Metrics {
/**
* {@inheritDoc}
*/
@Override
public Counter createCounter(final String name) {
return createCounterInternal(name);
}
/**
* {@inheritDoc}
*/
@Override
public void incrementCounter(final String name) {
incrementCounter(name, 1L);
}
/**
* {@inheritDoc}
*/
@Override
public void incrementCounter(final String name, final long value) {
if (!assertIsOpen()) {
return;
}
final TsdCounter counter = _counters.compute(name, _createCounterBiFunction);
counter.increment(value);
}
/**
* {@inheritDoc}
*/
@Override
public void decrementCounter(final String name) {
incrementCounter(name, -1L);
}
/**
* {@inheritDoc}
*/
@Override
public void decrementCounter(final String name, final long value) {
incrementCounter(name, -1L * value);
}
/**
* {@inheritDoc}
*/
@Override
public void resetCounter(final String name) {
if (!assertIsOpen()) {
return;
}
_counters.put(name, createCounterInternal(name));
}
/**
* {@inheritDoc}
*/
@Override
public Timer createTimer(final String name) {
if (!assertIsOpen()) {
// To prevent the calling code from throwing a NPE we just return a
// timer object; note that the call to assertIsOpen has already
// logged a warning about incorrect use of the class.
return TsdTimer.newInstance(name, _isOpen);
}
final ConcurrentLinkedDeque samples = getOrCreateDeque(_timerSamples, name);
final TsdTimer timer = TsdTimer.newInstance(name, _isOpen);
samples.add(timer);
return timer;
}
/**
* {@inheritDoc}
*/
@Override
public void startTimer(final String name) {
if (!assertIsOpen()) {
return;
}
// In this case the normal behavior is to insert; only if the library is
// being incorrectly used will there be a running timer with the same
// name.
final TsdTimer timer = TsdTimer.newInstance(name, _isOpen);
if (_timers.putIfAbsent(name, timer) != null) {
// This is in place of an exception; see class Javadoc
_logger.warn(String.format("Cannot start timer because timer already started; timerName=%s", name));
return;
}
final ConcurrentLinkedDeque samples = getOrCreateDeque(_timerSamples, name);
samples.add(timer);
}
/**
* {@inheritDoc}
*/
@Override
public void stopTimer(final String name) {
if (!assertIsOpen()) {
return;
}
final Timer timer = _timers.remove(name);
if (timer == null) {
// This is in place of an exception; see class Javadoc
_logger.warn(String.format("Cannot stop timer because timer was not started; timerName=%s", name));
return;
}
timer.stop();
}
/**
* {@inheritDoc}
*/
@Override
public void setTimer(final String name, final long duration, @Nullable final TimeUnit unit) {
setTimer(name, duration, fromTimeUnit(unit));
}
/**
* {@inheritDoc}
*/
@Override
public void setTimer(final String name, final long duration, @Nullable final Unit unit) {
if (!assertIsOpen()) {
return;
}
final ConcurrentLinkedDeque samples = getOrCreateDeque(_timerSamples, name);
samples.add(TsdQuantity.newInstance(Long.valueOf(duration), unit));
}
/**
* {@inheritDoc}
*/
@Override
public void setGauge(final String name, final double value) {
setGauge(name, value, null);
}
/**
* {@inheritDoc}
*/
@Override
public void setGauge(final String name, final double value, @Nullable final Unit unit) {
if (!assertIsOpen()) {
return;
}
final Deque list = getOrCreateDeque(_gaugeSamples, name);
list.add(TsdQuantity.newInstance(Double.valueOf(value), unit));
}
/**
* {@inheritDoc}
*/
@Override
public void setGauge(final String name, final long value) {
setGauge(name, value, null);
}
/**
* {@inheritDoc}
*/
@Override
public void setGauge(final String name, final long value, @Nullable final Unit unit) {
if (!assertIsOpen()) {
return;
}
final Deque list = getOrCreateDeque(_gaugeSamples, name);
list.add(TsdQuantity.newInstance(Long.valueOf(value), unit));
}
/**
* {@inheritDoc}
*/
@Override
public void addAnnotation(final String key, final String value) {
if (!assertIsOpen()) {
return;
}
_annotations.put(key, value);
}
/**
* {@inheritDoc}
*/
@Override
public void addAnnotations(final Map map) {
if (!assertIsOpen()) {
return;
}
for (final Map.Entry entry : map.entrySet()) {
addAnnotation(entry.getKey(), entry.getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isOpen() {
return _isOpen.get();
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
if (!assertIsOpen(_isOpen.getAndSet(false))) {
return;
}
_finalTimestamp = _clock.instant();
_annotations.put(INITIAL_TIMESTAMP_KEY, DATE_TIME_FORMATTER.format(_initialTimestamp));
_annotations.put(FINAL_TIMESTAMP_KEY, DATE_TIME_FORMATTER.format(_finalTimestamp));
final Map annotations = Collections.unmodifiableMap(_annotations);
final Map> timerSamples = cloneSamples(
_timerSamples,
PREDICATE_STOPPED_TIMERS,
PREDICATE_NON_ABORTED_TIMERS);
final Map> counterSamples = cloneSamples(_counterSamples);
final Map> gaugeSamples = cloneSamples(_gaugeSamples);
for (final Sink sink : _sinks) {
sink.record(new TsdEvent(
annotations,
timerSamples,
counterSamples,
gaugeSamples));
}
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public Instant getOpenTime() {
return _initialTimestamp;
}
/**
* {@inheritDoc}
*/
@Override
@Nullable
public Instant getCloseTime() {
return _finalTimestamp;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return String.format(
"TsdMetrics{Sinks=%s, ServiceName=%s, ClusterName=%s, HostName=%s}",
_sinks,
_annotations.get(SERVICE_KEY),
_annotations.get(CLUSTER_KEY),
_annotations.get(HOST_KEY));
}
// NOTE: Package private for testing
/* package private */ T getOrCreate(final ConcurrentMap map, final String key, final T newValue) {
final T currentValue = map.putIfAbsent(key, newValue);
if (currentValue != null) {
return currentValue;
}
return newValue;
}
private TsdCounter createCounterInternal(final String name) {
if (!assertIsOpen()) {
// To prevent the calling code from throwing a NPE we just return a
// counter object; note that the call to assertIsOpen has already
// logged a warning about incorrect use of the class.
return TsdCounter.newInstance(name, _isOpen);
}
// Create and register a new counter sample
final ConcurrentLinkedDeque counters = getOrCreateDeque(_counterSamples, name);
final TsdCounter newCounter = TsdCounter.newInstance(name, _isOpen);
counters.add(newCounter);
return newCounter;
}
private ConcurrentLinkedDeque getOrCreateDeque(
final ConcurrentMap> map,
final String key) {
ConcurrentLinkedDeque deque = map.get(key);
if (deque == null) {
final ConcurrentLinkedDeque newDeque = new ConcurrentLinkedDeque<>();
deque = getOrCreate(map, key, newDeque);
}
return deque;
}
private boolean assertIsOpen() {
return assertIsOpen(_isOpen.get());
}
private boolean assertIsOpen(final boolean isOpen) {
if (!isOpen) {
// This is in place of an exception; see class Javadoc
_logger.warn("Metrics object was closed during an operation; you may have a race condition");
}
return isOpen;
}
@SafeVarargs
// CHECKSTYLE.OFF: RedundantModifier - Required for @SafeVarargs
private final Map> cloneSamples(
final Map> samples,
final Predicate... predicates) {
// CHECKSTYLE.OFF: IllegalInstantiation - No Guava
final Map> clonedSamples = new HashMap<>((int) (samples.size() / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
// CHECKSTYLE.ON: IllegalInstantiation
for (final Map.Entry> entry : samples.entrySet()) {
final List quantities = new ArrayList<>(entry.getValue().size());
clonedSamples.put(entry.getKey(), quantities);
for (final Quantity quantity : entry.getValue()) {
final List> rejections = new ArrayList<>();
for (Predicate predicate : predicates) {
if (!predicate.apply(quantity)) {
rejections.add(predicate);
}
}
if (rejections.isEmpty()) {
quantities.add(TsdQuantity.newInstance(quantity.getValue(), quantity.getUnit()));
} else {
_logger.warn(String.format(
"Sample rejected; name=%s, sample=%s, rejections=%s",
entry.getKey(),
quantity,
rejections));
}
}
}
return clonedSamples;
}
/* package private */ @Nullable static Unit fromTimeUnit(@Nullable final TimeUnit timeUnit) {
if (timeUnit == null) {
return null;
}
return TIME_UNIT_MAP.get(timeUnit);
}
// NOTE: Use an instance of TsdMetricsFactory to construct TsdMetrics instances.
/* package private */ TsdMetrics(
final String serviceName,
final String clusterName,
final String hostName,
final List sinks) {
this(serviceName, clusterName, hostName, sinks, Clock.systemUTC(), LOGGER);
}
// NOTE: Package private for testing.
/* package private */ TsdMetrics(
final String serviceName,
final String clusterName,
final String hostName,
final List sinks,
final Clock clock,
final Logger logger) {
_sinks = sinks;
_logger = logger;
_clock = clock;
_initialTimestamp = _clock.instant();
_annotations.put(ID_KEY, UUID.randomUUID().toString());
_annotations.put(HOST_KEY, hostName);
_annotations.put(SERVICE_KEY, serviceName);
_annotations.put(CLUSTER_KEY, clusterName);
}
private final List _sinks;
private final Logger _logger;
private final AtomicBoolean _isOpen = new AtomicBoolean(true);
private final Clock _clock;
private final Instant _initialTimestamp;
private Instant _finalTimestamp = null;
private final ConcurrentMap _counters = new ConcurrentHashMap<>();
private final ConcurrentMap _timers = new ConcurrentHashMap<>();
private final ConcurrentMap> _counterSamples = new ConcurrentHashMap<>();
private final ConcurrentMap> _timerSamples = new ConcurrentHashMap<>();
private final ConcurrentMap> _gaugeSamples = new ConcurrentHashMap<>();
private final ConcurrentMap _annotations = new ConcurrentHashMap<>();
private final BiFunction _createCounterBiFunction = new CreateCounterFunction();
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ").withZone(ZoneId.of("UTC"));
private static final Predicate PREDICATE_STOPPED_TIMERS = new StoppedTimersPredicate();
private static final Predicate PREDICATE_NON_ABORTED_TIMERS = new NonAbortedTimersPredicate();
private static final Logger LOGGER = LoggerFactory.getLogger(TsdMetrics.class);
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final String FINAL_TIMESTAMP_KEY = "_end";
private static final String INITIAL_TIMESTAMP_KEY = "_start";
private static final String ID_KEY = "_id";
private static final String HOST_KEY = "_host";
private static final String SERVICE_KEY = "_service";
private static final String CLUSTER_KEY = "_cluster";
// CHECKSTYLE.OFF: IllegalInstantiation - No Guava dependency here.
private static final Map TIME_UNIT_MAP = new HashMap<>();
// CHECKSTYLE.ON: IllegalInstantiation
static {
TIME_UNIT_MAP.put(TimeUnit.DAYS, Units.DAY);
TIME_UNIT_MAP.put(TimeUnit.HOURS, Units.HOUR);
TIME_UNIT_MAP.put(TimeUnit.MINUTES, Units.MINUTE);
TIME_UNIT_MAP.put(TimeUnit.SECONDS, Units.SECOND);
TIME_UNIT_MAP.put(TimeUnit.MILLISECONDS, Units.MILLISECOND);
TIME_UNIT_MAP.put(TimeUnit.MICROSECONDS, Units.MICROSECOND);
TIME_UNIT_MAP.put(TimeUnit.NANOSECONDS, Units.NANOSECOND);
}
private final class CreateCounterFunction implements BiFunction {
public CreateCounterFunction() {}
@Override
public TsdCounter apply(final String name, final TsdCounter existingValue) {
if (existingValue == null) {
final TsdCounter newCounter = TsdCounter.newInstance(name, _isOpen);
final ConcurrentLinkedDeque counters = getOrCreateDeque(_counterSamples, name);
counters.add(newCounter);
return newCounter;
}
return existingValue;
}
}
private interface Predicate {
boolean apply(T item);
}
private static final class StoppedTimersPredicate implements Predicate {
@Override
public boolean apply(final Quantity item) {
if (item instanceof Timer) {
final Timer timer = (Timer) item;
return !timer.isRunning();
}
return true;
}
}
private static final class NonAbortedTimersPredicate implements Predicate {
@Override
public boolean apply(final Quantity item) {
if (item instanceof Timer) {
final Timer timer = (Timer) item;
return !timer.isAborted();
}
return true;
}
}
}