/*
* -----------------------------------------------------------------------\
* 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.reporting;
import org.perfcake.PerfCakeConst;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* A result of the smallest measurement unit - an iteration.
* One should obtain a new instance of a MeasurementUnit using {@link org.perfcake.reporting.ReportManager#newMeasurementUnit()}.
*
* @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a>
*/
public class MeasurementUnit implements Serializable {
private static final long serialVersionUID = 3596375306594505085L;
/**
* Logger.
*/
private static final Logger log = LogManager.getLogger(MeasurementUnit.class);
/**
* Iteration for which this unit was created.
*/
private final long iteration;
/**
* Time when last measurement started. A unit may accumulate more measurements together.
*/
private long startTime = -1;
/**
* Time when last measurement ended.
*/
private long stopTime = -1;
/**
* Total measured time.
*/
private double totalTime = 0;
/**
* Custom results reported by a sender.
*/
private final Map<String, Object> measurementResults = new HashMap<>();
/**
* When the measurement was first started in real time (timestamp value from {@link System#currentTimeMillis()}).
*/
private long timeStarted = -1;
/**
* Failure that happened during processing of this task.
*/
private Exception failure = null;
/**
* Time when the sender request was enqueued. By default we assume this is the creation time.
*/
private long enqueueTime = System.nanoTime();
/**
* Constructor is protected. Use {@link org.perfcake.reporting.ReportManager#newMeasurementUnit()} to obtain a new instance.
*
* @param iteration
* Current iteration number.
*/
protected MeasurementUnit(final long iteration) {
this.iteration = iteration;
measurementResults.put(PerfCakeConst.FAILURES_TAG, 0L);
}
/**
* Appends a custom result.
*
* @param label
* The label of the result.
* @param value
* The value of the result.
*/
public void appendResult(final String label, final Object value) {
measurementResults.put(label, value);
}
/**
* Gets immutable map with all the custom results.
*
* @return An immutable copy of the custom results map.
*/
public Map<String, Object> getResults() {
return Collections.unmodifiableMap(measurementResults);
}
/**
* Gets a custom result for the given label.
*
* @param label
* The label of the custom result.
* @return The value for the given custom result.
*/
public Object getResult(final String label) {
return measurementResults.get(label);
}
/**
* Starts measuring. This is independent on current system time.
*/
public void startMeasure() {
timeStarted = System.currentTimeMillis();
startTime = System.nanoTime();
stopTime = -1;
}
/**
* Stops measuring.
*/
public void stopMeasure() {
stopTime = System.nanoTime();
totalTime = totalTime + getLastTime();
}
/**
* Gets total time measured during all measurements done by this Measurement Unit (all time periods between calls to {@link #startMeasure()} and {@link #stopMeasure()} in milliseconds.
*
* @return The total time measured by this unit in milliseconds.
*/
public double getTotalTime() {
return totalTime;
}
/**
* Gets the start time of the measurement in nanoseconds.
*
* @return The start time of the measurement in nanoseconds.
*/
public long getStartTime() {
return startTime;
}
/**
* Gets the stop time of the measurement in nanoseconds.
*
* @return The stop time of the measurement in nanoseconds.
*/
public long getStopTime() {
return stopTime;
}
/**
* Gets time of the last measurement (time period between calls to {@link #startMeasure()} and {@link #stopMeasure()} in milliseconds.
*
* @return Time of the last measurement in milliseconds, -1 when there was no measurement recorded yet.
*/
public double getLastTime() {
if (startTime == -1 || stopTime == -1) {
return -1;
}
if (stopTime - startTime == 0) {
log.warn("Zero time measured! PerfCake is probably running on a machine where the internal timer does not provide enough resolution (e.g. a virtual machine). "
+ "Please refer to the Troubleshooting section in the User Guide.\nCurrent measurement unit: " + this.toString());
}
return (stopTime - startTime) / 1_000_000d;
}
/**
* Gets the total service time between enqueuing the sender task and its completion.
*
* @return The total service time between enqueuing the sender task and its completion, -1 when there was no measurement recorded yet.
*/
public double getServiceTime() {
if (startTime == -1 || stopTime == -1) {
return -1;
}
return (stopTime - enqueueTime) / 1_000_000d;
}
/**
* Checks whether this measurement unit was first started after the specified time (Unix time in millis).
*
* @param ref
* The reference time to compare to the start of the measurement.
* @return <code>true</code> if this measurement unit was first started after the specified reference time.
*/
public boolean startedAfter(final long ref) {
return timeStarted > ref;
}
/**
* Gets the number of current iteration of this Measurement Unit.
*
* @return The number of iteration.
*/
public long getIteration() {
return iteration;
}
/**
* Gets the failure that happened during processing of this task.
*
* @return The exception that occurred or null if there was no exception.
*/
public Exception getFailure() {
return failure;
}
/**
* Sets the exception that happened during processing of this task to be remembered and reported.
*
* @param failure
* The exception that happened or null to clear the failure flag.
*/
public void setFailure(final Exception failure) {
if (failure != null) {
measurementResults.put(PerfCakeConst.FAILURES_TAG, 1L);
} else {
measurementResults.put(PerfCakeConst.FAILURES_TAG, 0L);
}
this.failure = failure;
}
/**
* Gets the time when the current sender request was enqueued.
*
* @return The time when the current sender request was enqueued.
*/
public long getEnqueueTime() {
return enqueueTime;
}
/**
* Sets the time when the current sender request was enqueued.
*
* @param enqueueTime
* The time when the current sender request was enqueued.
*/
public void setEnqueueTime(final long enqueueTime) {
this.enqueueTime = enqueueTime;
}
@Override
public int hashCode() {
int result;
result = (int) (iteration ^ (iteration >>> 32));
result = 31 * result + (int) (startTime ^ (startTime >>> 32));
result = 31 * result + (int) (stopTime ^ (stopTime >>> 32));
long temp = Double.doubleToLongBits(totalTime);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + measurementResults.hashCode();
result = 31 * result + (int) (timeStarted ^ (timeStarted >>> 32));
result = 31 * result + (int) (enqueueTime ^ (enqueueTime >>> 32));
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final MeasurementUnit that = (MeasurementUnit) obj;
if (iteration != that.iteration) {
return false;
}
if (startTime != that.startTime) {
return false;
}
if (stopTime != that.stopTime) {
return false;
}
if (timeStarted != that.timeStarted) {
return false;
}
if (enqueueTime != that.enqueueTime) {
return false;
}
if (Double.compare(that.totalTime, totalTime) != 0) {
return false;
}
if (!measurementResults.equals(that.measurementResults)) {
return false;
}
return true;
}
@Override
public String toString() {
return "MeasurementUnit ["
+ "iteration=" + iteration
+ ", enqueueTime=" + enqueueTime
+ ", startTime=" + startTime
+ ", stopTime=" + stopTime
+ ", totalTime=" + totalTime
+ ", measurementResults=" + measurementResults
+ ", timeStarted=" + timeStarted
+ ']';
}
/**
* Writes this instance into output stream with a minimum data needed. This serves for easy transfer of serialized Mesurement Units.
*
* @param oos
* Stream to write the data to.
* @throws IOException
* When there was an error writing the data.
*/
public void streamOut(ObjectOutputStream oos) throws IOException {
oos.writeLong(iteration);
oos.writeLong(startTime);
oos.writeLong(stopTime);
oos.writeLong(timeStarted);
oos.writeLong(enqueueTime);
oos.writeDouble(totalTime);
// write FAILURES_TAG
final Long failures = (Long) measurementResults.get(PerfCakeConst.FAILURES_TAG);
oos.writeLong(failures == null ? 0 : failures);
// write THREADS_TAG
final Integer threads = (Integer) measurementResults.get(PerfCakeConst.THREADS_TAG);
oos.writeInt(threads == null ? 0 : threads);
// write REQUEST_SIZE_TAG
final Long requestSize = (Long) measurementResults.get(PerfCakeConst.REQUEST_SIZE_TAG);
oos.writeLong(requestSize == null ? 0 : requestSize);
// write RESPONSE_SIZE_TAG
final Long responseSize = (Long) measurementResults.get(PerfCakeConst.RESPONSE_SIZE_TAG);
oos.writeLong(responseSize == null ? 0 : responseSize);
// write all results from the map except for FAILURES_TAG and properties under ATTRIBUTES_TAG
int size = measurementResults.size() - (measurementResults.get(PerfCakeConst.ATTRIBUTES_TAG) != null ? 1 : 0)
- (failures != null ? 1 : 0) - (threads != null ? 1 : 0) - (requestSize != null ? 1 : 0) - (responseSize != null ? 1 : 0);
oos.writeInt(size);
measurementResults.forEach((key, value) -> {
if (!PerfCakeConst.ATTRIBUTES_TAG.equals(key) && !PerfCakeConst.FAILURES_TAG.equals(key) && !PerfCakeConst.THREADS_TAG.equals(key)
&& !PerfCakeConst.REQUEST_SIZE_TAG.equals(key) && !PerfCakeConst.RESPONSE_SIZE_TAG.equals(key)) {
try {
oos.writeUTF(key);
oos.writeObject(value);
} catch (IOException e) {
log.warn("Unable to serialize Measurement Unit: ", e);
}
}
});
// write properties under ATTRIBUTES_TAG
final Properties props = (Properties) measurementResults.get(PerfCakeConst.ATTRIBUTES_TAG);
if (props == null) {
oos.writeLong(0);
oos.writeInt(0);
} else {
//write iteration number in the attributes
final String iteration = props.getProperty(PerfCakeConst.ITERATION_NUMBER_PROPERTY);
oos.writeLong(iteration == null ? 0 : Long.parseLong(iteration));
// write all remaining attributes
oos.writeInt(props.size() - (iteration != null ? 1 : 0));
props.forEach((key, value) -> {
if (!PerfCakeConst.ITERATION_NUMBER_PROPERTY.equals(key)) {
try {
oos.writeUTF((String) key);
oos.writeUTF((String) value);
} catch (IOException e) {
log.warn("Unable to serialize Measurement Unit: ", e);
}
}
});
}
// write exception if it is stored
oos.writeInt(failure == null ? 0 : 1);
if (failure != null) {
oos.writeObject(failure);
}
}
/**
* Reads the minimalistic serialization of Measurement Unit from the input stream.
*
* @param in
* The stream to read the object data from.
* @return The deserialized {@link MeasurementUnit}.
* @throws ClassNotFoundException
* When it is not possible to restore an unknown class from the data.
* @throws IOException
* When there was an I/O error reading data.
*/
public static MeasurementUnit streamIn(ObjectInputStream in) throws ClassNotFoundException, IOException {
final MeasurementUnit mu = new MeasurementUnit(in.readLong());
mu.startTime = in.readLong();
mu.stopTime = in.readLong();
mu.timeStarted = in.readLong();
mu.enqueueTime = in.readLong();
mu.totalTime = in.readDouble();
mu.measurementResults.put(PerfCakeConst.FAILURES_TAG, in.readLong());
mu.measurementResults.put(PerfCakeConst.THREADS_TAG, in.readInt());
mu.measurementResults.put(PerfCakeConst.REQUEST_SIZE_TAG, in.readLong());
mu.measurementResults.put(PerfCakeConst.RESPONSE_SIZE_TAG, in.readLong());
int size = in.readInt();
for (int i = 0; i < size; i++) {
mu.measurementResults.put(in.readUTF(), in.readObject());
}
final Properties props = new Properties();
mu.measurementResults.put(PerfCakeConst.ATTRIBUTES_TAG, props);
props.setProperty(PerfCakeConst.ITERATION_NUMBER_PROPERTY, String.valueOf(in.readLong()));
size = in.readInt();
for (int i = 0; i < size; i++) {
props.setProperty(in.readUTF(), in.readUTF());
}
final int isFailure = in.readInt();
if (isFailure > 0) {
mu.failure = (Exception) in.readObject();
}
return mu;
}
}