org.apache.jackrabbit.oak.benchmark.AbstractTest Maven / Gradle / Ivy
/*
* 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.jackrabbit.oak.benchmark;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.jcr.Credentials;
import javax.jcr.GuestCredentials;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math.stat.descriptive.SynchronizedDescriptiveStatistics;
import org.apache.jackrabbit.oak.benchmark.util.Profiler;
import org.apache.jackrabbit.oak.fixture.RepositoryFixture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract base class for individual performance benchmarks.
*/
public abstract class AbstractTest extends Benchmark implements CSVResultGenerator {
/**
* A random string to guarantee concurrently running tests don't overwrite
* each others changes (for example in a cluster).
*
* The probability of duplicates, for 50 concurrent processes, is less than
* 1 in 1 million.
*/
static final String TEST_ID = Integer.toHexString(new Random().nextInt());
static AtomicInteger nodeNameCounter = new AtomicInteger();
/**
* A node name that is guarantee to be unique within the current JVM.
*/
static String nextNodeName() {
return "n" + Integer.toHexString(nodeNameCounter.getAndIncrement());
}
private static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
private static final long WARMUP = TimeUnit.SECONDS.toMillis(Long.getLong("warmup", 5));
private static final long RUNTIME = TimeUnit.SECONDS.toMillis(Long.getLong("runtime", 60));
private static final boolean PROFILE = Boolean.getBoolean("profile");
private static final Logger LOG = LoggerFactory.getLogger(AbstractTest.class);
private Repository repository;
private Credentials credentials;
private List sessions;
private List threads;
private volatile boolean running;
private Profiler profiler;
private PrintStream out;
/**
*
* used to signal the {@link #runTest(int)} if stop running future test planned or not. If set
* to true, it will exit the loop not performing any more tests.
*
*
*
* useful when the running of the benchmark makes sense for as long as other processes didn't
* complete.
*
*
*
* Set this variable from within the benchmark itself by using {@link #issueHaltRequest(String)}
*
*
*
* it works only for concurrency level of 1 ({@code --concurrency 1} the
* default)
*
*/
private boolean haltRequested;
/**
* If concurrency level is 1 ({@code --concurrency 1}, the default) it will issue a request to
* halt any future runs of a single benchmark. Useful when the benchmark makes sense only if run
* in conjunction of any other parallel operations.
*
* @param message an optional message that can be provided. It will logged at info level.
*/
protected void issueHaltRequest(@Nullable final String message) {
String m = message == null ? "" : message;
LOG.info("halt requested. {}", m);
haltRequested = true;
}
/**
*
* this method will be called during the {@link #tearDown()} before the {@link #afterSuite()}.
* Override it if you have background processes you wish to stop.
*
*
* For example in case of big imports, the suite could be keep running for as long as the import
* is running, even if the tests are actually no longer executed.
*
*/
protected void issueHaltChildThreads() {
}
@Override
public void setPrintStream(PrintStream out) {
this.out = out;
}
protected static int getScale(int def) {
int scale = Integer.getInteger("scale", 0);
if (scale == 0) {
scale = def;
}
return scale;
}
/**
* Prepares this performance benchmark.
*
* @param repository the repository to use
* @param credentials credentials of a user with write access
* @throws Exception if the benchmark can not be prepared
*/
public void setUp(Repository repository, Credentials credentials)
throws Exception {
this.repository = repository;
this.credentials = credentials;
this.sessions = new LinkedList();
this.threads = new LinkedList();
this.running = true;
haltRequested = false;
beforeSuite();
if (PROFILE) {
profiler = new Profiler().startCollecting();
}
}
@Override
public void run(Iterable fixtures) {
run(fixtures, null);
}
@Override
public void run(Iterable fixtures, List concurrencyLevels) {
System.out.format(
"# %-26.26s C min 10%% 50%% 90%% max N%n",
toString());
if (out != null) {
out.format(
"# %-26.26s, C, min, 10%%, 50%%, 90%%, max, N%n",
toString());
}
for (RepositoryFixture fixture : fixtures) {
try {
Repository[] cluster = createRepository(fixture);
try {
runTest(fixture, cluster[0], concurrencyLevels);
} finally {
fixture.tearDownCluster();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void runTest(RepositoryFixture fixture, Repository repository, List concurrencyLevels) throws Exception {
setUp(repository, CREDENTIALS);
try {
// Run a few iterations to warm up the system
long warmupEnd = System.currentTimeMillis() + WARMUP;
boolean stop = false;
while (System.currentTimeMillis() < warmupEnd && !stop) {
if (!stop) {
// we want to execute this at lease once. after that we consider the
// `haltRequested` flag.
stop = haltRequested;
}
execute();
}
if (concurrencyLevels == null || concurrencyLevels.isEmpty()) {
concurrencyLevels = Arrays.asList(1);
}
for (Integer concurrency: concurrencyLevels) {
// Run the test
DescriptiveStatistics statistics = runTest(concurrency);
if (statistics.getN() > 0) {
System.out.format(
"%-28.28s %6d %6.0f %6.0f %6.0f %6.0f %6.0f %6d%n",
fixture.toString(),
concurrency,
statistics.getMin(),
statistics.getPercentile(10.0),
statistics.getPercentile(50.0),
statistics.getPercentile(90.0),
statistics.getMax(),
statistics.getN());
if (out != null) {
out.format(
"%-28.28s, %6d, %6.0f, %6.0f, %6.0f, %6.0f, %6.0f, %6d%n",
fixture.toString(),
concurrency,
statistics.getMin(),
statistics.getPercentile(10.0),
statistics.getPercentile(50.0),
statistics.getPercentile(90.0),
statistics.getMax(),
statistics.getN());
}
}
}
} finally {
tearDown();
}
}
private class Executor extends Thread {
private final SynchronizedDescriptiveStatistics statistics;
private boolean running = true;
private Executor(String name, SynchronizedDescriptiveStatistics statistics) {
super(name);
this.statistics = statistics;
}
@Override
public void run() {
try {
T context = prepareThreadExecutionContext();
try {
while (running) {
statistics.addValue(execute(context));
}
} finally {
disposeThreadExecutionContext(context);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private DescriptiveStatistics runTest(int concurrencyLevel) throws Exception {
final SynchronizedDescriptiveStatistics statistics = new SynchronizedDescriptiveStatistics();
if (concurrencyLevel == 1) {
// Run test iterations, and capture the execution times
long runtimeEnd = System.currentTimeMillis() + RUNTIME;
boolean stop = false;
while (System.currentTimeMillis() < runtimeEnd && !stop) {
if (!stop) {
// we want to execute this at lease once. after that we consider the
// `haltRequested` flag.
stop = haltRequested;
}
statistics.addValue(execute());
}
} else {
List threads = new LinkedList();
for (int n=0; n