
com.arpnetworking.metrics.mad.Bucket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metrics-aggregator-daemon Show documentation
Show all versions of metrics-aggregator-daemon Show documentation
Aggregates samples published by metrics client libraries and writes to destinations.
/*
* 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.AggregatedData;
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.metrics.mad.model.statistics.Accumulator;
import com.arpnetworking.metrics.mad.model.statistics.Calculator;
import com.arpnetworking.metrics.mad.model.statistics.Statistic;
import com.arpnetworking.metrics.mad.model.statistics.StatisticFactory;
import com.arpnetworking.steno.LogValueMapFactory;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
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.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
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.ExecutionException;
import java.util.function.BiFunction;
/**
* Contains samples for a particular aggregation period in time.
*
* @author Ville Koskela (ville dot koskela at inscopemetrics dot io)
*/
/* 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) {
_isOpen = false;
final ImmutableMultimap.Builder data = ImmutableMultimap.builder();
computeStatistics(_counterMetricCalculators, _specifiedCounterStatistics, data);
computeStatistics(_gaugeMetricCalculators, _specifiedGaugeStatistics, data);
computeStatistics(_timerMetricCalculators, _specifiedTimerStatistics, data);
computeStatistics(_explicitMetricCalculators, _specifiedStatisticsCache, data);
final PeriodicData periodicData = ThreadLocalBuilder.build(
PeriodicData.Builder.class,
b -> b.setData(data.build())
.setDimensions(_key)
.setPeriod(_period)
.setStart(_start)
.setMinRequestTime(_minRequestTime.orElse(null)));
_sink.recordAggregateData(periodicData);
} else {
LOGGER.warn()
.setMessage("Bucket closed multiple times")
.addData("bucket", this)
.log();
}
}
/**
* Add data in the form of a {@link Record} to this {@link Bucket}.
*
* @param record The data to add to this {@link Bucket}.
*/
public void add(final Record record) {
if (!_isOpen) {
// TODO(vkoskela): Re-aggregation starts here.
// 1) Send the record back to Aggregator.
// 2) This causes a new bucket to be created for this start+period.
// 3) Enhance aggregation at edges to support re-aggregation (or prevent overwrite).
BUCKET_CLOSED_LOGGER
.warn()
.setMessage("Discarding metric")
.addData("reason", "added after close")
.addData("record", record)
.log();
return;
}
for (final Map.Entry entry : record.getMetrics().entrySet()) {
final String name = entry.getKey();
final Metric metric = entry.getValue();
if (metric.getValues().isEmpty() && metric.getStatistics().isEmpty()) {
LOGGER.debug()
.setMessage("Discarding metric")
.addData("reason", "no samples or statistics")
.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(metric, calculators);
}
updateMinRequestTime(record);
}
private void updateMinRequestTime(final Record record) {
if (record.getRequestTime().isPresent()) {
if (!_minRequestTime.isPresent()) {
_minRequestTime = record.getRequestTime();
}
if (record.getRequestTime().get().isBefore(_minRequestTime.get())) {
_minRequestTime = record.getRequestTime();
}
}
}
public ZonedDateTime getStart() {
return _start;
}
public boolean isOpen() {
return _isOpen;
}
/**
* 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 Map>> 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 Map>> calculatorsByMetric,
final ImmutableSet specifiedStatistics,
final ImmutableMultimap.Builder data) {
computeStatistics(calculatorsByMetric, (metric, statistic) -> specifiedStatistics.contains(statistic), data);
}
private void computeStatistics(
final Map>> 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