/* * -----------------------------------------------------------------------\ * 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; import org.perfcake.common.Period; import org.perfcake.common.PeriodType; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.Serializable; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; /** * Information about the current scenario run. * * @author <a href="mailto:marvenec@gmail.com">Martin Večeřa</a> */ public class RunInfo implements Serializable { private static final long serialVersionUID = 2616572877123413497L; /** * How long is this measurement scheduled to run in milliseconds or iterations. * Another {@link org.perfcake.common.PeriodType Period Types} do not make any sense here. */ private final Period duration; /** * Unix time of the measurement start. If the system clock changes during the run, the results based * on this value are influenced. The iterations however use {@link System#nanoTime()} so there is no * worry. */ private long startTime = -1; /** * Unix time of the measurement end. If the system clock changes during the run, the results based * on this value are influenced. The iterations however use {@link System#nanoTime()} so there is no * worry. */ private long endTime = -1; /** * Number of threads that is currently used to generate the load. */ @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "This is needed for RawReporter not to create huge files. The value of this field is stored separately.") private transient volatile int threads = 1; /** * Number of the last iteration. */ @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "This is needed for RawReporter not to create huge files. The value of this field can be derived.") private final transient AtomicLong iterations = new AtomicLong(0); /** * Tags associated with this measurement run. */ @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "This is needed for RawReporter not to create huge files. The value of this field is stored separately.") private final transient Set<String> tags = new HashSet<>(); /** * The name of the scenario. This can be used in reporting and is filled with scenario file name * by {@link org.perfcake.scenario.ScenarioLoader}. */ private String scenarioName = ""; /** * Creates a new RunInfo. * * @param duration * Target duration of the run (time or iterations) */ public RunInfo(final Period duration) { this.duration = duration; assert duration.getPeriodType() == PeriodType.ITERATION || duration.getPeriodType() == PeriodType.TIME : "Unsupported RunInfo duration set."; } /** * Starts a new measurement run. This resets the iteration counter. */ public void start() { startTime = System.currentTimeMillis(); endTime = -1; iterations.set(0); } /** * Resets this RunInfo to its original state. If there is a running measurement, a new start time is obtained for the measurement * to be able to continue smoothly. */ public void reset() { if (isRunning()) { startTime = System.currentTimeMillis(); } else { startTime = -1; } endTime = -1; iterations.set(0); } /** * Stops the measurement run. */ public void stop() { if (endTime == -1) { endTime = System.currentTimeMillis(); } } /** * Gets the current iteration counter value. * This can be used as an approximate value of passed iterations even though some of them * might still be pending their execution. * * @return Current iteration counter value, -1 if there was no iteration so far. */ public long getIteration() { return iterations.get() - 1; // actual number of iterations is 1 higher as we use getAndIncrement() } /** * Gets the next iteration counter value. * This automatically increases the iteration counter for the next call to obtain a different value. * * @return The next available iteration counter value. */ public long getNextIteration() { return iterations.getAndIncrement(); } /** * Gets the current measurement run time in millisecond. If the system clock changed * during the running measurement, this value will be influenced. * * @return Current measurement run time. */ public long getRunTime() { if (startTime == -1) { return 0; } else if (endTime == -1) { return System.currentTimeMillis() - startTime; } else { return endTime - startTime; } } /** * Gets the current measurement progress in percents. * If the run is limited by time based period and the system clock changed during the measurement, * the result value will be influenced. * * @return Completed percents of the current measurement. */ public double getPercentage() { return getPercentage(getIteration()); } /** * Gets the theoretical measurement progress in percents based on the provided number of passed iterations. * If the run is limited by time based period and the system clock changed during the measurement, * the result value will be influenced. * * @param iteration * The iteration for which we want to know the progress % number compared to the configured period duration. * @return Completed percents of the theoretical measurement state. This cannot be more than 100% no matter what value is provided. */ public double getPercentage(final long iteration) { if (startTime == -1) { return 0d; } double progress; switch (duration.getPeriodType()) { case ITERATION: progress = Math.min(iteration + 1, duration.getPeriod()); // at the beginning, iteration can be -1, and we do not want to report more than 100% break; case TIME: progress = Math.min(getRunTime(), duration.getPeriod()); break; default: throw new IllegalArgumentException("Detected unsupported ReportPeriod type."); } return (progress / duration.getPeriod()) * 100; } /** * Gets Unix time of the measurement start. If the system clock changed during the measurement, * this value will not represent the real start. * * @return Measurement start time. */ public long getStartTime() { return startTime; } /** * Gets Unix time of the measurement end. * * @return Measurement end time. */ public long getEndTime() { return endTime; } /** * Is there a running measurement? This is true if and only if {@link #isStarted()} returns true and we did not reached * the final iterations. * * @return True if the measurement is running. */ public boolean isRunning() { return isStarted() && !reachedLastIteration(); } /** * Was the measurement started? This is true if and only if {@link #start()} has been called * and there has been no call to {@link #stop()} since then. * * @return True if the measurement has been started. */ public boolean isStarted() { return startTime != -1 && endTime == -1; } /** * Returns true if the last iteration has been reached. * * @return True if the last iteration has been reached. */ private boolean reachedLastIteration() { return (duration.getPeriodType().equals(PeriodType.ITERATION) && iterations.get() >= duration.getPeriod()) || (duration.getPeriodType().equals(PeriodType.TIME) && getRunTime() >= duration.getPeriod()); } /** * Gets an unmodifiable set of tags associated with this measurement run. * * @return An unmodifiable set of tags. */ public Set<String> getTags() { return Collections.unmodifiableSet(tags); } /** * Checks for a presence of a given tag. * * @param tag * A tag to be checked. * @return <code>true</code> if the specified tag is set for this run info. */ public boolean hasTag(final String tag) { return tags.contains(tag); } /** * Associates a new tag with this measurement. * * @param tag * A new tag to be associated. */ public void addTag(final String tag) { tags.add(tag); } /** * Adds a set of tags to be associated with the current measurement. * * @param tags * A set of tags to be associated. */ public void addTags(final Set<String> tags) { this.tags.addAll(tags); } /** * Removes a tag from this run. * * @param tag * A tag to be removed. */ public void removeTag(final String tag) { this.tags.remove(tag); } /** * Gets the desired run duration * * @return The run duration. */ public Period getDuration() { return duration; } @Override public String toString() { return String.format("RunInfo [duration=%s, startTime=%d, endTime=%d, iterations=%d, tags=%s, started=%d, running=%d, percentage=%.3f]", duration, startTime, endTime, getIteration(), tags, isStarted() ? 1 : 0, isRunning() ? 1 : 0, getPercentage()); } /** * Returns number of threads that is currently used to generate the load. * * @return The number of threads. */ public int getThreads() { return threads; } /** * Sets the information about the number of threads that is currently used to generate the load. * * @param threads * The number of threads. */ public void setThreads(final int threads) { this.threads = threads; } /** * Gets the current scenario name. This can carry any useful information. Gets filled * by {@link org.perfcake.scenario.ScenarioLoader} to the scenario file name. * * @return The scenario name. */ public String getScenarioName() { return scenarioName; } /** * Sets the current scenario name. This is useful for reporting to refer to the original * scenario file name for example. * * @param scenarioName * The name of current scenario. */ public void setScenarioName(final String scenarioName) { this.scenarioName = scenarioName; } }