/** * Copyright 2015 Netflix, Inc. * * 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 com.netflix.hystrix.perf; import com.netflix.hystrix.HystrixCollapser; import com.netflix.hystrix.HystrixCollapserKey; import com.netflix.hystrix.HystrixCollapserProperties; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; 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.Subscription; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; public class CollapserPerfTest { @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(); } } @State(Scope.Thread) public static class CollapserState { @Param({"1", "10", "100", "1000"}) int numToCollapse; @Param({"1", "1000", "1000000"}) int blackholeConsumption; HystrixRequestContext reqContext; Observable<String> executionHandle; @Setup(Level.Invocation) public void setUp() { reqContext = HystrixRequestContext.initializeContext(); List<Observable<String>> os = new ArrayList<Observable<String>>(); for (int i = 0; i < numToCollapse; i++) { IdentityCollapser collapser = new IdentityCollapser(i, blackholeConsumption); os.add(collapser.observe()); } executionHandle = Observable.merge(os); } @TearDown(Level.Invocation) public void tearDown() { reqContext.shutdown(); } } private static class IdentityCollapser extends HystrixCollapser<List<String>, String, String> { private final int arg; private final int blackholeConsumption; IdentityCollapser(int arg, int blackholeConsumption) { super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("COLLAPSER")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withMaxRequestsInBatch(1000).withTimerDelayInMilliseconds(1))); this.arg = arg; this.blackholeConsumption = blackholeConsumption; } @Override public String getRequestArgument() { return arg + ""; } @Override protected HystrixCommand<List<String>> createCommand(Collection<CollapsedRequest<String, String>> collapsedRequests) { List<String> args = new ArrayList<String>(); for (CollapsedRequest<String, String> collapsedReq: collapsedRequests) { args.add(collapsedReq.getArgument()); } return new BatchCommand(args, blackholeConsumption); } @Override protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, String>> collapsedRequests) { for (CollapsedRequest<String, String> collapsedReq: collapsedRequests) { String requestArg = collapsedReq.getArgument(); String response = "<not found>"; for (String responsePiece: batchResponse) { if (responsePiece.startsWith(requestArg + ":")) { response = responsePiece; break; } } collapsedReq.setResponse(response); } } } private static class BatchCommand extends HystrixCommand<List<String>> { private final List<String> inputArgs; private final int blackholeConsumption; BatchCommand(List<String> inputArgs, int blackholeConsumption) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("PERF")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PERF"))); this.inputArgs = inputArgs; this.blackholeConsumption = blackholeConsumption; } @Override protected List<String> run() throws Exception { Blackhole.consumeCPU(blackholeConsumption); List<String> toReturn = new ArrayList<String>(); for (String inputArg: inputArgs) { toReturn.add(inputArg + ":1"); } return toReturn; } } @Benchmark @BenchmarkMode({Mode.Throughput}) @OutputTimeUnit(TimeUnit.SECONDS) public List<String> observeCollapsedAndWait(CollapserState collapserState, ThreadPoolState threadPoolState) { return collapserState.executionHandle.toList().toBlocking().single(); } }