/**
* Copyright 2014 Netflix, Inc.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 com.netflix.hystrix.perf;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.hystrix.HystrixThreadPool;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.infra.Blackhole;
import rx.Observable;
import rx.functions.Func0;
import rx.schedulers.Schedulers;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Note that the hystrixExecute test must be run on a forked JVM. Otherwise, the initial properties that get
* set for the command apply to all runs. This would leave the command as THREAD-isolated in all test, for example.
*/
public class CommandExecutionPerfTest {
private static HystrixCommandProperties.Setter threadIsolatedCommandDefaults = HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
.withRequestCacheEnabled(true)
.withRequestLogEnabled(true)
.withCircuitBreakerEnabled(true)
.withCircuitBreakerForceOpen(false);
private static HystrixCommandProperties.Setter threadIsolatedFailFastCommandDefaults = HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
.withRequestCacheEnabled(true)
.withRequestLogEnabled(true)
.withCircuitBreakerEnabled(true)
.withCircuitBreakerForceOpen(true);
private static HystrixCommandProperties.Setter semaphoreIsolatedCommandDefaults = HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
.withRequestCacheEnabled(true)
.withRequestLogEnabled(true)
.withCircuitBreakerEnabled(true)
.withCircuitBreakerForceOpen(false);
private static HystrixCommandProperties.Setter semaphoreIsolatedFailFastCommandDefaults = HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
.withRequestCacheEnabled(true)
.withRequestLogEnabled(true)
.withCircuitBreakerEnabled(true)
.withCircuitBreakerForceOpen(true);
private static HystrixThreadPoolProperties.Setter threadPoolDefaults = HystrixThreadPoolProperties.Setter()
.withCoreSize(100);
private static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("PERF");
private static HystrixCommandProperties.Setter getCommandSetter(HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy, boolean forceOpen) {
switch (isolationStrategy) {
case THREAD:
if (forceOpen) {
return threadIsolatedFailFastCommandDefaults;
} else {
return threadIsolatedCommandDefaults;
}
default:
if (forceOpen) {
return semaphoreIsolatedFailFastCommandDefaults;
} else {
return semaphoreIsolatedCommandDefaults;
}
}
}
@State(Scope.Thread)
public static class BlackholeState {
//amount of "work" to give to CPU
@Param({"1", "100", "10000"})
public int blackholeConsumption;
}
@State(Scope.Thread)
public static class CommandState {
HystrixCommand<Integer> command;
HystrixRequestContext requestContext;
@Param({"true", "false"})
public boolean forceOpen;
@Param({"true", "false"})
public boolean setUpRequestContext;
@Param({"THREAD", "SEMAPHORE"})
public HystrixCommandProperties.ExecutionIsolationStrategy isolationStrategy;
//amount of "work" to give to CPU
@Param({"1", "100", "10000"})
public int blackholeConsumption;
@Setup(Level.Invocation)
public void setUp() {
if (setUpRequestContext) {
requestContext = HystrixRequestContext.initializeContext();
}
command = new HystrixCommand<Integer>(
HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("PERF"))
.andCommandPropertiesDefaults(getCommandSetter(isolationStrategy, forceOpen))
.andThreadPoolPropertiesDefaults(threadPoolDefaults)
) {
@Override
protected Integer run() throws Exception {
Blackhole.consumeCPU(blackholeConsumption);
return 1;
}
@Override
protected Integer getFallback() {
return 2;
}
};
}
@TearDown(Level.Invocation)
public void tearDown() {
if (setUpRequestContext) {
requestContext.shutdown();
}
}
}
@State(Scope.Thread)
public static class ObservableCommandState {
HystrixObservableCommand<Integer> command;
HystrixRequestContext requestContext;
@Param({"true", "false"})
public boolean forceOpen;
@Param({"true", "false"})
public boolean setUpRequestContext;
//amount of "work" to give to CPU
@Param({"1", "100", "10000"})
public int blackholeConsumption;
@Setup(Level.Invocation)
public void setUp() {
if (setUpRequestContext) {
requestContext = HystrixRequestContext.initializeContext();
}
command = new HystrixObservableCommand<Integer>(
HystrixObservableCommand.Setter.withGroupKey(groupKey)
.andCommandPropertiesDefaults(getCommandSetter(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE, forceOpen))
) {
@Override
protected Observable<Integer> construct() {
return Observable.defer(new Func0<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
Blackhole.consumeCPU(blackholeConsumption);
return Observable.just(1);
}
}).subscribeOn(Schedulers.computation());
}
};
}
@TearDown(Level.Invocation)
public void tearDown() {
if (setUpRequestContext) {
requestContext.shutdown();
}
}
}
@State(Scope.Benchmark)
public static class ExecutorState {
ExecutorService executorService;
@Setup
public void setUp() {
executorService = Executors.newFixedThreadPool(100);
}
@TearDown
public void tearDown() {
List<Runnable> runnables = executorService.shutdownNow();
}
}
@State(Scope.Benchmark)
public static class ThreadPoolState {
HystrixThreadPool hystrixThreadPool;
@Setup
public void setUp() {
hystrixThreadPool = new HystrixThreadPool.HystrixThreadPoolDefault(
HystrixThreadPoolKey.Factory.asKey("PERF")
, HystrixThreadPoolProperties.Setter().withCoreSize(100));
}
@TearDown
public void tearDown() {
hystrixThreadPool.getExecutor().shutdownNow();
}
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer baselineExecute(BlackholeState bhState) {
Blackhole.consumeCPU(bhState.blackholeConsumption);
return 1;
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer baselineQueue(ExecutorState state, final BlackholeState bhState) throws InterruptedException, ExecutionException {
try {
return state.executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Blackhole.consumeCPU(bhState.blackholeConsumption);
return 1;
}
}).get();
} catch (Throwable t) {
return 2;
}
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer baselineSyncObserve(final BlackholeState bhState) throws InterruptedException {
Observable<Integer> syncObservable = Observable.defer(new Func0<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
Blackhole.consumeCPU(bhState.blackholeConsumption);
return Observable.just(1);
}
});
try {
return syncObservable.toBlocking().first();
} catch (Throwable t) {
return 2;
}
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer baselineAsyncComputationObserve(final BlackholeState bhState) throws InterruptedException {
Observable<Integer> asyncObservable = Observable.defer(new Func0<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
Blackhole.consumeCPU(bhState.blackholeConsumption);
return Observable.just(1);
}
}).subscribeOn(Schedulers.computation());
try {
return asyncObservable.toBlocking().first();
} catch (Throwable t) {
return 2;
}
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer baselineAsyncCustomThreadPoolObserve(ThreadPoolState state, final BlackholeState bhState) {
Observable<Integer> asyncObservable = Observable.defer(new Func0<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
Blackhole.consumeCPU(bhState.blackholeConsumption);
return Observable.just(1);
}
}).subscribeOn(state.hystrixThreadPool.getScheduler());
try {
return asyncObservable.toBlocking().first();
} catch (Throwable t) {
return 2;
}
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer hystrixExecute(CommandState state) {
return state.command.execute();
}
@Benchmark
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public Integer hystrixObserve(ObservableCommandState state) {
return state.command.observe().toBlocking().first();
}
}