
com.arpnetworking.metrics.mad.Bucket Maven / Gradle / Ivy
/*
* Copyright 2015 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.mad;
import com.arpnetworking.commons.builder.OvalBuilder;
import com.arpnetworking.commons.builder.ThreadLocalBuilder;
import com.arpnetworking.logback.annotations.LogValue;
import com.arpnetworking.metrics.mad.model.DefaultQuantity;
import com.arpnetworking.metrics.mad.model.Metric;
import com.arpnetworking.metrics.mad.model.Quantity;
import com.arpnetworking.metrics.mad.model.Record;
import com.arpnetworking.steno.LogValueMapFactory;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.arpnetworking.tsdcore.model.AggregatedData;
import com.arpnetworking.tsdcore.model.CalculatedValue;
import com.arpnetworking.tsdcore.model.Key;
import com.arpnetworking.tsdcore.model.PeriodicData;
import com.arpnetworking.tsdcore.sinks.Sink;
import com.arpnetworking.tsdcore.statistics.Accumulator;
import com.arpnetworking.tsdcore.statistics.Calculator;
import com.arpnetworking.tsdcore.statistics.Statistic;
import com.arpnetworking.tsdcore.statistics.StatisticFactory;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import net.sf.oval.constraint.NotNull;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
/**
* Contains samples for a particular aggregation period in time.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
*/
/* package private */ final class Bucket {
/**
* Close the bucket. The aggregates for each metric are emitted to the sink.
*/
public void close() {
// Set the close flag before acquiring the write lock to allow "readers" to fail fast
if (_isOpen.getAndSet(false)) {
try {
// Acquire the write lock and flush the calculated statistics
_addCloseLock.writeLock().lock();
final ImmutableMultimap.Builder data = ImmutableMultimap.builder();
computeStatistics(_counterMetricCalculators, _specifiedCounterStatistics, data);
computeStatistics(_gaugeMetricCalculators, _specifiedGaugeStatistics, data);
computeStatistics(_timerMetricCalculators, _specifiedTimerStatistics, data);
computeStatistics(_explicitMetricCalculators, _specifiedStatisticsCache, data);
// TODO(vkoskela): Perform expression evaluation here. [NEXT]
// -> This still requires realizing and indexing the computed aggregated data
// in order to feed the expression evaluation. Once the filtering is consolidated
// we can probably just build a map here and then do one copy into immutable form
// in the PeriodicData. This becomes feasible with consolidated filtering because
// fewer copies (e.g. none) are made downstream.
// TODO(vkoskela): Perform alert evaluation here. [NEXT]
// -> This requires expressions. Otherwise, it's just a matter of changing the
// alerts abstraction from a Sink to something more appropriate and hooking it in
// here.
final PeriodicData periodicData = ThreadLocalBuilder.build(
PeriodicData.Builder.class,
b -> b.setData(data.build())
.setDimensions(_key)
.setPeriod(_period)
.setStart(_start));
_sink.recordAggregateData(periodicData);
} finally {
_addCloseLock.writeLock().unlock();
}
} else {
LOGGER.warn()
.setMessage("Bucket closed multiple times")
.addData("bucket", this)
.log();
}
}
/**
* Add data in the form of a Record
to this Bucket
.
*
* @param record The data to add to this Bucket
.
*/
public void add(final Record record) {
for (final Map.Entry entry : record.getMetrics().entrySet()) {
final String name = entry.getKey();
final Metric metric = entry.getValue();
if (metric.getValues().isEmpty()) {
LOGGER.debug()
.setMessage("Discarding metric")
.addData("reason", "no samples")
.addData("name", name)
.addData("metric", metric)
.log();
continue;
}
Collection> calculators = Collections.emptyList();
// First check to see if the user has specified a set of statistics for this metric
final Optional> specifiedStatistics;
try {
specifiedStatistics = _specifiedStatisticsCache.get(name);
} catch (final ExecutionException e) {
throw new RuntimeException(e);
}
if (specifiedStatistics.isPresent()) {
final Optional> dependentStatistics;
try {
dependentStatistics = _dependentStatisticsCache.get(name);
} catch (final ExecutionException e) {
throw new RuntimeException(e);
}
calculators = getOrCreateCalculators(
name,
specifiedStatistics.get(),
dependentStatistics.get(),
_explicitMetricCalculators);
} else {
switch (metric.getType()) {
case COUNTER: {
calculators = getOrCreateCalculators(
name,
_specifiedCounterStatistics,
_dependentCounterStatistics,
_counterMetricCalculators);
break;
}
case GAUGE: {
calculators = getOrCreateCalculators(
name,
_specifiedGaugeStatistics,
_dependentGaugeStatistics,
_gaugeMetricCalculators);
break;
}
case TIMER: {
calculators = getOrCreateCalculators(
name,
_specifiedTimerStatistics,
_dependentTimerStatistics,
_timerMetricCalculators);
break;
}
default:
LOGGER.warn()
.setMessage("Discarding metric")
.addData("reason", "unsupported type")
.addData("name", name)
.addData("metric", metric)
.log();
}
}
addMetric(
name,
metric,
record.getTime(),
calculators);
}
}
public ZonedDateTime getStart() {
return _start;
}
public boolean isOpen() {
return _isOpen.get();
}
/**
* Generate a Steno log compatible representation.
*
* @return Steno log compatible representation.
*/
@LogValue
public Object toLogValue() {
return LogValueMapFactory.builder(this)
.put("isOpen", _isOpen)
.put("sink", _sink)
.put("key", _key)
.put("start", _start)
.put("period", _period)
.put("timerStatistics", _specifiedTimerStatistics)
.put("counterStatistics", _specifiedCounterStatistics)
.put("gaugeStatistics", _specifiedGaugeStatistics)
.put("timerStatistics", _specifiedTimerStatistics)
.put("counterStatistics", _specifiedCounterStatistics)
.put("gaugeStatistics", _specifiedGaugeStatistics)
.build();
}
@Override
public String toString() {
return toLogValue().toString();
}
private void computeStatistics(
final ConcurrentMap>> calculatorsByMetric,
final LoadingCache>> specifiedStatistics,
final ImmutableMultimap.Builder data) {
computeStatistics(calculatorsByMetric, (metric, statistic) -> {
final Optional> stats;
try {
stats = specifiedStatistics.get(metric);
} catch (final ExecutionException e) {
throw new RuntimeException(e);
}
return stats.isPresent() && stats.get().contains(statistic);
}, data);
}
private void computeStatistics(
final ConcurrentMap>> calculatorsByMetric,
final ImmutableSet specifiedStatistics,
final ImmutableMultimap.Builder data) {
computeStatistics(calculatorsByMetric, (metric, statistic) -> specifiedStatistics.contains(statistic), data);
}
private void computeStatistics(
final ConcurrentMap>> calculatorsByMetric,
final BiFunction specified,
final ImmutableMultimap.Builder data) {
for (final Map.Entry>> entry : calculatorsByMetric.entrySet()) {
final String metric = entry.getKey();
final Collection> calculators = entry.getValue();
// Build calculator dependencies for metric
// TODO(vkoskela): This is a waste of time. [NEXT]
// - Single set of calculators per metric and per statistic (no distinction between non-aux and aux)
// - Check the set of statistics to see if the calculator should be published
// - Still need both sets of statistics in order to create new set of calculators
final Map> dependencies = Maps.newHashMap();
Optional> countStatisticCalculator = Optional.empty();
for (final Calculator> calculator : calculators) {
if (COUNT_STATISTIC.equals(calculator.getStatistic())) {
countStatisticCalculator = Optional.of(calculator);
}
dependencies.put(calculator.getStatistic(), calculator);
}
final CalculatedValue> populationSize;
if (countStatisticCalculator.isPresent()) {
populationSize = countStatisticCalculator.get().calculate(dependencies);
} else {
populationSize = ThreadLocalBuilder.<
CalculatedValue
© 2015 - 2025 Weber Informatics LLC | Privacy Policy