package org.radargun.reporting; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.*; import org.radargun.Operation; import org.radargun.config.Cluster; import org.radargun.config.Configuration; import org.radargun.config.Definition; import org.radargun.stats.Statistics; /** * Data collected during scenarion on one configuration * * @author Radim Vansa <rvansa@redhat.com> */ public class Report implements Comparable<Report>, Serializable { /* Configuration part */ private Configuration configuration; private Cluster cluster; /* Scenario */ private List<Stage> stages = new ArrayList<>(); /* Service configurations */ private Map<Integer, Map<String, Properties>> normalizedServiceConfigs = new HashMap<Integer, Map<String, Properties>>(); private Map<Integer, Map<String, byte[]>> originalServiceConfig = new HashMap<Integer, Map<String, byte[]>>(); /* Results part */ private List<Timeline> timelines = new ArrayList<>(); /* Test name - iterations */ private Map<String, Test> tests = new LinkedHashMap<>(); public Report(Configuration configuration, Cluster cluster) { this.configuration = configuration; this.cluster = cluster; this.timelines.add(new Timeline(-1)); } public void addNormalizedServiceConfig(int slaveIndex, Map<String, Properties> serviceConfigs) { normalizedServiceConfigs.put(slaveIndex, serviceConfigs); } public Map<Integer, Map<String, Properties>> getNormalizedServiceConfigs() { return Collections.unmodifiableMap(normalizedServiceConfigs); } public void addOriginalServiceConfig(int slaveIndex, Map<String, byte[]> serviceConfigs) { originalServiceConfig.put(slaveIndex, serviceConfigs); } public Map<Integer, Map<String, byte[]>> getOriginalServiceConfig() { return Collections.unmodifiableMap(originalServiceConfig); } public Stage addStage(String name) { Stage stage = new Stage(name); stages.add(stage); return stage; } public List<Stage> getStages() { return Collections.unmodifiableList(stages); } public void addTimelines(Collection<Timeline> timelines) { this.timelines.addAll(timelines); } public List<Timeline> getTimelines() { return Collections.unmodifiableList(timelines); } public boolean hasTimelineWithValuesOfType(Timeline.Category.Type type) { return timelines.stream().anyMatch(t -> t.containsValuesOfType(type)); } /** * @param testName * @return Existing test or null. */ public Test getTest(String testName) { return tests.get(testName); } /** * @param testName * @param iterationsName What is the changing property in different iterations. If set to null, * the iterations will use just {@link org.radargun.reporting.Report.TestIteration#id} * @param allowExisting Flag whether this method can return existing test. * @return New or existing test (if allowExisting = true), or IllegalArgumentException (if allowExisting = false) */ public Report.Test createTest(String testName, String iterationsName, boolean allowExisting) { Test test = tests.get(testName); if (test != null) { if (allowExisting) { return test; } else { throw new IllegalArgumentException("Test '" + testName + "' is already defined"); } } test = new Test(testName, iterationsName); tests.put(testName, test); return test; } public Configuration getConfiguration() { return configuration; } public Cluster getCluster() { return cluster; } public Collection<Test> getTests() { return tests.values(); } @Override public int compareTo(Report o) { int c = configuration.name.compareTo(o.configuration.name); return c != 0 ? c : cluster.compareTo(o.cluster); } /** * One test can span multiple stages, and consists of one or more * {@link org.radargun.reporting.Report.TestIteration iterations}. * The results from single test should be plotted together in report. */ public class Test implements Serializable { public final String name; public final String iterationsName; private ArrayList<TestIteration> iterations = new ArrayList<TestIteration>(); private Map<String, Set<Operation>> groupOperationsMap; private Test(String name, String iterationsName) { this.name = name; this.iterationsName = iterationsName; } public void setGroupOperationsMap(Map<String, Set<Operation>> groupOperationsMap) { this.groupOperationsMap = groupOperationsMap; } public Map<String, Set<Operation>> getGroupOperationsMap() { return groupOperationsMap; } /** * Set 'description' of iteration - value of the property that is changing * through different iterations of the same test. * * @param iteration Iteration id (numbered from 0). * @param iterationValue String value. Cannot be null. */ public void setIterationValue(int iteration, String iterationValue) { if (iterationValue == null) { throw new IllegalArgumentException("Null iteration value"); } ensureIterations(iteration + 1); TestIteration ti = iterations.get(iteration); if (ti.value != null && !iterationValue.equals(ti.value)) { throw new IllegalStateException("Previous iteration value " + ti.value + ", now it is " + iterationValue); } ti.value = iterationValue; } /** * Set statistics from given slave for given iteration. * @param iteration * @param slaveIndex * @param stats */ public void addStatistics(int iteration, int slaveIndex, List<Statistics> stats) { ensureIterations(iteration + 1); TestIteration ti = iterations.get(iteration); ti.addStatistics(slaveIndex, stats); } /** * Add the result to given iteration. Each iteration can contain only one result with the same name. * @param iteration * @param result */ public void addResult(int iteration, TestResult result) { ensureIterations(iteration + 1); iterations.get(iteration).addResult(result); } private void ensureIterations(int size) { iterations.ensureCapacity(size); for (int i = iterations.size(); i < size; ++i) iterations.add(new TestIteration(this, i)); } public List<TestIteration> getIterations() { return Collections.unmodifiableList(iterations); } public Report getReport() { return Report.this; } } /** * One part of {@link org.radargun.reporting.Report.Test}. Usually, the iteration reflects * results from one stage but one stage can add more iterations. * Each iteration has ID and 'value' - this describes the value that is changing between stages. * This changing property is described in {@link Test#iterationsName}. */ public static class TestIteration implements Serializable { public final Test test; public final int id; private String value; /* Slave index - Statistics from threads */ private Map<Integer, List<Statistics>> statistics = new HashMap<>(); private Map<String, TestResult> results = new TreeMap<>(); private int threadCount; public TestIteration(Test test, int id) { this.test = test; this.id = id; } /** * Add statistics for given slave. * @param slaveIndex * @param slaveStats */ public void addStatistics(int slaveIndex, List<Statistics> slaveStats) { statistics.put(slaveIndex, slaveStats); threadCount += slaveStats.size(); } /** * Add the result. The name must be unique in this iteration. * @param result */ public void addResult(TestResult result) { if (results.containsKey(result.name)) { throw new IllegalStateException("Result '" + result.name + "' already set: " + this.results.get(result.name)); } else { result.setIteration(this); this.results.put(result.name, result); } } public Set<Map.Entry<Integer, List<Statistics>>> getStatistics() { return Collections.unmodifiableSet(statistics.entrySet()); } public List<Statistics> getStatistics(int slaveIndex) { return statistics.get(slaveIndex); } public int getThreadCount() { return threadCount; } public Map<String, TestResult> getResults() { return results == null ? null : Collections.unmodifiableMap(results); } public String getValue() { return value; } } /** * Other data of the test that should be reported but don't contain Operation execution times. */ public static class TestResult implements Serializable { public final String name; public final Map<Integer, SlaveResult> slaveResults; public final String aggregatedValue; public final boolean suspicious; private TestIteration iteration; /** * @param name Name of the result must be unique. * @param slaveResults Results from each slave. * @param aggregatedValue Results from slaves 'merged' together. * @param suspicious Flag that the result is unexpected and should be highlighted in the report. */ public TestResult(String name, Map<Integer, SlaveResult> slaveResults, String aggregatedValue, boolean suspicious) { this.name = name; this.slaveResults = Collections.unmodifiableMap(slaveResults); this.aggregatedValue = aggregatedValue; this.suspicious = suspicious; } private void setIteration(TestIteration iteration) { this.iteration = iteration; } public TestIteration getIteration() { return iteration; } } /** * Result data from single slave. */ public static class SlaveResult implements Serializable { public final String value; public final boolean suspicious; public SlaveResult(String value, boolean suspicious) { this.value = value; this.suspicious = suspicious; } } public static class Stage implements Serializable { private final String name; private final List<Property> properties = new ArrayList<>(); public Stage(String name) { this.name = name; } public String getName() { return name; } public void addProperty(String name, Definition definition, Object value) { properties.add(new Property(name, definition, value)); } public List<Property> getProperties() { return Collections.unmodifiableList(properties); } } public static final class Property implements Serializable { private final String name; private final Definition definition; private final Object value; public Property(String name, Definition definition, Object value) { this.name = name; this.definition = definition; this.value = value; } public String getName() { return name; } public Definition getDefinition() { return definition; } public Object getValue() { return value; } private Object writeReplace() { return new PropertyProxy(name, definition, value); } } private static final class PropertyProxy implements Serializable { private String name; private Definition definition; private Object value; private PropertyProxy(String name, Definition definition, Object value) { this.name = name; this.definition = definition; this.value = value; } private void writeObject(ObjectOutputStream o) throws IOException { o.writeObject(name); o.writeObject(definition); if (value == null) { o.writeObject(null); } else if (value instanceof Serializable) { o.writeObject(value); } else { o.writeObject(String.valueOf(value)); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); definition = (Definition) in.readObject(); value = in.readObject(); } private Object readResolve() { return new Property(name, definition, value); } } }