package org.rhq.server.metrics.aggregation;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.ExecutionInfo;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.exceptions.ReadTimeoutException;
import com.google.common.collect.Iterators;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.rhq.server.metrics.MetricsConfiguration;
import org.rhq.server.metrics.MetricsDAO;
import org.rhq.server.metrics.domain.IndexBucket;
import org.rhq.server.metrics.domain.IndexEntry;
/**
* An iterator for the index with paging support. The iterator will scan through all partitions for a particular date
* range, one page at a time. If a query results in a read timeout, the iterator simply logs the exception and queries
* the next page.
*
* @author John Sanda
*/
public class IndexIterator implements Iterator<IndexEntry> {
private static final Log log = LogFactory.getLog(IndexIterator.class);
private static final ResultSet EMPTY_RESULT_SET = new ResultSet() {
@Override
public ColumnDefinitions getColumnDefinitions() {
return null;
}
@Override
public boolean isExhausted() {
return true;
}
@Override
public Row one() {
return null;
}
@Override
public List<Row> all() {
return Collections.emptyList();
}
@Override
public Iterator<Row> iterator() {
return null;
}
@Override
public ExecutionInfo getExecutionInfo() {
return null;
}
};
private DateTime time;
private Duration duration;
private DateTime endTime;
private IndexBucket bucket;
private MetricsDAO dao;
private Iterator<Row> rowIterator;
private int numPartitions;
private int partition;
private int pageSize;
private int lastScheduleId;
private int rowCount;
/**
*
* @param startTime The start time inclusive of the date range to query
* @param endTime The end time exlusive of the date range to query
* @param bucket Either raw, 1 hour, or 6 hour
* @param dao Used for querying the index
* @param configuration The metrics configuration which provides the total number of partitions and page size
*/
public IndexIterator(DateTime startTime, DateTime endTime, IndexBucket bucket, MetricsDAO dao,
MetricsConfiguration configuration) {
time = startTime;
this.endTime = endTime;
this.bucket = bucket;
this.dao = dao;
this.numPartitions = configuration.getIndexPartitions();
this.pageSize = configuration.getIndexPageSize();
switch (bucket) {
case RAW:
this.duration = configuration.getRawTimeSliceDuration();
break;
case ONE_HOUR:
this.duration = configuration.getOneHourTimeSliceDuration();
break;
default:
this.duration = configuration.getSixHourTimeSliceDuration();
}
loadPage();
}
@Override
public boolean hasNext() {
return rowIterator.hasNext();
}
@Override
public IndexEntry next() {
Row row = rowIterator.next();
lastScheduleId = row.getInt(0);
IndexEntry indexEntry = new IndexEntry(bucket, partition, time.getMillis(), lastScheduleId);
if (!rowIterator.hasNext()) {
loadPage();
}
return indexEntry;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void loadPage() {
if (rowIterator == null) {
nextPage(findIndexEntries());
} else {
if (rowCount < pageSize) {
// When we get here, it means that we have gone through all the pages in
// the current partition; consequently, we query the next partition.
++partition;
nextPage(findIndexEntries());
} else{
// We query the current partition again because there could be more pages.
nextPage(findIndexEntriesAfterScheduleId());
}
}
}
/**
* This method moves to the next page of data if one exists. If the result set is empty, we query the next
* partition. If we have queried the last partition, then we wrap around to the first partition of the next time
* slice. We continue searching for a non-empty result set until we hit endTime.
*/
private void nextPage(ResultSet resultSet) {
ResultSet nextResultSet = resultSet;
while (nextResultSet.isExhausted() && time.isBefore(endTime)) {
if (partition < numPartitions - 1) {
++partition;
} else {
partition = 0;
time = time.plus(duration);
}
nextResultSet = findIndexEntries();
}
if (time.isBefore(endTime)) {
List<Row> rows = nextResultSet.all();
rowCount = rows.size();
rowIterator = rows.iterator();
} else {
rowCount = 0;
rowIterator = Iterators.emptyIterator();
}
}
private ResultSet findIndexEntries() {
try {
return dao.findIndexEntries(bucket, partition, time.getMillis()).get();
} catch (ReadTimeoutException e) {
log.warn("There was a read timeout while querying the index with {bucket: " + bucket + ", partition: " +
partition + ", time: " + time + "}", e);
return EMPTY_RESULT_SET;
}
}
private ResultSet findIndexEntriesAfterScheduleId() {
try {
return dao.findIndexEntries(bucket, partition, time.getMillis(), lastScheduleId).get();
} catch (ReadTimeoutException e) {
log.warn("There was a read timeout while querying the index with {bucket: " + bucket + ", partition: " +
partition + ", time: " + time + ", lastScheduleId: " + lastScheduleId + "}", e);
return EMPTY_RESULT_SET;
}
}
}