
org.perfcake.scenario.ReplayResults Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of perfcake Show documentation
Show all versions of perfcake Show documentation
A Lightweight Performance Testing Framework
/*
* -----------------------------------------------------------------------\
* PerfCake
*
* Copyright (C) 2010 - 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -----------------------------------------------------------------------/
*/
package org.perfcake.scenario;
import org.perfcake.RunInfo;
import org.perfcake.common.PeriodType;
import org.perfcake.reporting.MeasurementUnit;
import org.perfcake.reporting.ReportManager;
import org.perfcake.reporting.ReportingException;
import org.perfcake.reporting.destination.Destination;
import org.perfcake.reporting.reporter.RawReporter;
import org.perfcake.reporting.reporter.Reporter;
import org.perfcake.util.Utils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.Closeable;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/**
* Replays the results previously recorded with {@link org.perfcake.reporting.reporter.RawReporter}. The same
* scenario should be used, and its reporting section will be used to configure reporters and destinations.
* Then the normal reporting operation is emulated to achieve repeatable results.
*
* @author Martin Večeřa
*/
public class ReplayResults implements Closeable {
/**
* The logger.
*/
private static final Logger log = LogManager.getLogger(ReplayResults.class);
/**
* Reading the recorded and serialized Measurement Units.
*/
private FileInputStream inputStream;
/**
* Unzipper of the data.
*/
private GZIPInputStream gzip;
/**
* We need to remember when we reported for the last time to emulate time based reporting.
*/
private Map> reportLastTimes = new HashMap<>();
/**
* Link to {@link ReportManager} used to report results.
*/
private ReportManager reportManager;
/**
* Fake information about the run.
*/
private ReplayRunInfo runInfo;
/**
* Artificial test start time.
*/
private long firstTime = -1;
/**
* Artificial test last reporting time.
*/
private long lastTime = -1;
/**
* Gets a new replay facility for the given scenario and data file reported by {@link RawReporter}.
*
* @param scenario
* The scenario from which the reporting configuration will be taken.
* @param rawRecords
* The file wit recorded results.
* @throws IOException
* When it was not possible to read the recorded results.
*/
public ReplayResults(final Scenario scenario, final String rawRecords) throws IOException {
inputStream = new FileInputStream(rawRecords);
gzip = new GZIPInputStream(inputStream);
reportManager = scenario.getReportManager();
reportManager.getReporters().forEach(reporter -> {
if (!(reporter instanceof RawReporter)) { // we do not want to cycle or rewrite the results
final Map lastTimes = new HashMap<>();
reportLastTimes.put(reporter, lastTimes);
reporter.getReportingPeriods().forEach(boundPeriod -> {
lastTimes.put(boundPeriod.getBinding(), 0L);
});
reporter.start();
}
});
Utils.initTimeStamps();
}
/**
* Replays the recorded data through the scenario's reporters. Any instance of {@link RawReporter} is ignored.
*
* @throws IOException
* When there was an error reading the replay data.
*/
public void replay() throws IOException {
try (
final ObjectInputStream ois = new ObjectInputStream(gzip);
) {
final RunInfo originalRunInfo = (RunInfo) ois.readObject();
runInfo = new ReplayRunInfo(originalRunInfo);
reportManager.setRunInfo(runInfo);
try {
while (gzip.available() > 0) {
report(MeasurementUnit.streamIn(ois));
}
} catch (EOFException eof) {
// nothing wrong, we just stop reporting here, gzip actually keeps some excessive buffer at the end
}
resetLastTimes();
publishResults(lastTime);
} catch (ClassNotFoundException cnfe) {
throw new IOException("Unknown class in the recorded data. Make sure all the plugins are loaded that were used during recording: ", cnfe);
}
}
/**
* Reports the recorded {@link MeasurementUnit} through the scenario's reporters. All instances of {@link RawReporter} are ignored.
* Needs to emulate time flow because the replay runs faster than the original data recording.
*
* @param mu
* Thhe {@link MeasurementUnit} to be reported.
*/
private void report(final MeasurementUnit mu) {
runInfo.getNextIteration();
if (firstTime == -1) {
firstTime = mu.getStartTime();
}
runInfo.moveTime(mu);
reportManager.getReporters().forEach(reporter -> {
if (!(reporter instanceof RawReporter)) { // we do not want to cycle or rewrite our results
try {
reporter.report(mu);
} catch (ReportingException e) {
log.warn("Unable to report result: ", e);
}
}
});
lastTime = (mu.getStopTime() - firstTime) / 1_000_000;
publishResults(lastTime);
}
/**
* Takes care of publishing via time-bound destinations. This is only triggered by reading the next {@link MeasurementUnit} from
* the input file. This is a different to real test where we simply report at the given time periods no matter if we have
* any new recorded data or not. In a faster replay mode, this cannot be emulated.
*
* @param currentTime
* Artificial time.
*/
private void publishResults(final long currentTime) {
reportManager.getReporters().forEach(reporter -> {
if (!(reporter instanceof RawReporter)) {
reporter.getReportingPeriods().forEach(boundPeriod -> {
if (boundPeriod.getPeriodType() == PeriodType.TIME) {
if (reportLastTimes.get(reporter).get(boundPeriod.getBinding()) == 0L || reportLastTimes.get(reporter).get(boundPeriod.getBinding()) + boundPeriod.getPeriod() < currentTime) {
reportLastTimes.get(reporter).put(boundPeriod.getBinding(), currentTime);
try {
reporter.publishResult(boundPeriod.getPeriodType(), boundPeriod.getBinding());
} catch (ReportingException e) {
log.warn("Unable to publish result: ", e);
}
}
}
});
}
});
}
/**
* Resets all reporters and remembered reporting periods. This is a callback from the {@link ReplayRunInfo}.
*/
private void resetReporters() {
reportManager.getReporters().stream().filter(reporter -> !(reporter instanceof RawReporter)).forEach(Reporter::reset);
resetLastTimes();
}
/**
* Resets all remembered reporting periods.
*/
private void resetLastTimes() {
reportLastTimes.forEach((reporter, boundPeriod) -> {
boundPeriod.keySet().forEach(destination -> {
boundPeriod.put(destination, 0L);
});
});
}
@Override
public void close() throws IOException {
reportManager.getReporters().stream().filter(reporter -> !(reporter instanceof RawReporter)).forEach(Reporter::stop);
inputStream.close();
}
/**
* A replacement of original {@link RunInfo} that allows us to create a notion of
* artificial time flow and report time bounded results in a faster replay mode.
*/
private class ReplayRunInfo extends RunInfo {
/**
* Current artificial time.
*/
private long time;
/**
* Gets a new replay run information based on the original one stored in the replay data header.
*
* @param originalRunInfo
* The original {@link RunInfo}.
*/
private ReplayRunInfo(final RunInfo originalRunInfo) {
super(originalRunInfo.getDuration());
try {
final Field startTimeField = RunInfo.class.getDeclaredField("startTime");
startTimeField.setAccessible(true);
startTimeField.set(this, originalRunInfo.getStartTime());
final Field endTimeField = RunInfo.class.getDeclaredField("endTime");
endTimeField.setAccessible(true);
endTimeField.set(this, originalRunInfo.getEndTime());
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("Unable to properly configure the replay run information: ", e);
}
}
/**
* Keeps the artificial time clock ticking.
*
* @param mu
* Another {@link MeasurementUnit} to count/estimate the current artificial time.
*/
private void moveTime(final MeasurementUnit mu) {
time = Math.max(time, (mu.getStopTime() - firstTime) / 1_000_000);
}
@Override
public long getRunTime() {
return time; // returns the artificial clock's time
}
@Override
public void reset() {
super.reset();
resetReporters(); // callback to reset remembered reporting time periods
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy