@@ -0,0 +1,149 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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.alibaba.csp.sentinel.node; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowException; | |||
import org.junit.Test; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Random; | |||
import java.util.Set; | |||
import java.util.concurrent.Callable; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import static org.junit.Assert.*; | |||
/** | |||
* Test cases for {@link ClusterNode}. | |||
* | |||
* @author cdfive | |||
* @date 2019-01-11 | |||
*/ | |||
public class ClusterNodeTest { | |||
@Test | |||
public void testGetOrCreateOriginNodeSingleThread() { | |||
ClusterNode clusterNode = new ClusterNode(); | |||
String origin1 = "origin1"; | |||
Node originNode1 = clusterNode.getOrCreateOriginNode(origin1); | |||
assertNotNull(originNode1); | |||
assertEquals(1, clusterNode.getOriginCountMap().size()); | |||
String origin2 = "origin2"; | |||
Node originNode2 = clusterNode.getOrCreateOriginNode(origin2); | |||
assertNotNull(originNode2); | |||
assertEquals(2, clusterNode.getOriginCountMap().size()); | |||
assertNotSame(originNode1, originNode2); | |||
// test same origin, no StatisticNode added into the originCountMap | |||
Node tmpOriginNode = clusterNode.getOrCreateOriginNode(origin1); | |||
assertEquals(2, clusterNode.getOriginCountMap().size()); | |||
assertSame(tmpOriginNode, originNode1); | |||
assertTrue(clusterNode.getOriginCountMap().containsKey(origin1)); | |||
assertTrue(clusterNode.getOriginCountMap().containsKey(origin2)); | |||
} | |||
@Test | |||
public void testGetOrCreateOriginNodeMultiThread() { | |||
// note: in junit4, repeat execute a test method is not very convenient | |||
// for simple, here use a loop instead | |||
// https://stackoverflow.com/questions/1492856/easy-way-of-running-the-same-junit-test-over-and-over | |||
// in junit5, use @RepeatedTest(10) | |||
int testTimes = 10;// execute 10 times, test will have chance to failed, if remove the lock in ClusterNode | |||
for (int times = 0; times < testTimes; times++) { | |||
final ClusterNode clusterNode = new ClusterNode(); | |||
// store all distinct nodes by calling ClusterNode#getOrCreateOriginNode | |||
final Set<Node> createdNodes = new HashSet<Node>(); | |||
final Random random = new Random(); | |||
// 10 threads, 3 origins, 20 tasks(in total, calling 20 times of ClusterNode#getOrCreateOriginNode concurrently) | |||
final ExecutorService es = Executors.newFixedThreadPool(10); | |||
final List<String> origins = Arrays.asList("origin1", "origin2", "origin3"); | |||
int taskCount = 20; | |||
List<Callable<Object>> tasks = new ArrayList<Callable<Object>>(taskCount); | |||
for (int i = 0; i < taskCount; i++) { | |||
tasks.add(new Callable<Object>() { | |||
@Override | |||
public Object call() throws Exception { | |||
// one task call one times of ClusterNode#getOrCreateOriginNode | |||
Node node = clusterNode.getOrCreateOriginNode(origins.get(random.nextInt(origins.size()))); | |||
// add the result node to the createdNodes set | |||
// node: since HashSet is non-threadsafe, synchronized the ClusterNodeTest.class | |||
synchronized (ClusterNodeTest.class) { | |||
createdNodes.add(node); | |||
} | |||
return null; | |||
} | |||
}); | |||
} | |||
try { | |||
es.invokeAll(tasks); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
es.shutdown(); | |||
// origins.size() origins, the same count as the originCountMap | |||
assertEquals(origins.size(), clusterNode.getOriginCountMap().size()); | |||
// not use `assertEquals(origins.size(), createdNodes.size());`, but a compare judgement for debug | |||
if (origins.size() != createdNodes.size()) { | |||
// for debug, we can add a breakpoint here to see the detail info of createdNodes, if remove the lock in ClusterNode | |||
fail("originCountMap's size should " + origins.size() + ", but actual " + createdNodes.size()); | |||
} | |||
// verify originCountMap's key | |||
for (String origin : origins) { | |||
assertTrue(clusterNode.getOriginCountMap().containsKey(origin)); | |||
} | |||
} | |||
} | |||
@Test | |||
public void testTrace() { | |||
ClusterNode clusterNode = new ClusterNode(); | |||
Exception exception = new RuntimeException("test"); | |||
// test count<=0, no exceptionQps added | |||
clusterNode.trace(exception, 0); | |||
clusterNode.trace(exception, -1); | |||
assertEquals(0, clusterNode.exceptionQps()); | |||
assertEquals(0, clusterNode.totalException()); | |||
// test count=1, not BlockException, 1 exceptionQps added | |||
clusterNode.trace(exception, 1); | |||
assertEquals(1, clusterNode.exceptionQps()); | |||
assertEquals(1, clusterNode.totalException()); | |||
// test count=1, BlockException, no exceptionQps added | |||
FlowException flowException = new FlowException("flow"); | |||
clusterNode.trace(flowException, 1); | |||
assertEquals(1, clusterNode.exceptionQps()); | |||
assertEquals(1, clusterNode.totalException()); | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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.alibaba.csp.sentinel.node; | |||
import com.alibaba.csp.sentinel.EntryType; | |||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||
import org.junit.Test; | |||
import static org.junit.Assert.*; | |||
/** | |||
* Test cases for {@link DefaultNodeBuilder}. | |||
* | |||
* @author cdfive | |||
* @date 2019-01-15 | |||
*/ | |||
public class DefaultNodeBuilderTest { | |||
@Test | |||
public void testBuildTreeNode() { | |||
DefaultNodeBuilder builder = new DefaultNodeBuilder(); | |||
ResourceWrapper id = new StringResourceWrapper("resA", EntryType.IN); | |||
ClusterNode clusterNode = new ClusterNode(); | |||
DefaultNode defaultNode = builder.buildTreeNode(id, clusterNode); | |||
assertNotNull(defaultNode); | |||
assertEquals(id, defaultNode.getId()); | |||
assertEquals(clusterNode, defaultNode.getClusterNode()); | |||
// verify each call returns a different instance | |||
DefaultNode defaultNode2 = builder.buildTreeNode(id, clusterNode); | |||
assertNotNull(defaultNode2); | |||
assertNotSame(defaultNode, defaultNode2); | |||
// now DefaultNode#equals(Object) is not implemented, they are not equal | |||
assertNotEquals(defaultNode, defaultNode2); | |||
} | |||
@Test | |||
public void testBuildTreeNode_NullClusterNode() { | |||
DefaultNodeBuilder builder = new DefaultNodeBuilder(); | |||
ResourceWrapper id = new StringResourceWrapper("resA", EntryType.IN); | |||
DefaultNode defaultNode = builder.buildTreeNode(id, null); | |||
assertNotNull(defaultNode); | |||
assertEquals(id, defaultNode.getId()); | |||
assertEquals(null, defaultNode.getClusterNode()); | |||
// verify each call returns a different instance | |||
DefaultNode defaultNode2 = builder.buildTreeNode(id, null); | |||
assertNotNull(defaultNode2); | |||
assertNotSame(defaultNode, defaultNode2); | |||
// now DefaultNode#equals(Object) is not implemented, they are not equal | |||
assertNotEquals(defaultNode, defaultNode2); | |||
} | |||
@Test | |||
public void testBuildClusterNode() { | |||
DefaultNodeBuilder builder = new DefaultNodeBuilder(); | |||
ClusterNode clusterNode = builder.buildClusterNode(); | |||
assertNotNull(clusterNode); | |||
// verify each call returns a different instance | |||
ClusterNode clusterNode2 = builder.buildClusterNode(); | |||
assertNotNull(clusterNode2); | |||
assertNotSame(clusterNode, clusterNode2); | |||
// as new a ClusterNode instance in DefaultNodeBuilder#buildClusterNode(), they are not equal | |||
assertNotEquals(clusterNode, clusterNode2); | |||
} | |||
} |
@@ -0,0 +1,202 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* 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.alibaba.csp.sentinel.node; | |||
import com.alibaba.csp.sentinel.Constants; | |||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||
import org.junit.Test; | |||
import java.text.SimpleDateFormat; | |||
import java.util.ArrayList; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Random; | |||
import java.util.concurrent.Callable; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.TimeUnit; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
/** | |||
* Test cases for {@link StatisticNode}. | |||
* | |||
* @author cdfive | |||
* @date 2019-01-08 | |||
*/ | |||
public class StatisticNodeTest { | |||
private static final String LOG_PREFIX = "[StatisticNodeTest]"; | |||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss"); | |||
private static final Random RANDOM = new Random(); | |||
private static final int THREAD_COUNT = 20; | |||
/** | |||
* A simple test for statistic threadNum and qps by using StatisticNode | |||
* | |||
* <p> | |||
* 20 threads, 30 tasks, every task execute 10 times of bizMethod | |||
* one bizMthod execute within 1 second, and within 0.5 second interval to exceute next bizMthod | |||
* so that the total time cost will be within 1 minute | |||
* </p> | |||
* | |||
* <p> | |||
* Print the statistic info of StatisticNode and verify some results | |||
* </p> | |||
*/ | |||
@Test | |||
public void testStatisticThreadNumAndQps() { | |||
long testStartTime = TimeUtil.currentTimeMillis(); | |||
int taskCount = 30; | |||
int taskBizExecuteCount = 10; | |||
StatisticNode node = new StatisticNode(); | |||
ExecutorService bizEs = Executors.newFixedThreadPool(THREAD_COUNT); | |||
ExecutorService tickEs = Executors.newSingleThreadExecutor(); | |||
tickEs.submit(new TickTask(node)); | |||
List<BizTask> bizTasks = new ArrayList<BizTask>(taskBizExecuteCount); | |||
for (int i = 0; i < taskCount; i++) { | |||
bizTasks.add(new BizTask(node, taskBizExecuteCount)); | |||
} | |||
try { | |||
bizEs.invokeAll(bizTasks); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
log("===================================================="); | |||
log("all biz task done, waiting 3 second to exit"); | |||
sleep(3000); | |||
bizEs.shutdown(); | |||
tickEs.shutdown(); | |||
// now no biz method execute, so there is no curThreadNum,passQps,successQps | |||
assertEquals(0, node.curThreadNum()); | |||
assertEquals(0, node.passQps()); | |||
assertEquals(0, node.successQps()); | |||
// note: total time cost should be controlled within 1 minute, | |||
// as the node.totalRequest() holding statistics of recent 60 seconds | |||
int totalRequest = taskCount * taskBizExecuteCount; | |||
// verify totalRequest | |||
assertEquals(totalRequest, node.totalRequest()); | |||
// as all execute success, totalRequest should equal to totalSuccess | |||
assertEquals(totalRequest, node.totalSuccess()); | |||
// now there are no data in time span, so the minRT should be equals to TIME_DROP_VALVE | |||
assertEquals(node.minRt(), Constants.TIME_DROP_VALVE); | |||
log("===================================================="); | |||
log("testStatisticThreadNumAndQps done, cost " + (TimeUtil.currentTimeMillis() - testStartTime) + "ms"); | |||
} | |||
private static class BizTask implements Callable<Object> { | |||
private StatisticNode node; | |||
private Integer bizExecuteCount; | |||
public BizTask(StatisticNode node, Integer bizExecuteCount) { | |||
this.node = node; | |||
this.bizExecuteCount = bizExecuteCount; | |||
} | |||
@Override | |||
public Object call() throws Exception { | |||
while (true) { | |||
node.increaseThreadNum(); | |||
node.addPassRequest(1); | |||
long startTime = TimeUtil.currentTimeMillis(); | |||
bizMethod(); | |||
node.decreaseThreadNum(); | |||
// decrease one ThreadNum, so curThreadNum should less than THREAD_COUNT | |||
assertTrue(node.curThreadNum() < THREAD_COUNT); | |||
long rt = TimeUtil.currentTimeMillis() - startTime; | |||
node.addRtAndSuccess(rt, 1); | |||
// wait random 0.5 second for simulate method call interval, | |||
// otherwise the curThreadNum will always be THREAD_COUNT at the beginning | |||
sleep(RANDOM.nextInt(500)); | |||
bizExecuteCount--; | |||
if (bizExecuteCount <= 0) { | |||
break; | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
private static void bizMethod() { | |||
// simulate biz method call in random 1 second | |||
sleep(RANDOM.nextInt(1000)); | |||
} | |||
private static class TickTask implements Runnable { | |||
private StatisticNode node; | |||
public TickTask(StatisticNode node) { | |||
this.node = node; | |||
} | |||
@Override | |||
public void run() { | |||
while (true) { | |||
// print statistic info every 1 second | |||
sleep(1000); | |||
// the curThreadNum should not greater than THREAD_COUNT | |||
assertTrue(node.curThreadNum() <= THREAD_COUNT); | |||
logNode(node); | |||
} | |||
} | |||
} | |||
private static void sleep(long ms) { | |||
try { | |||
TimeUnit.MILLISECONDS.sleep(ms); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
private static void logNode(StatisticNode node) { | |||
log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps() | |||
+ ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps() | |||
+ ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess() | |||
+ ",avgRt=" + node.avgRt() + ",minRt=" + node.minRt()); | |||
} | |||
private static void log(Object obj) { | |||
System.out.println(LOG_PREFIX + obj); | |||
} | |||
} |