package org.radargun.stages.query; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.radargun.DistStageAck; import org.radargun.Operation; import org.radargun.StageResult; import org.radargun.config.Property; import org.radargun.config.PropertyDelegate; import org.radargun.config.Stage; import org.radargun.reporting.Report; import org.radargun.stages.AbstractDistStage; import org.radargun.state.SlaveState; import org.radargun.stats.BasicOperationStats; import org.radargun.stats.Statistics; import org.radargun.stats.SynchronizedStatistics; import org.radargun.traits.ContinuousQuery; import org.radargun.traits.InjectTrait; import org.radargun.traits.Query; import org.radargun.traits.Queryable; import org.radargun.utils.TimeService; import org.radargun.utils.Timestamped; /** * Registers continuous query for given query and cache. * * @author vjuranek */ @Stage(doc = "Benchmark operations performance with enabled/disabled continuous query.") public class ContinuousQueryStage extends AbstractDistStage { private static final String CQ_TEST_NAME = "ContinuousQueryTest"; @Property(doc = "Name of the test as used for reporting. Default is 'Test'.") public String testName = "Test"; @Property(doc = "Cache name with which continuous query should registered. Default is null, i.e. default cache.") public String cacheName = null; @Property(doc = "If multiple queries are used, specifies, if statistics should be merged in one or each CQ should keep its own statistics. Default it false.") public boolean mergeCq = false; @Property(doc = "Allows to reset statistics at the begining of the stage. Default is false.") public boolean resetStats = false; @Property(doc = "Allows to remove continuous query. Default is false.") public boolean remove = false; @PropertyDelegate public QueryConfiguration query = new QueryConfiguration(); @InjectTrait private ContinuousQuery continuousQueryTrait; @InjectTrait private Queryable queryable; private Map<String, SynchronizedStatistics> statistics; @Override public DistStageAck executeOnSlave() { String statsKey = mergeCq ? CQ_TEST_NAME + ".Stats" : testName + ".Stats"; statistics = (Map<String, SynchronizedStatistics>) slaveState.get(statsKey); if (statistics == null) { statistics = new HashMap<String, SynchronizedStatistics>(); slaveState.put(statsKey, statistics); } if (!statistics.containsKey(statsKey)) { statistics.put(statsKey, new SynchronizedStatistics(new BasicOperationStats())); } else if (resetStats) { statistics.get(statsKey).reset(); } if (!remove) { registerCQ(slaveState); } else { unregisterCQ(slaveState); } return new ContinuousQueryAck(slaveState, statistics.get(statsKey).snapshot(true)); } @Override public StageResult processAckOnMaster(List<DistStageAck> acks) { StageResult result = super.processAckOnMaster(acks); if (result.isError()) return result; String testKey = mergeCq ? CQ_TEST_NAME : testName; Report.Test test = createTest(testKey, null); if (test != null) { int testIteration = (mergeCq || remove) ? 0 : test.getIterations().size(); // when merging or removing CQ, don't consider iterations for (ContinuousQueryAck ack : instancesOf(acks, ContinuousQueryAck.class)) { if (ack.stats != null) test.addStatistics(testIteration, ack.getSlaveIndex(), Collections.singletonList(ack.stats)); } } return StageResult.SUCCESS; } protected Report.Test createTest(String testKey, String iterationName) { if (testKey == null || testKey.isEmpty()) { log.warn("No test name - results are not recorded"); return null; } else { Report report = masterState.getReport(); return report.createTest(testKey, iterationName, true); } } private void registerCQ(SlaveState slaveState) { if ((query.projectionAggregated != null && !query.projectionAggregated.isEmpty()) || (query.groupBy != null && query.groupBy.length != 0) || (query.orderByAggregatedColumns != null && !query.orderByAggregatedColumns.isEmpty())) { throw new IllegalStateException("Aggregations are not supported in continuous queries!"); } Query q = QueryBase.constructBuilder(queryable, query).build(); slaveState.put(ContinuousQuery.QUERY, q); ContinuousQuery.Listener cqListener = new ContinuousQuery.Listener() { private final String statsKey = mergeCq ? CQ_TEST_NAME + ".Stats" : testName + ".Stats"; @Override public void onEntryJoined(Object key, Object value) { record(key, ContinuousQuery.ENTRY_JOINED); log.trace("Entry joined " + key + " -> " + value); } @Override public void onEntryLeft(Object key) { record(key, ContinuousQuery.ENTRY_LEFT); log.trace("Entry left " + key); } private void record(Object key, Operation operation) { if (key instanceof Timestamped) { SynchronizedStatistics stats = statistics.get(statsKey); stats.message() .times(((Timestamped) key).getTimestamp(), TimeService.currentTimeMillis()) .record(operation); } } }; Map<String, ContinuousQuery.ListenerReference> listeners = (Map<String, ContinuousQuery.ListenerReference>) slaveState .get(ContinuousQuery.LISTENERS); if (listeners == null) { listeners = new HashMap<>(); slaveState.put(ContinuousQuery.LISTENERS, listeners); } ContinuousQuery.ListenerReference ref = continuousQueryTrait.createContinuousQuery(cacheName, q, cqListener); listeners.put(testName, ref); } public void unregisterCQ(SlaveState slaveState) { Map<String, ContinuousQuery.ListenerReference> listeners = (Map<String, ContinuousQuery.ListenerReference>) slaveState .get(ContinuousQuery.LISTENERS); if (listeners != null && listeners.containsKey(testName)) { ContinuousQuery.ListenerReference ref = listeners.remove(testName); continuousQueryTrait.removeContinuousQuery(cacheName, ref); } } private static class ContinuousQueryAck extends DistStageAck { public final Statistics stats; public ContinuousQueryAck(SlaveState slaveState, Statistics stats) { super(slaveState); this.stats = stats; } } }