org.apache.jackrabbit.stats.TimeSeriesRecorder Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.stats;
import static java.lang.Math.round;
import static java.util.Arrays.fill;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
import org.apache.jackrabbit.api.stats.TimeSeries;
/**
* Recorder of a time series. An instance of this class records (and clears)
* the state of a given {@link AtomicLong} counter once every second and
* exposes the collected time series through the {@link TimeSeries}
* interface.
*/
public class TimeSeriesRecorder implements TimeSeries {
/** Value */
private final AtomicLong counter;
/** Whether to reset value each second */
private final boolean resetValueEachSecond;
/** The value used to encode missing values */
private final long missingValue;
/** Measured value per second over the last minute. */
private final long[] valuePerSecond;
/** Measured value per minute over the last hour. */
private final long[] valuePerMinute;
/** Measured value per hour over the last week. */
private final long[] valuePerHour;
/** Measured value per week over the last three years. */
private final long[] valuePerWeek;
/** Current second (index in {@link #valuePerSecond}) */
private int seconds;
/** Current minute (index in {@link #valuePerMinute}) */
private int minutes;
/** Current hour (index in {@link #valuePerHour}) */
private int hours;
/** Current week (index in {@link #valuePerWeek}) */
private int weeks;
public TimeSeriesRecorder(Type type) {
this(type.isResetValueEachSecond());
}
/**
* Same as {@link #TimeSeriesRecorder(boolean, long)} passing long for the 2nd argument
* @param resetValueEachSecond Whether to reset value each second
*/
public TimeSeriesRecorder(boolean resetValueEachSecond) {
this(resetValueEachSecond, 0);
}
/**
* @param resetValueEachSecond Whether to reset value each second
* @param missingValue The value used to encode missing values
*/
public TimeSeriesRecorder(boolean resetValueEachSecond, long missingValue) {
this.resetValueEachSecond = resetValueEachSecond;
this.missingValue = missingValue;
counter = new AtomicLong(missingValue);
valuePerSecond = newArray(60, missingValue);
valuePerMinute = newArray(60, missingValue);
valuePerHour = newArray(7 * 24, missingValue);
valuePerWeek = newArray(3 * 52, missingValue);
}
private static long[] newArray(int size, long value) {
long[] array = new long[size];
fill(array, value);
return array;
}
/**
* Returns the {@link AtomicLong} instance used to measure the value for
* the time series.
*
* @return value
*/
public AtomicLong getCounter() {
return counter;
}
/**
* Records the number of measured values over the past second and resets
* the counter. This method should be scheduled to be called once per
* second.
*/
public synchronized void recordOneSecond() {
if (resetValueEachSecond) {
valuePerSecond[seconds++] = counter.getAndSet(missingValue);
} else {
valuePerSecond[seconds++] = counter.get();
}
if (seconds == valuePerSecond.length) {
seconds = 0;
valuePerMinute[minutes++] = aggregate(valuePerSecond);
}
if (minutes == valuePerMinute.length) {
minutes = 0;
valuePerHour[hours++] = aggregate(valuePerMinute);
}
if (hours == valuePerHour.length) {
hours = 0;
valuePerWeek[weeks++] = aggregate(valuePerHour);
}
if (weeks == valuePerWeek.length) {
weeks = 0;
}
}
//----------------------------------------------------------< TimeSeries >
@Override
public long getMissingValue() {
return missingValue;
}
@Override
public synchronized long[] getValuePerSecond() {
return cyclicCopyFrom(valuePerSecond, seconds);
}
@Override
public synchronized long[] getValuePerMinute() {
return cyclicCopyFrom(valuePerMinute, minutes);
}
@Override
public synchronized long[] getValuePerHour() {
return cyclicCopyFrom(valuePerHour, hours);
}
@Override
public synchronized long[] getValuePerWeek() {
return cyclicCopyFrom(valuePerWeek, weeks);
}
//-------------------------------------------------------------< private >
/**
* Returns the sum of all entries in the given array.
*
* @param array array to be summed
* @return sum of entries
*/
private long aggregate(long[] array) {
long sum = 0;
int count = 0;
for (long value : array) {
if (value != missingValue) {
count++;
sum += value;
}
}
if (count == 0) {
return missingValue;
} else if (resetValueEachSecond) {
return sum;
} else {
return round((double) sum / count);
}
}
/**
* Returns a copy of the given cyclical array, with the element at
* the given position as the first element of the returned array.
*
* @param array cyclical array
* @param pos position of the first element
* @return copy of the array
*/
private static long[] cyclicCopyFrom(long[] array, int pos) {
long[] reverse = new long[array.length];
for (int i = 0; i < array.length; i++) {
reverse[i] = array[(pos + i) % array.length];
}
return reverse;
}
}