
com.tomtom.speedtools.metrics.MetricsCollector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of metrics Show documentation
Show all versions of metrics Show documentation
Provides easy interfaces for external LBS services, like mapping,
geocoding and routing.
/*
* Copyright (C) 2012-2017. TomTom International BV (http://tomtom.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.tomtom.speedtools.metrics;
import com.tomtom.speedtools.metrics.MultiMetricsData.Period;
import com.tomtom.speedtools.time.UTCTime;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Utility class to collect metrics data. It keeps track of a sum, count and average for the given time period. Not all
* values are maintained. Instead, the time period is divided in a number of entries, which constrains memory usage. The
* trade-off for this reduced memory usage is that statistics are not reported for EXACTLY given time period, but
* possibly for a slightly longer period.
*
* This class is thread-safe.
*/
@SuppressWarnings("SynchronizedMethod")
public class MetricsCollector implements MetricsData {
private static final Logger LOG = LoggerFactory.getLogger(MetricsCollector.class);
@Nonnull
private final Duration totalMetricDuration;
@Nonnull
private final Duration timeSlotDuration;
@Nonnull
private final ArrayDeque values;
private float sum;
private int count;
private float sumSquares;
@Nonnull
public static MetricsCollector create(@Nonnull final Period interval) {
assert interval != null;
switch (interval) {
case LAST_MONTH:
return new MetricsCollector(Duration.standardDays(30), 30);
case LAST_WEEK:
return new MetricsCollector(Duration.standardDays(7), 24 * 7);
case LAST_DAY:
return new MetricsCollector(Duration.standardDays(1), 48);
case LAST_HOUR:
return new MetricsCollector(Duration.standardHours(1), 60);
case LAST_MINUTE:
return new MetricsCollector(Duration.standardMinutes(1), 30);
default:
assert false;
break;
}
throw new IllegalStateException();
}
public MetricsCollector(@Nonnull final Duration totalMetricDuration, final int maxEntries) {
assert totalMetricDuration != null;
assert totalMetricDuration.isLongerThan(Duration.millis(1));
assert maxEntries > 0;
this.totalMetricDuration = totalMetricDuration;
this.timeSlotDuration = Duration.millis(totalMetricDuration.getMillis() / maxEntries);
this.values = new ArrayDeque<>();
}
/**
* Retrieve the slots by which the metric is represented internally. Mainly used for serialization.
*
* @return Metric slots.
*/
@Nonnull
public synchronized List getSlots() {
return new ArrayList<>(this.values);
}
/**
* Set the slots by which the metric is represented internally. Existing data will be overwritten. Mainly used for
* serialization.
*
* @param slots Metric slots.
*/
public synchronized void setSlots(@Nonnull final Collection slots) {
assert slots != null;
this.values.clear();
this.sum = 0.0f;
this.count = 0;
this.sumSquares = 0.0f;
for (final MetricsTimeSlot slot : slots) {
this.values.add(slot);
this.sum += slot.getSum();
this.count += slot.getCount();
this.sumSquares += slot.getSumSquares();
}
}
@Nonnull
public Duration getTotalMetricDuration() {
return totalMetricDuration;
}
/**
* Adds a value to the collector.
*
* @param value Value to add.
*/
public synchronized void addValueNow(final float value) {
addValue(value, UTCTime.now());
}
/**
* Adds a value to the collector. Use this method instead of {@link #addValueNow(float)} when now is determined by the
* caller and should be aligned with other now-related functionality. Note that now should denote the current time
* and cannot be an arbitrary time in the past or the future.
*
* @param value Value to add.
* @param now Current time, as determined by the caller.
*/
public synchronized void addValue(final float value, @Nonnull final DateTime now) {
assert now != null;
// Prune old slots.
prune(now);
// Add the initial slot?
if (values.isEmpty()) {
values.add(new MetricsTimeSlot(now, value, value * value, value, value, 1));
} else {
// Add to new or existing slot.
DateTime slotTime = values.getLast().getStartTime();
DateTime slotEndTime = slotTime.plus(timeSlotDuration);
// Skip values that are too old for last slot.
if (now.isBefore(slotTime)) {
//noinspection AccessToStaticFieldLockedOnInstance
LOG.debug("Dropped metric data: {} at {}", value, now);
return;
}
// Append new time slots until now is reached.
while (slotEndTime.isBefore(now)) {
values.add(new MetricsTimeSlot(slotEndTime, 0, 0, 0, 0, 0));
slotTime = slotEndTime;
slotEndTime = slotTime.plus(timeSlotDuration);
}
final MetricsTimeSlot slot = values.removeLast();
values.addLast(slot.addValue(value));
}
this.sum += value;
this.sumSquares += value * value;
this.count += 1;
}
/**
* Return the total number of metric data items added up until now since the duration (approx) of this metric.
*
* @return Total number of data items.
*/
@Override
public synchronized int getCount() {
prune(UTCTime.now());
return count;
}
/**
* Return the sum of all metric data added up until now since the duration (approx) of this metric.
*
* @return Sum of metric data.
*/
@Override
public synchronized float getSum() {
prune(UTCTime.now());
return sum;
}
/**
* Return the average of all metric data added up until now since the duration (approx) of this metric.
*
* @return Average of metric data, or {@link Float#NaN} if no data has been added.
*/
@Override
public synchronized float getAvg() {
prune(UTCTime.now());
if (count == 0) {
return Float.NaN;
}
return sum / (float) count;
}
/**
* Get standard deviation.
*
* @return The the sample standard deviation of all metric data added up until now since the duration (approx) of
* this metric, or {@link Float#NaN} if no data has been added.
*/
@Override
public synchronized float getStdDev() {
prune(UTCTime.now());
if (count == 0) {
return Float.NaN;
}
if (count == 1) {
return 0;
}
assert count >= 2;
final double dividend = (count * sumSquares) - (sum * sum);
final int divider = count * (count - 1);
assert divider != 0;
final double quotient = dividend / divider;
if (quotient < 0) {
return 0;
}
//noinspection NumericCastThatLosesPrecision
return (float) Math.sqrt(quotient);
}
/**
* Get maximum.
*
* @return The maximum value of all metric data added up until now since the duration (approx) of this metric, or
* {@link Float#NaN} if no data has been added.
*/
@Override
public synchronized float getMax() {
prune(UTCTime.now());
float max = Float.NaN;
for (final MetricsTimeSlot value : values) {
if (Float.isNaN(max)) {
max = value.getMax();
} else {
max = Math.max(max, value.getMax());
}
}
return max;
}
/**
* Get minimum.
*
* @return The minimum value of all metric data added up until now since the duration (approx) of this metric, or
* {@link Float#NaN} if no data has been added.
*/
@Override
public synchronized float getMin() {
prune(UTCTime.now());
float min = Float.NaN;
for (final MetricsTimeSlot value : values) {
if (Float.isNaN(min)) {
min = value.getMin();
} else {
min = Math.min(min, value.getMin());
}
}
return min;
}
private void prune(@Nonnull final DateTime now) {
assert now != null;
final DateTime earliest = now.minus(totalMetricDuration);
while (!values.isEmpty() && values.getFirst().getStartTime().isBefore(earliest)) {
final MetricsTimeSlot slot = values.removeFirst();
this.sum -= slot.getSum();
this.count -= slot.getCount();
this.sumSquares -= slot.getSumSquares();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy