/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.test.performance;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import org.apache.brooklyn.util.time.Duration;
import org.apache.commons.io.FileUtils;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
/**
* For building up a description of what to measure.
* <p>
* Users are strongly encouraged to call the setter methods, rather than accessing the fields
* directly. The fields may be made protected in a future release.
* <p>
* The following fields are compulsory:
* <ul>
* <li>{@link #job(Runnable)}
* <li>Exactly one of {@link #duration(Duration)} or {@link #iterations(int)}
* </ul>
*
* See {@link PerformanceTestUtils#run(PerformanceTestDescriptor)}.
*/
@Beta
public class PerformanceTestDescriptor {
public String summary;
public Duration warmup;
public Integer warmupIterations;
public Duration duration;
public Integer iterations;
public Runnable job;
public CountDownLatch completionLatch;
public Duration completionLatchTimeout = Duration.FIVE_MINUTES;
public Double minAcceptablePerSecond;
public Duration sampleCpuInterval = Duration.ONE_SECOND;
public Duration logInterval = Duration.FIVE_SECONDS;
public boolean histogram = true;
public MeasurementResultPersister persister = new FilePersister(new File(FileUtils.getUserDirectory(), "brooklyn-performance"));
public boolean sealed;
public static PerformanceTestDescriptor create() {
return new PerformanceTestDescriptor();
}
public static PerformanceTestDescriptor create(String summary) {
return create().summary(summary);
}
public PerformanceTestDescriptor summary(String val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.summary = val; return this;
}
/**
* The length of time to repeatedly execute the job for, before doing the proper performance
* test. At most one of {@link #warmup(Duration)} or {@link #warmupIterations(int)} should be
* set - if neither is set, the warmup defaults to one tenth of the test duration.
*/
public PerformanceTestDescriptor warmup(Duration val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.warmup = val; return this;
}
/**
* See {@link #warmup(Duration)}.
*/
public PerformanceTestDescriptor warmupIterations(int val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.warmupIterations = val; return this;
}
/**
* The length of time to repeatedly execute the job for, when measuring the performance.
* Exactly one of {@link #duration(Duration)} or {@link #iterations(int)} should be
* set.
*/
public PerformanceTestDescriptor duration(Duration val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.duration = val; return this;
}
/**
* See {@link #duration(Duration)}.
*/
public PerformanceTestDescriptor iterations(int val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.iterations = val; return this;
}
/**
* The job to be repeatedly executed.
*/
public PerformanceTestDescriptor job(Runnable val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.job = val; return this;
}
/**
* If non-null, the performance test will wait for this latch before stopping the timer.
* This is useful for asynchronous work. For example, 1000 iterations of the job might
* be executed that each submits work asynchronously, and then the latch signals when all
* of those 1000 tasks have completed.
*/
public PerformanceTestDescriptor completionLatch(CountDownLatch val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.completionLatch = val; return this;
}
/**
* The maximum length of time to wait for the {@link #completionLatch(CountDownLatch)}, after
* executing the designated number of jobs. If the latch has not completed within this time,
* then the test will fail.
*/
public PerformanceTestDescriptor completionLatchTimeout(Duration val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.completionLatchTimeout = val; return this;
}
/**
* If non-null, the measured jobs-per-second will be compared against this number. If the
* jobs-per-second is not high enough, then the test wil fail.
*/
public PerformanceTestDescriptor minAcceptablePerSecond(Double val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.minAcceptablePerSecond = val; return this;
}
/**
* Whether to collect a histogram of the individual job times. This histogram stores the count
* in buckets (e.g. number of jobs that took 1-2ms, number that took 2-4ms, number that took
* 4-8ms, etc).
*/
public PerformanceTestDescriptor histogram(boolean val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.histogram = val; return this;
}
/**
* How often to log progress (e.g. number of iterations completed so far). If null, then no
* progress will be logged.
*/
public PerformanceTestDescriptor logInterval(Duration val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.logInterval = val; return this;
}
/**
* How often to calculate + record the fraction of CPU being used. If null, then CPU usage
* will not be recorded.
*/
public PerformanceTestDescriptor sampleCpuInterval(Duration val) {
if (sealed) throw new IllegalStateException("Should not modify after sealed (e.g. after use)");
this.sampleCpuInterval = val; return this;
}
public void seal() {
sealed = true;
assertNotNull(job, "Job must be supplied: "+toString());
assertTrue(duration != null ^ iterations != null, "Exactly one of duration or iterations must be set: "+toString());
assertFalse(warmup != null && warmupIterations != null, "At most one of duration and iterations must be set: "+toString());
if (warmup == null && warmupIterations == null) {
if (duration != null) warmup = Duration.millis(duration.toMilliseconds() / 10);
if (iterations != null) warmupIterations = iterations / 10;
}
if (summary == null) {
summary = job.toString();
}
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("summary", summary)
.add("duration", duration)
.add("warmup", warmup)
.add("iterations", iterations)
.add("warmupIterations", warmupIterations)
.add("job", job)
.add("completionLatch", completionLatch)
.add("minAcceptablePerSecond", minAcceptablePerSecond)
.toString();
}
}