/**
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import com.hystrix.junit.HystrixRequestContextRule;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import rx.Observable;
import rx.Subscriber;
import rx.observers.SafeSubscriber;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class HystrixCommandMetricsTest {
@Rule
public HystrixRequestContextRule ctx = new HystrixRequestContextRule();
@Before
public void init() {
HystrixCommandMetrics.reset();
Hystrix.reset();
}
@Test
public void testGetErrorPercentage() {
String key = "cmd-metrics-A";
try {
HystrixCommand<Boolean> cmd1 = new SuccessCommand(key, 1);
HystrixCommandMetrics metrics = cmd1.metrics;
cmd1.execute();
Thread.sleep(100);
assertEquals(0, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1);
cmd2.execute();
Thread.sleep(100);
assertEquals(50, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd3 = new SuccessCommand(key, 1);
HystrixCommand<Boolean> cmd4 = new SuccessCommand(key, 1);
cmd3.execute();
cmd4.execute();
Thread.sleep(100);
assertEquals(25, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd5 = new TimeoutCommand(key);
HystrixCommand<Boolean> cmd6 = new TimeoutCommand(key);
cmd5.execute();
cmd6.execute();
Thread.sleep(100);
assertEquals(50, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd7 = new SuccessCommand(key, 1);
HystrixCommand<Boolean> cmd8 = new SuccessCommand(key, 1);
HystrixCommand<Boolean> cmd9 = new SuccessCommand(key, 1);
cmd7.execute();
cmd8.execute();
cmd9.execute();
// latent
HystrixCommand<Boolean> cmd10 = new SuccessCommand(key, 60);
cmd10.execute();
// 6 success + 1 latent success + 1 failure + 2 timeout = 10 total
// latent success not considered error
// error percentage = 1 failure + 2 timeout / 10
Thread.sleep(100);
assertEquals(30, metrics.getHealthCounts().getErrorPercentage());
} catch (Exception e) {
e.printStackTrace();
fail("Error occurred: " + e.getMessage());
}
}
@Test
public void testBadRequestsDoNotAffectErrorPercentage() {
String key = "cmd-metrics-B";
try {
HystrixCommand<Boolean> cmd1 = new SuccessCommand(key ,1);
HystrixCommandMetrics metrics = cmd1.metrics;
cmd1.execute();
Thread.sleep(100);
assertEquals(0, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd2 = new FailureCommand(key, 1);
cmd2.execute();
Thread.sleep(100);
assertEquals(50, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd3 = new BadRequestCommand(key, 1);
HystrixCommand<Boolean> cmd4 = new BadRequestCommand(key, 1);
try {
cmd3.execute();
} catch (HystrixBadRequestException ex) {
System.out.println("Caught expected HystrixBadRequestException from cmd3");
}
try {
cmd4.execute();
} catch (HystrixBadRequestException ex) {
System.out.println("Caught expected HystrixBadRequestException from cmd4");
}
Thread.sleep(100);
assertEquals(50, metrics.getHealthCounts().getErrorPercentage());
HystrixCommand<Boolean> cmd5 = new FailureCommand(key, 1);
HystrixCommand<Boolean> cmd6 = new FailureCommand(key, 1);
cmd5.execute();
cmd6.execute();
Thread.sleep(100);
assertEquals(75, metrics.getHealthCounts().getErrorPercentage());
} catch (Exception e) {
e.printStackTrace();
fail("Error occurred : " + e.getMessage());
}
}
@Test
public void testCurrentConcurrentExecutionCount() throws InterruptedException {
String key = "cmd-metrics-C";
HystrixCommandMetrics metrics = null;
List<Observable<Boolean>> cmdResults = new ArrayList<Observable<Boolean>>();
int NUM_CMDS = 8;
for (int i = 0; i < NUM_CMDS; i++) {
HystrixCommand<Boolean> cmd = new SuccessCommand(key, 900);
if (metrics == null) {
metrics = cmd.metrics;
}
Observable<Boolean> eagerObservable = cmd.observe();
cmdResults.add(eagerObservable);
}
try {
Thread.sleep(150);
} catch (InterruptedException ie) {
fail(ie.getMessage());
}
System.out.println("ReqLog: " + HystrixRequestLog.getCurrentRequest().getExecutedCommandsAsString());
assertEquals(NUM_CMDS, metrics.getCurrentConcurrentExecutionCount());
final CountDownLatch latch = new CountDownLatch(1);
Observable.merge(cmdResults).subscribe(new Subscriber<Boolean>() {
@Override
public void onCompleted() {
System.out.println("All commands done");
latch.countDown();
}
@Override
public void onError(Throwable e) {
System.out.println("Error duing command execution");
e.printStackTrace();
latch.countDown();
}
@Override
public void onNext(Boolean aBoolean) {
}
});
latch.await(10000, TimeUnit.MILLISECONDS);
assertEquals(0, metrics.getCurrentConcurrentExecutionCount());
}
private class Command extends HystrixCommand<Boolean> {
private final boolean shouldFail;
private final boolean shouldFailWithBadRequest;
private final long latencyToAdd;
public Command(String commandKey, boolean shouldFail, boolean shouldFailWithBadRequest, long latencyToAdd) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("Command"))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
.andCommandPropertiesDefaults(HystrixCommandPropertiesTest.getUnitTestPropertiesSetter()
.withExecutionTimeoutInMilliseconds(1000)
.withCircuitBreakerRequestVolumeThreshold(20)));
this.shouldFail = shouldFail;
this.shouldFailWithBadRequest = shouldFailWithBadRequest;
this.latencyToAdd = latencyToAdd;
}
@Override
protected Boolean run() throws Exception {
Thread.sleep(latencyToAdd);
if (shouldFail) {
throw new RuntimeException("induced failure");
}
if (shouldFailWithBadRequest) {
throw new HystrixBadRequestException("bad request");
}
return true;
}
@Override
protected Boolean getFallback() {
return false;
}
}
private class SuccessCommand extends Command {
SuccessCommand(String commandKey, long latencyToAdd) {
super(commandKey, false, false, latencyToAdd);
}
}
private class FailureCommand extends Command {
FailureCommand(String commandKey, long latencyToAdd) {
super(commandKey, true, false, latencyToAdd);
}
}
private class TimeoutCommand extends Command {
TimeoutCommand(String commandKey) {
super(commandKey, false, false, 2000);
}
}
private class BadRequestCommand extends Command {
BadRequestCommand(String commandKey, long latencyToAdd) {
super(commandKey, false, true, latencyToAdd);
}
}
}