package net.iponweb.disthene.reader.graphite.functions;
import net.iponweb.disthene.reader.beans.TimeSeries;
import net.iponweb.disthene.reader.exceptions.EvaluationException;
import net.iponweb.disthene.reader.exceptions.InvalidArgumentException;
import net.iponweb.disthene.reader.exceptions.TimeSeriesNotAlignedException;
import net.iponweb.disthene.reader.graphite.Target;
import net.iponweb.disthene.reader.graphite.evaluation.TargetEvaluator;
import net.iponweb.disthene.reader.utils.DateTimeUtils;
import net.iponweb.disthene.reader.utils.TimeSeriesUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author Andrei Ivanov
*/
public class HitcountFunction extends DistheneFunction {
public HitcountFunction(String text) {
super(text, "hitcount");
}
@Override
public List<TimeSeries> evaluate(TargetEvaluator evaluator) throws EvaluationException {
// parse interval
int interval = (int) Math.abs(DateTimeUtils.parseTimeOffset((String) arguments.get(1)));
List<TimeSeries> processedArguments = new ArrayList<>();
processedArguments.addAll(evaluator.eval((Target) arguments.get(0)));
if (processedArguments.size() == 0) return new ArrayList<>();
if (!TimeSeriesUtils.checkAlignment(processedArguments)) {
throw new TimeSeriesNotAlignedException();
}
// todo: replicating graphite code below. probably, should be refactored
for (TimeSeries ts : processedArguments) {
Double[] values = ts.getValues();
int bucketCount = (int) Math.ceil((ts.getTo() - ts.getFrom()) / (double) interval);
Double[] buckets = new Double[bucketCount];
// long newStart = ts.getTo() - bucketCount * interval;
long newStart = (ts.getTo() / interval) * interval - (bucketCount - 1) * interval;
for (int i = 0; i < values.length; i++) {
Double value = values[i];
if (value == null) continue;
long startTime = ts.getFrom() + i * ts.getStep();
int startBucket = (int) (((startTime - newStart) - (startTime - newStart) % interval) / interval);
int startMod = (int) ((startTime - newStart) % interval);
long endTime = startTime + ts.getStep();
int endBucket = (int) (((endTime - newStart) - (endTime - newStart) % interval) / interval);
int endMod = (int) ((endTime - newStart) % interval);
if (endBucket >= bucketCount) {
endBucket = bucketCount - 1;
endMod = interval;
}
if (startBucket == endBucket) {
if (startBucket >= 0) {
buckets[startBucket] = buckets[startBucket] != null ? buckets[startBucket] + value * (endMod - startMod) : value * (endMod - startMod);
}
} else {
if (startBucket >= 0) {
buckets[startBucket] = buckets[startBucket] != null ? buckets[startBucket] + value * (interval - startMod) : value * (interval - startMod);
}
Double hitsPerBucket = value * interval;
for (int j = startBucket + 1; j < endBucket; j++) {
buckets[j] = buckets[j] != null ? buckets[j] + hitsPerBucket : hitsPerBucket;
}
if (endMod > 0) {
buckets[endBucket] = buckets[endBucket] != null ? buckets[endBucket] + value * endMod : value * endMod;
}
}
}
ts.setFrom(newStart);
ts.setStep(interval);
ts.setValues(buckets);
ts.setName("hitcount(" + ts.getName() + ",\"" + arguments.get(1) + "\")");
}
return processedArguments;
}
@Override
public void checkArguments() throws InvalidArgumentException {
if (arguments.size() > 3 || arguments.size() < 2)
throw new InvalidArgumentException("hitcount: number of arguments is " + arguments.size() + ". Must be two or three.");
if (!(arguments.get(0) instanceof Target))
throw new InvalidArgumentException("hitcount: argument is " + arguments.get(0).getClass().getName() + ". Must be series");
if (!DateTimeUtils.testTimeOffset((String) arguments.get(1)))
throw new InvalidArgumentException("hitcount: interval cannot be parsed (" + arguments.get(1) + ")");
}
}