package com.bizo.asperatus.tracker.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.amazonaws.services.cloudwatch.AmazonCloudWatch;
import com.amazonaws.services.cloudwatch.model.PutMetricDataRequest;
import com.bizo.asperatus.model.CompoundDimension;
import com.bizo.asperatus.model.Unit;
import com.bizo.asperatus.tracker.AbstractMetricTracker;
import com.bizo.asperatus.tracker.MetricTracker;
import com.bizo.asperatus.tracker.impl.buffer.InMemoryTracker;
import com.bizo.asperatus.tracker.impl.buffer.MetricBuffer;
/**
* MetricTracker implementation that performs local aggregation, then pushes the results to Cloudwatch at a fixed
* interval.
*
* @author larry
*/
public final class CWMetricTracker extends AbstractMetricTracker implements MetricTracker {
private final MetricBuffer buffer = new InMemoryTracker();
private final ScheduledExecutorService executor;
public CWMetricTracker(
final AmazonCloudWatch cloudwatch,
final String namespace,
final ScheduledExecutorService executor,
final long flushDelay,
final TimeUnit flushDelayUnit
) {
this.executor = executor;
executor.scheduleAtFixedRate(
new PushStatsTask(cloudwatch, namespace, buffer, executor),
flushDelay,
flushDelay,
flushDelayUnit);
}
@Override
public void track(final String metricName, final Number value, final Unit unit, final Collection<CompoundDimension> dimensions) {
buffer.track(metricName, value, unit, dimensions);
}
@Override
public void close() {
try {
// TODO: this should really just pull out any pending tasks and execute them....
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
// fall through
}
executor.shutdownNow();
}
private static final class PushStatsTask implements Runnable {
private static final int MAX_PER_REQUEST = 10;
private static final int NUM_RETRIES = 5;
private static final int RETRY_DELAY_SEC = 30;
private final AmazonCloudWatch cloudwatch;
private final MetricBuffer buffer;
private final String namespace;
private final RetryingScheduler scheduler;
private PushStatsTask(
final AmazonCloudWatch cloudwatch,
final String namespace,
final MetricBuffer buffer,
final ScheduledExecutorService executor) {
this.cloudwatch = cloudwatch;
this.namespace = namespace;
this.buffer = buffer;
scheduler = new RetryingScheduler(executor);
}
public void run() {
final Map<MetricKey, MetricStatistics> stats = buffer.reset();
if (stats.isEmpty()) {
return;
}
final List<Aggregation> agg = new ArrayList<Aggregation>();
// batch up cloudwatch requests
for (final Map.Entry<MetricKey, MetricStatistics> me : stats.entrySet()) {
final Aggregation a = toAggregation(me.getKey(), me.getValue());
agg.add(a);
if (agg.size() >= MAX_PER_REQUEST) {
submit(agg);
agg.clear();
}
}
submit(agg);
}
private void submit(final List<Aggregation> agg) {
if (agg.size() > 0) {
final CloudwatchSubmitTask task =
new CloudwatchSubmitTask(cloudwatch, namespace, new ArrayList<Aggregation>(agg));
scheduler.schedule(task, NUM_RETRIES, RETRY_DELAY_SEC);
}
}
private static final Aggregation toAggregation(final MetricKey key, final MetricStatistics val) {
return new Aggregation(
key.getMetricName(),
val.getSamples(),
val.getSum(),
val.getMin(),
val.getMax(),
val.getUnit(),
key.getDimension().getDimensions());
}
}
private static final class CloudwatchSubmitTask implements Callable<Boolean> {
private final AmazonCloudWatch cloudwatch;
private final String namespace;
private final List<Aggregation> agg;
public CloudwatchSubmitTask(final AmazonCloudWatch cloudwatch, final String namespace, final List<Aggregation> agg) {
this.cloudwatch = cloudwatch;
this.namespace = namespace;
this.agg = agg;
}
public Boolean call() throws Exception {
final PutMetricDataRequest request = new PutMetricDataRequest();
request.setNamespace(namespace);
request.setMetricData(AggregationUtils.toMetricDatum(agg));
cloudwatch.putMetricData(request);
return Boolean.TRUE;
}
public String toString() {
return String.format("CloudwatchSubmitTask[ns=%s,agg=%s]", agg, namespace);
}
}
}