package org.rhq.server.metrics;
import static java.util.Arrays.asList;
import static org.rhq.test.AssertUtils.assertCollectionEqualsNoOrder;
import static org.testng.Assert.assertTrue;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import com.datastax.driver.core.ResultSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.joda.time.DateTime;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.server.metrics.aggregation.AggregationManager;
import org.rhq.server.metrics.domain.AggregateNumericMetric;
import org.rhq.server.metrics.domain.Bucket;
import org.rhq.server.metrics.domain.IndexBucket;
import org.rhq.server.metrics.domain.RawNumericMetric;
/**
* @author John Sanda
*/
public class AggregationTests extends MetricsTest {
private Aggregates schedule1 = new Aggregates();
private Aggregates schedule2 = new Aggregates();
private Aggregates schedule3 = new Aggregates();
private Aggregates schedule4 = new Aggregates();
private Aggregates schedule5 = new Aggregates();
private final int BATCH_SIZE = 10;
private MetricsServer metricsServer;
private DateTimeServiceStub dateTimeService;
private InMemoryMetricsDB testdb;
private AggregationManager aggregationManager;
@BeforeClass
public void setUp() throws Exception {
purgeDB();
schedule1.id = 100;
schedule2.id = 101;
schedule3.id = 131;
schedule4.id = 104;
schedule5.id = 105;
testdb = new InMemoryMetricsDB();
dateTimeService = new DateTimeServiceStub();
dateTimeService.setConfiguration(new MetricsConfiguration());
metricsServer = new MetricsServer();
metricsServer.setConfiguration(new MetricsConfiguration());
metricsServer.setDateTimeService(dateTimeService);
metricsServer.setDAO(dao);
metricsServer.init();
aggregationManager = metricsServer.getAggregationManager();
}
@Override
protected MetricsConfiguration createConfiguration() {
return new MetricsConfiguration()
.setIndexPageSize(5)
.setIndexPartitions(4);
}
// @Test
public void aggregateLate6HourData() throws Exception {
insertRawData(newRawData(hour(6), 200, 15.0));
insertRawData(newRawData(hour(6), 201, 25.0));
dao.insert1HourData(new AggregateNumericMetric(200, Bucket.ONE_HOUR, 10.0, 10.0, 20.0, hour(4).getMillis()));
dao.insert1HourData(new AggregateNumericMetric(200, Bucket.ONE_HOUR, 10.0, 5.0, 10.0, hour(5).getMillis()));
dao.insert1HourData(new AggregateNumericMetric(201, Bucket.ONE_HOUR, 20.0, 20.0, 20.0, hour(5).getMillis()));
dao.updateIndex(IndexBucket.ONE_HOUR, hour(0).getMillis(), 200);
dao.updateIndex(IndexBucket.ONE_HOUR, hour(0).getMillis(), 2001);
assert6HourDataEquals(200, new AggregateNumericMetric(200, Bucket.SIX_HOUR, 10.0, 5.0, 20.0,
hour(0).getMillis()));
}
@Test//(dependsOnMethods = "aggregateLate6HourData")
public void insertRawDataDuringHour16() throws Exception {
dateTimeService.setNow(hour(16).plusMinutes(41));
insertRawData(
newRawData(hour(16).plusMinutes(20), schedule1.id, 3.0),
newRawData(hour(16).plusMinutes(40), schedule1.id, 5.0),
newRawData(hour(16).plusMinutes(15), schedule2.id, 0.0032),
newRawData(hour(16).plusMinutes(30), schedule2.id, 0.104),
newRawData(hour(16).plusMinutes(7), schedule3.id, 3.14)
);
}
@Test(dependsOnMethods = "insertRawDataDuringHour16")
public void runAggregationForHour16() throws Exception {
dateTimeService.setNow(hour(17).plusMinutes(1));
testdb.aggregateRawData(hour(16), hour(17));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(testdb.get1HourData(hour(16)), oneHourData,
"The returned one hour aggregates are wrong");
// verify values in the db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(hour(16), schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(hour(16), schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(hour(16), schedule3.id));
assert6HourDataEmpty(schedule1.id, schedule2.id, schedule3.id);
assert24HourDataEmpty(schedule1.id, schedule2.id, schedule3.id);
assertRawIndexEmpty(hour(16));
assert1HourIndexEquals(hour(12), asList(schedule1.id, schedule2.id, schedule3.id));
assert6HourIndexEmpty(hour(0));
}
@Test(dependsOnMethods = "runAggregationForHour16")
public void insertRawDataDuringHour17() throws Exception {
dateTimeService.setNow(hour(17).plusMinutes(50));
insertRawData(
newRawData(hour(17).plusMinutes(20), schedule1.id, 11.0),
newRawData(hour(17).plusMinutes(40), schedule1.id, 16.0),
newRawData(hour(17).plusMinutes(30), schedule2.id, 0.092),
newRawData(hour(17).plusMinutes(45), schedule2.id, 0.0733)
);
}
@Test(dependsOnMethods = "insertRawDataDuringHour17")
public void runAggregationForHour17() throws Exception {
dateTimeService.setNow(hour(18).plusMinutes(1));
testdb.aggregateRawData(hour(17), hour(18));
testdb.aggregate1HourData(hour(12), hour(18));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(testdb.get1HourData(hour(17)), oneHourData,
"The returned one hour data is wrong");
// verify values in the db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assert6HourDataEquals(schedule1.id, testdb.get6HourData(hour(12), schedule1.id));
assert6HourDataEquals(schedule2.id, testdb.get6HourData(hour(12), schedule2.id));
assert6HourDataEquals(schedule3.id, testdb.get6HourData(hour(12), schedule3.id));
assert24HourDataEmpty(schedule1.id, schedule2.id, schedule3.id);
assert1HourIndexEmpty(hour(12));
assert6HourIndexEquals(hour(0), asList(schedule1.id, schedule2.id, schedule3.id));
}
@Test(dependsOnMethods = "runAggregationForHour17")
public void insertRawDataDuringHour18() throws Exception {
dateTimeService.setNow(hour(18).plusMinutes(50));
insertRawData(
newRawData(hour(18).plusMinutes(20), schedule1.id, 22.0),
newRawData(hour(18).plusMinutes(40), schedule1.id, 26.0),
newRawData(hour(18).plusMinutes(15), schedule2.id, 0.205),
newRawData(hour(18).plusMinutes(15), schedule3.id, 2.42)
);
}
@Test(dependsOnMethods = "insertRawDataDuringHour18")
public void runAggregationForHour18() throws Exception {
dateTimeService.setNow(hour(19).plusMinutes(1));
testdb.aggregateRawData(hour(18), hour(19));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(testdb.get1HourData(hour(18)), oneHourData, "The returned 1 hour data is wrong");
// verify values in db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assert6HourDataEquals(schedule1.id, testdb.get6HourData(schedule1.id));
assert6HourDataEquals(schedule2.id, testdb.get6HourData(schedule2.id));
assert6HourDataEquals(schedule3.id, testdb.get6HourData(schedule3.id));
assert24HourDataEmpty(schedule1.id, schedule2.id, schedule3.id);
assert1HourIndexEquals(hour(18), asList(schedule1.id, schedule2.id, schedule3.id));
assert6HourIndexEquals(hour(0), asList(schedule1.id, schedule2.id, schedule3.id));
}
@Test(dependsOnMethods = "runAggregationForHour18")
public void insertRawDataDuringHour23() throws Exception {
dateTimeService.setNow(hour(23).plusMinutes(50));
insertRawData(
newRawData(hour(23).plusMinutes(25), schedule1.id, 34.0),
newRawData(hour(23).plusMinutes(30), schedule2.id, 0.322)
);
}
@Test(dependsOnMethods = "insertRawDataDuringHour23")
public void runAggregationForHour24() throws Exception {
dateTimeService.setNow(hour(24).plusMinutes(1));
testdb.aggregateRawData(hour(23), hour(24));
testdb.aggregate1HourData(hour(18), hour(24));
testdb.aggregate6HourData(hour(0), hour(24));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(testdb.get1HourData(hour(23)), oneHourData, "The returned 1 hour data is wrong");
// verify values in db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assert6HourDataEquals(schedule1.id, testdb.get6HourData(schedule1.id));
assert6HourDataEquals(schedule2.id, testdb.get6HourData(schedule2.id));
assert6HourDataEquals(schedule3.id, testdb.get6HourData(schedule3.id));
assert24HourDataEquals(schedule1.id, testdb.get24HourData(schedule1.id));
assert24HourDataEquals(schedule2.id, testdb.get24HourData(schedule2.id));
assert24HourDataEquals(schedule3.id, testdb.get24HourData(schedule3.id));
assert1HourIndexEmpty(hour(18));
assert6HourIndexEmpty(hour(0));
}
@Test(dependsOnMethods = "runAggregationForHour24")
public void prepareForLateDataAggregationInSame6HourTimeSlice() throws Exception {
purgeDB();
testdb = new InMemoryMetricsDB();
dateTimeService.setNow(hour(3).plusMinutes(55));
insertRawData(
newRawData(hour(3).plusMinutes(20), schedule1.id, 20),
newRawData(hour(3).plusMinutes(30), schedule1.id, 22),
newRawData(hour(3).plusMinutes(15), schedule2.id, 75),
newRawData(hour(3).plusMinutes(20), schedule2.id, 100)
);
dateTimeService.setNow(hour(4).plusSeconds(5));
aggregationManager.run();
assertRawIndexEmpty(hour(3));
// Insert the "late" data. That is, aggregation has already been run over the time
// time of the data being inserted.
insertRawData(
newRawData(hour(3).plusMinutes(20), schedule3.id, 30),
newRawData(hour(3).plusMinutes(40), schedule3.id, 40)
);
testdb.aggregateRawData(hour(3), hour(4));
}
@Test(dependsOnMethods = "prepareForLateDataAggregationInSame6HourTimeSlice")
public void aggregateLateDataInSame6HourTimeSlice() throws Exception {
dateTimeService.setNow(hour(4).plusMinutes(55));
insertRawData(
newRawData(hour(3).plusMinutes(35), schedule1.id, 20),
newRawData(hour(3).plusMinutes(40), schedule1.id, 30),
newRawData(hour(4).plusMinutes(40), schedule1.id, 27),
newRawData(hour(4).plusMinutes(40), schedule2.id, 321),
newRawData(hour(4).plusMinutes(45), schedule2.id, 333),
newRawData(hour(4).plusMinutes(20), schedule3.id, 50),
newRawData(hour(4).plusMinutes(40), schedule3.id, 60)
);
testdb.aggregateRawData(hour(3), hour(4));
testdb.aggregateRawData(hour(4), hour(5));
dateTimeService.setNow(hour(5).plusSeconds(10));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
Set<AggregateNumericMetric> expected1HourData = ImmutableSet.of(
testdb.get1HourData(hour(3), schedule1.id),
testdb.get1HourData(hour(3), schedule3.id),
testdb.get1HourData(hour(4), schedule1.id),
testdb.get1HourData(hour(4), schedule2.id),
testdb.get1HourData(hour(4), schedule3.id)
);
assertCollectionEqualsNoOrder(expected1HourData, oneHourData, "The returned 1 hour data is wrong");
// verify values in db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assertRawIndexEmpty(hour(4));
assert1HourIndexEquals(hour(0), asList(schedule1.id, schedule2.id, schedule3.id));
assert6HourIndexEmpty(hour(0));
}
@Test(dependsOnMethods = "aggregateLateDataInSame6HourTimeSlice")
public void aggregateLateDuringNext6HourTimeSlice() throws Exception {
// First we need to run aggregation for the 05:00 hour in order to generate the
// necessary 6 hour data
dateTimeService.setNow(hour(6).plusMinutes(1));
aggregationManager.run();
// Next we insert late data
dateTimeService.setNow(hour(6).plusMinutes(55));
insertRawData(
newRawData(hour(5).plusMinutes(10), schedule1.id, 5),
newRawData(hour(5).plusMinutes(15), schedule1.id, 125),
newRawData(hour(5).plusMinutes(20), schedule2.id, 22),
newRawData(hour(5).plusMinutes(40), schedule2.id, 18)
);
testdb.aggregateRawData(hour(5), hour(6));
// now insert data for the time slice to be aggregated
insertRawData(
newRawData(hour(6).plusMinutes(25), schedule1.id, 35),
newRawData(hour(6).plusMinutes(50), schedule1.id, 40),
newRawData(hour(6).plusMinutes(15), schedule2.id, 27),
newRawData(hour(6).plusMinutes(50), schedule2.id, 23),
newRawData(hour(6).plusMinutes(20), schedule3.id, 86.453),
newRawData(hour(6).plusMinutes(40), schedule3.id, 84.77)
);
testdb.aggregateRawData(hour(6), hour(7));
testdb.aggregate1HourData(hour(0), hour(6));
Set<AggregateNumericMetric> expected1HourData = ImmutableSet.of(
testdb.get1HourData(hour(5), schedule1.id),
testdb.get1HourData(hour(5), schedule2.id),
testdb.get1HourData(hour(6), schedule1.id),
testdb.get1HourData(hour(6), schedule2.id),
testdb.get1HourData(hour(6), schedule3.id)
);
dateTimeService.setNow(hour(7).plusSeconds(2));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(expected1HourData, oneHourData, "The returned 1 hour data is wrong");
// verify values in db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assert6HourDataEquals(schedule1.id, testdb.get6HourData(schedule1.id));
assert6HourDataEquals(schedule2.id, testdb.get6HourData(schedule2.id));
assert6HourDataEquals(schedule3.id, testdb.get6HourData(schedule3.id));
assertRawIndexEmpty(hour(5));
assertRawIndexEmpty(hour(6));
assert1HourIndexEmpty(hour(0));
assert1HourIndexEquals(hour(6), asList(schedule1.id, schedule2.id, schedule3.id));
assert6HourIndexEquals(hour(0), asList(schedule1.id, schedule2.id, schedule3.id));
}
@Test(dependsOnMethods = "aggregateLateDuringNext6HourTimeSlice")
public void aggregateLateDataDuringNext24HourTimeSlice() throws Exception {
// Run aggregation to clear out cache entries from the 06:00 - 12:00 time slice
dateTimeService.setNow(hour(12).plusMinutes(1));
aggregationManager.run();
testdb.aggregate1HourData(hour(6), hour(12));
// Run aggregation to clear out cache entries from the 00:00 - 24:00 time slice
dateTimeService.setNow(tomorrow().plusMinutes(1));
aggregationManager.run();
dateTimeService.setNow(tomorrow().plusHours(1).plusMinutes(50));
insertRawData(
newRawData(hour(23).plusMinutes(20), schedule1.id, 750),
newRawData(hour(23).plusMinutes(40), schedule1.id, 230),
newRawData(hour(23).plusMinutes(20), schedule3.id, 15),
newRawData(hour(23).plusMinutes(25), schedule3.id, 325),
newRawData(tomorrow().plusHours(1).plusMinutes(15), schedule1.id, 200),
newRawData(tomorrow().plusHours(1).plusMinutes(10), schedule2.id, 150),
newRawData(tomorrow().plusHours(1).plusMinutes(35), schedule3.id, 475)
);
testdb.aggregateRawData(hour(23), tomorrow());
testdb.aggregate1HourData(hour(18), tomorrow());
testdb.aggregate6HourData(today(), tomorrow());
testdb.aggregateRawData(tomorrow().plusHours(1), tomorrow().plusHours(2));
Set<AggregateNumericMetric> expected1HourData = ImmutableSet.of(
testdb.get1HourData(hour(23), schedule1.id),
testdb.get1HourData(hour(23), schedule3.id),
testdb.get1HourData(tomorrow().plusHours(1), schedule1.id),
testdb.get1HourData(tomorrow().plusHours(1), schedule2.id),
testdb.get1HourData(tomorrow().plusHours(1), schedule3.id)
);
dateTimeService.setNow(tomorrow().plusHours(2).plusSeconds(2));
Set<AggregateNumericMetric> oneHourData = aggregationManager.run();
assertCollectionEqualsNoOrder(expected1HourData, oneHourData, "The returned 1 hour data is wrong");
// verify values in the db
assert1HourDataEquals(schedule1.id, testdb.get1HourData(schedule1.id));
assert1HourDataEquals(schedule2.id, testdb.get1HourData(schedule2.id));
assert1HourDataEquals(schedule3.id, testdb.get1HourData(schedule3.id));
assert24HourDataEquals(schedule1.id, testdb.get24HourData(schedule1.id));
assert1HourIndexEmpty(hour(23));
assert1HourIndexEquals(hour(24), asList(schedule1.id, schedule2.id, schedule3.id));
assert6HourIndexEmpty(hour(0));
assert6HourIndexEmpty(hour(24));
}
// @Test(dependsOnMethods = "runAggregationForHour24")
public void resetDBForFailureScenarios() throws Exception {
purgeDB();
}
// @Test(dependsOnMethods = "resetDBForFailureScenarios")
// public void doNotDeleteCachePartitionOnBatchFailure() throws Exception {
// DateTime time = hour(4).plusMinutes(20);
// insertRawData(hour(4), new MeasurementDataNumeric(time.getMillis(), schedule1.id, 3.0))
// .await("Failed to insert raw data");
//
// TestDAO testDAO = new TestDAO() {
// @Override
// public StorageResultSetFuture insertOneHourDataAsync(int scheduleId, long timestamp, AggregateType type,
// double value) {
// StorageResultSetFuture future = super.insertOneHourDataAsync(scheduleId, timestamp, type, value);
// future.setException(new Exception("An unexpected error occurred while inserting 1 hour data"));
// return future;
// }
// };
//
// AggregationManagerTestStub aggregationManager = new AggregationManagerTestStub(hour(4), testDAO);
// aggregationManager.run();
//
// assertRawCacheEquals(hour(4), startScheduleId(schedule1.id), asList(new RawNumericMetric(schedule1.id,
// time.getMillis(), 3.0)));
// }
private void insertRawData(MeasurementDataNumeric... data) throws Exception {
Set<MeasurementDataNumeric> dataSet = Sets.newHashSet(data);
for (MeasurementDataNumeric datum : data) {
testdb.putRawData(new RawNumericMetric(datum.getScheduleId(), datum.getTimestamp(), datum.getValue()));
}
WaitForRawInserts waitForRawInserts = new WaitForRawInserts(data.length);
metricsServer.addNumericData(dataSet, waitForRawInserts);
waitForRawInserts.await("Failed to insert raw data");
}
private MeasurementDataNumeric newRawData(DateTime timestamp, int scheduleId, double value) {
return new MeasurementDataNumeric(timestamp.getMillis(), scheduleId, value);
}
private int[] scheduleIds(int scheduleId) {
int[] ids = new int[BATCH_SIZE];
int startId = startScheduleId(scheduleId);
for (int i = 0; i < BATCH_SIZE; ++i) {
ids[i] = startId + i;
}
return ids;
}
private class Aggregates {
int id; // schedule id
Map<DateTime, AggregateNumericMetric> oneHourData = new HashMap<DateTime, AggregateNumericMetric>();
Map<DateTime, AggregateNumericMetric> sixHourData = new HashMap<DateTime, AggregateNumericMetric>();
Map<DateTime, AggregateNumericMetric> twentyFourHourData = new HashMap<DateTime, AggregateNumericMetric>();
}
private class FailedStorageResultSetFuture extends StorageResultSetFuture implements ListenableFuture<ResultSet> {
private SettableFuture future;
private Throwable t;
public FailedStorageResultSetFuture(Throwable t) {
super(null, null);
future = SettableFuture.create();
this.t = t;
assertTrue(future.setException(t), "Failed to set exception for future");
}
@Override
public void addListener(Runnable listener, Executor executor) {
future.addListener(listener, executor);
}
@Override
public ResultSet get() {
throw new AssertionError();
}
}
protected void assert1HourDataEquals(int scheduleId, AggregateNumericMetric... expected) {
assertMetricDataEquals(scheduleId, Bucket.ONE_HOUR, asList(expected));
}
protected void assert1HourDataEquals(int scheduleId, List<AggregateNumericMetric> expected) {
assertMetricDataEquals(scheduleId, Bucket.ONE_HOUR, expected);
}
protected void assert6HourDataEquals(int scheduleId, AggregateNumericMetric... expected) {
assertMetricDataEquals(scheduleId, Bucket.SIX_HOUR, asList(expected));
}
protected void assert6HourDataEquals(int scheduleId, List<AggregateNumericMetric> expected) {
assertMetricDataEquals(scheduleId, Bucket.SIX_HOUR, expected);
}
protected void assert24HourDataEquals(int scheduleId, AggregateNumericMetric... expected) {
assertMetricDataEquals(scheduleId, Bucket.TWENTY_FOUR_HOUR, asList(expected));
}
protected void assert24HourDataEquals(int scheduleId, List<AggregateNumericMetric> expected) {
assertMetricDataEquals(scheduleId, Bucket.TWENTY_FOUR_HOUR, expected);
}
}