@@ -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); | |||||
} | |||||
} |