package org.radargun.stages.iteration; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.radargun.DistStageAck; import org.radargun.Operation; import org.radargun.StageResult; import org.radargun.config.Property; import org.radargun.config.Stage; import org.radargun.reporting.Report; import org.radargun.stages.test.Invocation; import org.radargun.stages.test.OperationLogic; import org.radargun.stages.test.Stressor; import org.radargun.stages.test.TestStage; import org.radargun.state.SlaveState; import org.radargun.stats.Request; import org.radargun.stats.Statistics; import org.radargun.traits.CacheInformation; import org.radargun.traits.InjectTrait; import org.radargun.traits.Iterable; import org.radargun.utils.Utils; /** * @author Radim Vansa <rvansa@redhat.com> */ @Stage(doc = "Iterates through all entries.") public class IterateStage extends TestStage { @Property(doc = "Full class name of the filter used to iterate through entries. Default is none (accept all).") public String filterClass; @Property(doc = "Parameters for the filter (used to resolve its properties). No defaults.") public String filterParam; @Property(doc = "Full class name of the converter. Default is no converter (Map.Entry<K, V> is returned).") public String converterClass; @Property(doc = "Parameter for the converter (used to resolve its properties). No defaults.") public String converterParam; @Property(doc = "Name of the container (e.g. cache, DB table etc.) that should be iterated. Default is the default container.") public String containerName; @Property(doc = "Number of next() calls that are allowed to fail until we break the loop. Default is 100.") public int maxNextFailures = 100; @Property(doc = "Fail the stage if some of the stressors has failed. Default is true.") public boolean failOnFailedIteration = true; @Property(doc = "Fail when the number of elements iterated is not same. Default is true.") public boolean failOnUnevenElements = true; @Property(doc = "Fail when the number of elements is different than total size. Default is true if filter is not defined and false otherwise.") public Boolean failOnNotTotalSize; @InjectTrait(dependency = InjectTrait.Dependency.MANDATORY) protected Iterable iterable; @InjectTrait protected CacheInformation info; @Override public void init() { super.init(); failOnNotTotalSize = (filterClass == null); } @Override protected DistStageAck newStatisticsAck(List<Stressor> stressors) { List<IterationResult> results = gatherResults(stressors, new IterationResultRetriever()); return new IterationAck(slaveState, results, info != null ? info.getCache(containerName).getTotalSize() : -1); } @Override public StageResult processAckOnMaster(List<DistStageAck> acks) { StageResult result = super.processAckOnMaster(acks); if (result.isError()) return result; Report.Test test = getTest(true); // test already created in super long prevTotalSize = -1; long totalMinElements = -1, totalMaxElements = -1; Map<Integer, Report.SlaveResult> slaveResults = new HashMap<>(); if (test != null) { int testIteration = test.getIterations().size(); String iterationValue = resolveIterationValue(); if (iterationValue != null) { test.setIterationValue(testIteration, iterationValue); } } for (IterationAck ack : instancesOf(acks, IterationAck.class)) { if (test != null) { test.addStatistics(getTestIteration(), ack.getSlaveIndex(), ack.results.stream().map(r -> r.stats).collect(Collectors.toList())); } long slaveMinElements = -1, slaveMaxElements = -1; for (int i = 0; i < ack.results.size(); ++i) { IterationResult sr = ack.results.get(i); if (sr.failed) { result = failOnFailedIteration ? errorResult() : result; log.warnf("Slave %d, stressor %d has failed", ack.getSlaveIndex(), i); } else { if (sr.minElements != sr.maxElements) { log.warnf("Slave %d, stressor %d reports %d .. %d elements", ack.getSlaveIndex(), i, sr.minElements, sr.maxElements); result = failOnUnevenElements ? errorResult() : result; } if (totalMinElements < 0) { totalMinElements = sr.minElements; totalMaxElements = sr.maxElements; } else if (totalMinElements != sr.minElements || totalMaxElements != sr.maxElements) { log.warnf("Previous stressor reported %d .. %d elements but slave %d, stressor %d reports %d .. %d elements", totalMinElements, totalMaxElements, ack.getSlaveIndex(), i, sr.minElements, sr.maxElements); result = failOnUnevenElements ? errorResult() : result; } if (ack.totalSize >= 0 && (sr.minElements != ack.totalSize || sr.maxElements != ack.totalSize)) { log.warnf("Slave %d stressor %d reports %d element but " + "total size is %d", ack.getSlaveIndex(), i, sr.maxElements, ack.totalSize); result = failOnNotTotalSize ? errorResult() : result; } totalMinElements = Math.min(totalMinElements, sr.minElements); totalMaxElements = Math.min(totalMaxElements, sr.maxElements); if (slaveMinElements < 0) { slaveMinElements = sr.minElements; slaveMaxElements = sr.maxElements; } else { slaveMinElements = Math.min(slaveMinElements, sr.minElements); slaveMaxElements = Math.min(slaveMaxElements, sr.maxElements); } } } if (prevTotalSize < 0) prevTotalSize = ack.totalSize; else if (prevTotalSize != ack.totalSize) { log.warnf("Previous total size was %d but slave %d reports total size %d", prevTotalSize, ack.getSlaveIndex(), ack.totalSize); result = failOnNotTotalSize ? errorResult() : result; } slaveResults.put(ack.getSlaveIndex(), new Report.SlaveResult(range(slaveMinElements, slaveMaxElements), slaveMinElements != slaveMaxElements)); } if (test != null) { test.addResult(getTestIteration(), new Report.TestResult("Elements", slaveResults, range(totalMinElements, totalMaxElements), totalMinElements != totalMaxElements)); } return result; } private String range(long min, long max) { return min == max ? String.valueOf(min) : String.format("%d .. %d", min, max); } @Override public OperationLogic getLogic() { return new Logic(); } private class Logic extends OperationLogic { private Iterable.Filter filter; private Iterable.Converter converter; private boolean failed; private long minElements = -1; private long maxElements = -1; @Override public void init(Stressor stressor) { super.init(stressor); if (filterClass != null) { filter = Utils.instantiateAndInit(filterClass, filterParam); } if (converterClass != null) { converter = Utils.instantiateAndInit(converterClass, converterParam); } } @Override public void run(Operation ignored) throws RequestException { Iterable.CloseableIterator iterator; try { iterator = (Iterable.CloseableIterator) stressor.makeRequest(new GetIterator(iterable, containerName, filter, converter)); } catch (Exception e) { log.error("Failed to retrieve iterator.", e); failed = true; return; } int nextFailures = 0; long elements = 0; Request request = stressor.getStats().startRequest(); while (!failed) { try { if (!(boolean) stressor.makeRequest(new HasNext(iterator))) break; } catch (Exception e) { failed = true; log.error("hasNext() failed", e); break; } try { stressor.makeRequest(new Next(iterator)); elements++; } catch (Exception e) { log.error("next() failed", e); nextFailures++; if (nextFailures > maxNextFailures) { failed = true; break; } } } if (!failed) { if (minElements < 0 || elements < minElements) { minElements = elements; } if (maxElements < 0 || elements > maxElements) { maxElements = elements; } } try { iterator.close(); } catch (IOException e) { log.error("Failed to close the iterator", e); failed = true; } request.succeeded(Iterable.FULL_LOOP); } } protected static class GetIterator implements Invocation { private final Iterable iterable; private final String containerName; private final Iterable.Filter filter; private final Iterable.Converter converter; public GetIterator(Iterable iterable, String containerName, Iterable.Filter filter, Iterable.Converter converter) { this.iterable = iterable; this.containerName = containerName; this.filter = filter; this.converter = converter; } @Override public Object invoke() { if (converter == null) { return iterable.getIterator(containerName, filter); } else { return iterable.getIterator(containerName, filter, converter); } } @Override public Operation operation() { return Iterable.GET_ITERATOR; } @Override public Operation txOperation() { return Iterable.GET_ITERATOR; } } protected static class HasNext implements Invocation { private final Iterator iterator; public HasNext(Iterator iterator) { this.iterator = iterator; } @Override public Object invoke() { return iterator.hasNext(); } @Override public Operation operation() { return Iterable.HAS_NEXT; } @Override public Operation txOperation() { return Iterable.HAS_NEXT; } } protected static class Next implements Invocation { private final Iterator iterator; public Next(Iterator iterator) { this.iterator = iterator; } @Override public Object invoke() { return iterator.next(); } @Override public Operation operation() { return Iterable.NEXT; } @Override public Operation txOperation() { return Iterable.NEXT; } } private static class IterationAck extends DistStageAck { private List<IterationResult> results; private long totalSize; public IterationAck(SlaveState slaveState, List<IterationResult> results, long totalSize) { super(slaveState); this.results = results; this.totalSize = totalSize; } } private static class IterationResult implements Serializable { private Statistics stats; private long minElements, maxElements; private boolean failed; private IterationResult(Statistics stats, long minElements, long maxElements, boolean failed) { this.stats = stats; this.minElements = minElements; this.maxElements = maxElements; this.failed = failed; } } private class IterationResultRetriever implements ResultRetriever<IterationResult> { @Override public IterationResult getResult(Stressor stressor) { Logic logic = (Logic) stressor.getLogic(); return new IterationResult(stressor.getStats(), logic.minElements, logic.maxElements, logic.failed); } @Override public IterationResult merge(IterationResult result1, IterationResult result2) { return new IterationResult(Statistics.MERGE.apply(result1.stats, result2.stats), Math.min(result1.minElements, result2.minElements), Math.max(result1.maxElements, result2.maxElements), result1.failed || result2.failed); } } }