- Add callback registry for statistic slot for extensions. - Add a new module `sentinel-parameter-flow-control` under `sentinel-extension`. - Add a `CacheMap` interface to provide abstraction for cache. We use ConcurrentLinkedHashMap as the default implementation (LRU strategy).. - Add a `ParameterMetric` class as frequent parameter metrics for a specific resource. The metric map is located in `ParamFlowSlot` rather than `ClusterNode`. - Implement `ParameterLeapArray` as statistic data structure for frequent parameters in a period of time window. - Add `ParamFlowSlot` as the checker slot; Add `ParamFlowChecker` to do rule checking; Add `ParamFlowRuleManager` to do rule managing. - The statistic metrics for frequent parameters is enabled only if the related resource has configured parameter flow rule; Parameter metrics for removed rules will be cleared automatically. - Leverage extensible `SlotChainBuilder` to provide a `HotParamSlotChainBuilder`. - Add command handlers for hot param rules. - Add test cases and demo.master
@@ -87,6 +87,11 @@ | |||||
<artifactId>sentinel-annotation-aspectj</artifactId> | <artifactId>sentinel-annotation-aspectj</artifactId> | ||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>com.alibaba.csp</groupId> | <groupId>com.alibaba.csp</groupId> | ||||
<artifactId>sentinel-datasource-extension</artifactId> | <artifactId>sentinel-datasource-extension</artifactId> | ||||
@@ -112,6 +117,11 @@ | |||||
<artifactId>sentinel-transport-simple-http</artifactId> | <artifactId>sentinel-transport-simple-http</artifactId> | ||||
<version>${project.version}</version> | <version>${project.version}</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-transport-common</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>com.alibaba.csp</groupId> | <groupId>com.alibaba.csp</groupId> | ||||
<artifactId>sentinel-adapter</artifactId> | <artifactId>sentinel-adapter</artifactId> | ||||
@@ -34,7 +34,7 @@ public interface ProcessorSlot<T> { | |||||
* @param param Generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node} | * @param param Generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node} | ||||
* @param count tokens needed | * @param count tokens needed | ||||
* @param args parameters of the original call | * @param args parameters of the original call | ||||
* @throws Throwable | |||||
* @throws Throwable blocked exception or unexpected error | |||||
*/ | */ | ||||
void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) | void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) | ||||
throws Throwable; | throws Throwable; | ||||
@@ -44,10 +44,10 @@ public interface ProcessorSlot<T> { | |||||
* | * | ||||
* @param context current {@link Context} | * @param context current {@link Context} | ||||
* @param resourceWrapper current resource | * @param resourceWrapper current resource | ||||
* @param obj | |||||
* @param obj relevant object (e.g. Node) | |||||
* @param count tokens needed | * @param count tokens needed | ||||
* @param args parameters of the original call | * @param args parameters of the original call | ||||
* @throws Throwable | |||||
* @throws Throwable blocked exception or unexpected error | |||||
*/ | */ | ||||
void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args) | void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, Object... args) | ||||
throws Throwable; | throws Throwable; | ||||
@@ -0,0 +1,32 @@ | |||||
/* | |||||
* 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.slotchain; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
/** | |||||
* Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public interface ProcessorSlotEntryCallback<T> { | |||||
void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception; | |||||
void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args); | |||||
} |
@@ -0,0 +1,29 @@ | |||||
/* | |||||
* 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.slotchain; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
/** | |||||
* Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public interface ProcessorSlotExitCallback { | |||||
void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); | |||||
} |
@@ -15,6 +15,10 @@ | |||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.slots.statistic; | package com.alibaba.csp.sentinel.slots.statistic; | ||||
import java.util.Collection; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | import com.alibaba.csp.sentinel.util.TimeUtil; | ||||
import com.alibaba.csp.sentinel.Constants; | import com.alibaba.csp.sentinel.Constants; | ||||
import com.alibaba.csp.sentinel.EntryType; | import com.alibaba.csp.sentinel.EntryType; | ||||
@@ -39,13 +43,13 @@ import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
* </p> | * </p> | ||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | |||||
*/ | */ | ||||
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | ||||
@Override | @Override | ||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | ||||
throws Throwable { | throws Throwable { | ||||
try { | try { | ||||
fireEntry(context, resourceWrapper, node, count, args); | fireEntry(context, resourceWrapper, node, count, args); | ||||
node.increaseThreadNum(); | node.increaseThreadNum(); | ||||
@@ -61,6 +65,9 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
Constants.ENTRY_NODE.addPassRequest(); | Constants.ENTRY_NODE.addPassRequest(); | ||||
} | } | ||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { | |||||
handler.onPass(context, resourceWrapper, node, count, args); | |||||
} | |||||
} catch (BlockException e) { | } catch (BlockException e) { | ||||
context.getCurEntry().setError(e); | context.getCurEntry().setError(e); | ||||
@@ -74,6 +81,10 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
Constants.ENTRY_NODE.increaseBlockedQps(); | Constants.ENTRY_NODE.increaseBlockedQps(); | ||||
} | } | ||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { | |||||
handler.onBlocked(e, context, resourceWrapper, node, count, args); | |||||
} | |||||
throw e; | throw e; | ||||
} catch (Throwable e) { | } catch (Throwable e) { | ||||
context.getCurEntry().setError(e); | context.getCurEntry().setError(e); | ||||
@@ -117,11 +128,14 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
Constants.ENTRY_NODE.decreaseThreadNum(); | Constants.ENTRY_NODE.decreaseThreadNum(); | ||||
} | } | ||||
} else { | } else { | ||||
// error may happen | |||||
// node.rt(-2); | |||||
// Error may happen. | |||||
} | |||||
Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks(); | |||||
for (ProcessorSlotExitCallback handler : exitCallbacks) { | |||||
handler.onExit(context, resourceWrapper, count, args); | |||||
} | } | ||||
fireExit(context, resourceWrapper, count); | fireExit(context, resourceWrapper, count); | ||||
} | } | ||||
} | } |
@@ -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.slots.statistic; | |||||
import java.util.Collection; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; | |||||
/** | |||||
* <p> | |||||
* Callback registry for {@link StatisticSlot}. Now two kind of callbacks are supported: | |||||
* <ul> | |||||
* <li>{@link ProcessorSlotEntryCallback}: callback for entry (passed and blocked)</li> | |||||
* <li>{@link ProcessorSlotExitCallback}: callback for exiting {@link StatisticSlot}</li> | |||||
* </ul> | |||||
* </p> | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public final class StatisticSlotCallbackRegistry { | |||||
private static final Map<String, ProcessorSlotEntryCallback<DefaultNode>> entryCallbackMap | |||||
= new ConcurrentHashMap<String, ProcessorSlotEntryCallback<DefaultNode>>(); | |||||
private static final Map<String, ProcessorSlotExitCallback> exitCallbackMap | |||||
= new ConcurrentHashMap<String, ProcessorSlotExitCallback>(); | |||||
public static void clearEntryCallback() { | |||||
entryCallbackMap.clear(); | |||||
} | |||||
public static void clearExitCallback() { | |||||
exitCallbackMap.clear(); | |||||
} | |||||
public static void addEntryCallback(String key, ProcessorSlotEntryCallback<DefaultNode> callback) { | |||||
entryCallbackMap.put(key, callback); | |||||
} | |||||
public static void addExitCallback(String key, ProcessorSlotExitCallback callback) { | |||||
exitCallbackMap.put(key, callback); | |||||
} | |||||
public static ProcessorSlotEntryCallback<DefaultNode> removeEntryCallback(String key) { | |||||
if (key == null) { | |||||
return null; | |||||
} | |||||
return entryCallbackMap.remove(key); | |||||
} | |||||
public static ProcessorSlotExitCallback removeExitCallback(String key) { | |||||
if (key == null) { | |||||
return null; | |||||
} | |||||
return exitCallbackMap.remove(key); | |||||
} | |||||
public static Collection<ProcessorSlotEntryCallback<DefaultNode>> getEntryCallbacks() { | |||||
return entryCallbackMap.values(); | |||||
} | |||||
public static Collection<ProcessorSlotExitCallback> getExitCallbacks() { | |||||
return exitCallbackMap.values(); | |||||
} | |||||
private StatisticSlotCallbackRegistry() {} | |||||
} |
@@ -26,6 +26,7 @@ | |||||
<module>sentinel-demo-zookeeper-datasource</module> | <module>sentinel-demo-zookeeper-datasource</module> | ||||
<module>sentinel-demo-apollo-datasource</module> | <module>sentinel-demo-apollo-datasource</module> | ||||
<module>sentinel-demo-annotation-spring-aop</module> | <module>sentinel-demo-annotation-spring-aop</module> | ||||
<module>sentinel-demo-parameter-flow-control</module> | |||||
</modules> | </modules> | ||||
<dependencies> | <dependencies> | ||||
@@ -0,0 +1,30 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<parent> | |||||
<artifactId>sentinel-demo</artifactId> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<version>0.2.0-SNAPSHOT</version> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<artifactId>sentinel-demo-parameter-flow-control</artifactId> | |||||
<properties> | |||||
<java.source.version>1.8</java.source.version> | |||||
<java.target.version>1.8</java.target.version> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-transport-simple-http</artifactId> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> |
@@ -0,0 +1,69 @@ | |||||
/* | |||||
* 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.demo.flow.param; | |||||
import java.util.Collections; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
/** | |||||
* This demo demonstrates flow control by frequent ("hot spot") parameters. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowQpsDemo { | |||||
private static final int PARAM_A = 1; | |||||
private static final int PARAM_B = 2; | |||||
private static final int PARAM_C = 3; | |||||
private static final int PARAM_D = 4; | |||||
/** | |||||
* Here we prepare different parameters to validate flow control by parameters. | |||||
*/ | |||||
private static final Integer[] PARAMS = new Integer[] {PARAM_A, PARAM_B, PARAM_C, PARAM_D}; | |||||
private static final String RESOURCE_KEY = "resA"; | |||||
public static void main(String[] args) { | |||||
initHotParamFlowRules(); | |||||
final int threadCount = 8; | |||||
ParamFlowQpsRunner<Integer> runner = new ParamFlowQpsRunner<>(PARAMS, RESOURCE_KEY, threadCount, 120); | |||||
runner.simulateTraffic(); | |||||
runner.tick(); | |||||
} | |||||
private static void initHotParamFlowRules() { | |||||
// QPS mode, threshold is 5 for every frequent "hot spot" parameter in index 0 (the first arg). | |||||
ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY) | |||||
.setParamIdx(0) | |||||
.setBlockGrade(RuleConstant.FLOW_GRADE_QPS) | |||||
.setCount(5); | |||||
// We can set threshold count for specific parameter value individually. | |||||
// Here we add an exception item. That means: QPS threshold of entries with parameter `PARAM_B` (type: int) | |||||
// in index 0 will be 10, rather than the global threshold (5). | |||||
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)) | |||||
.setClassType(int.class.getName()) | |||||
.setCount(10); | |||||
rule.setParamFlowItemList(Collections.singletonList(item)); | |||||
ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); | |||||
} | |||||
} |
@@ -0,0 +1,167 @@ | |||||
/* | |||||
* 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.demo.flow.param; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.concurrent.ThreadLocalRandom; | |||||
import java.util.concurrent.TimeUnit; | |||||
import java.util.concurrent.atomic.AtomicLong; | |||||
import com.alibaba.csp.sentinel.Entry; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
import com.alibaba.csp.sentinel.SphU; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
/** | |||||
* A traffic runner to simulate flow for different parameters. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
class ParamFlowQpsRunner<T> { | |||||
private final T[] params; | |||||
private final String resourceName; | |||||
private int seconds; | |||||
private final int threadCount; | |||||
private final Map<T, AtomicLong> passCountMap = new ConcurrentHashMap<>(); | |||||
private volatile boolean stop = false; | |||||
public ParamFlowQpsRunner(T[] params, String resourceName, int threadCount, int seconds) { | |||||
assertTrue(params != null && params.length > 0, "Parameter array should not be empty"); | |||||
assertTrue(StringUtil.isNotBlank(resourceName), "Resource name cannot be empty"); | |||||
assertTrue(seconds > 0, "Time period should be positive"); | |||||
assertTrue(threadCount > 0 && threadCount <= 1000, "Invalid thread count"); | |||||
this.params = params; | |||||
this.resourceName = resourceName; | |||||
this.seconds = seconds; | |||||
this.threadCount = threadCount; | |||||
for (T param : params) { | |||||
assertTrue(param != null, "Parameters should not be null"); | |||||
passCountMap.putIfAbsent(param, new AtomicLong()); | |||||
} | |||||
} | |||||
private void assertTrue(boolean b, String message) { | |||||
if (!b) { | |||||
throw new IllegalArgumentException(message); | |||||
} | |||||
} | |||||
/** | |||||
* Pick one of provided parameters randomly. | |||||
* | |||||
* @return picked parameter | |||||
*/ | |||||
private T generateParam() { | |||||
int i = ThreadLocalRandom.current().nextInt(0, params.length); | |||||
return params[i]; | |||||
} | |||||
void simulateTraffic() { | |||||
for (int i = 0; i < threadCount; i++) { | |||||
Thread t = new Thread(new RunTask()); | |||||
t.setName("sentinel-simulate-traffic-task-" + i); | |||||
t.start(); | |||||
} | |||||
} | |||||
void tick() { | |||||
Thread timer = new Thread(new TimerTask()); | |||||
timer.setName("sentinel-timer-task"); | |||||
timer.start(); | |||||
} | |||||
private void passFor(T param) { | |||||
passCountMap.get(param).incrementAndGet(); | |||||
} | |||||
final class RunTask implements Runnable { | |||||
@Override | |||||
public void run() { | |||||
while (!stop) { | |||||
Entry entry = null; | |||||
try { | |||||
T param = generateParam(); | |||||
entry = SphU.entry(resourceName, EntryType.IN, 1, param); | |||||
// Add pass for parameter. | |||||
passFor(param); | |||||
} catch (BlockException e1) { | |||||
// block.incrementAndGet(); | |||||
} catch (Exception e2) { | |||||
// biz exception | |||||
} finally { | |||||
// total.incrementAndGet(); | |||||
if (entry != null) { | |||||
entry.exit(); | |||||
} | |||||
} | |||||
try { | |||||
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(0, 10)); | |||||
} catch (InterruptedException e) { | |||||
// ignore | |||||
} | |||||
} | |||||
} | |||||
} | |||||
final class TimerTask implements Runnable { | |||||
@Override | |||||
public void run() { | |||||
long start = System.currentTimeMillis(); | |||||
System.out.println("Begin to run! Go go go!"); | |||||
System.out.println("See corresponding metrics.log for accurate statistic data"); | |||||
Map<T, Long> map = new HashMap<>(params.length); | |||||
for (T param : params) { | |||||
map.putIfAbsent(param, 0L); | |||||
} | |||||
while (!stop) { | |||||
try { | |||||
TimeUnit.SECONDS.sleep(1); | |||||
} catch (InterruptedException e) { | |||||
} | |||||
// There may be a mismatch for time window of internal sliding window. | |||||
// See corresponding `metrics.log` for accurate statistic log. | |||||
for (T param : params) { | |||||
long globalPass = passCountMap.get(param).get(); | |||||
long oldPass = map.get(param); | |||||
long oneSecondPass = globalPass - oldPass; | |||||
map.put(param, globalPass); | |||||
System.out.println(String.format("[%d][%d] Hot param metrics for resource %s: " | |||||
+ "pass count for param <%s> is %d", | |||||
seconds, TimeUtil.currentTimeMillis(), resourceName, param, oneSecondPass)); | |||||
} | |||||
if (seconds-- <= 0) { | |||||
stop = true; | |||||
} | |||||
} | |||||
long cost = System.currentTimeMillis() - start; | |||||
System.out.println("Time cost: " + cost + " ms"); | |||||
System.exit(0); | |||||
} | |||||
} | |||||
} |
@@ -18,6 +18,7 @@ | |||||
<module>sentinel-datasource-apollo</module> | <module>sentinel-datasource-apollo</module> | ||||
<module>sentinel-datasource-redis</module> | <module>sentinel-datasource-redis</module> | ||||
<module>sentinel-annotation-aspectj</module> | <module>sentinel-annotation-aspectj</module> | ||||
<module>sentinel-parameter-flow-control</module> | |||||
</modules> | </modules> | ||||
</project> | </project> |
@@ -0,0 +1,61 @@ | |||||
# Sentinel Parameter Flow Control | |||||
This component provides functionality of flow control by frequent ("hot spot") parameters. | |||||
## Usage | |||||
To use Sentinel Parameter Flow Control, you need to add the following dependency to `pom.xml`: | |||||
```xml | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
<version>x.y.z</version> | |||||
</dependency> | |||||
``` | |||||
First you need to pass parameters with the following `SphU.entry` overloaded methods: | |||||
```java | |||||
public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException | |||||
public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException | |||||
``` | |||||
For example, if there are two parameters to provide, you can: | |||||
```java | |||||
// paramA in index 0, paramB in index 1. | |||||
SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB); | |||||
``` | |||||
Then you can configure parameter flow control rules via `loadRules` method in `ParamFlowRuleManager`: | |||||
```java | |||||
// QPS mode, threshold is 5 for every frequent "hot spot" parameter in index 0 (the first arg). | |||||
ParamFlowRule rule = new ParamFlowRule(RESOURCE_KEY) | |||||
.setParamIdx(0) | |||||
.setCount(5); | |||||
// We can set threshold count for specific parameter value individually. | |||||
// Here we add an exception item. That means: QPS threshold of entries with parameter `PARAM_B` (type: int) | |||||
// in index 0 will be 10, rather than the global threshold (5). | |||||
ParamFlowItem item = new ParamFlowItem().setObject(String.valueOf(PARAM_B)) | |||||
.setClassType(int.class.getName()) | |||||
.setCount(10); | |||||
rule.setParamFlowItemList(Collections.singletonList(item)); | |||||
ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); | |||||
``` | |||||
The description for fields of `ParamFlowRule`: | |||||
| Field | Description | Default | | |||||
| :----: | :----| :----| | |||||
| resource| resource name (**required**) || | |||||
| count | flow control threshold (**required**) || | |||||
| blockGrade | flow control mode (only QPS mode is supported) | QPS mode | | |||||
| paramIdx | the index of provided parameter in `SphU.entry(xxx, args)` (**required**) || | |||||
| paramFlowItemList | the exception items of parameter; you can set threshold to a specific parameter value || | |||||
Now the parameter flow control rules will take effect. | |||||
@@ -0,0 +1,43 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<parent> | |||||
<artifactId>sentinel-extension</artifactId> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<version>0.2.0-SNAPSHOT</version> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<artifactId>sentinel-parameter-flow-control</artifactId> | |||||
<packaging>jar</packaging> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-core</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-transport-common</artifactId> | |||||
<scope>provided</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.googlecode.concurrentlinkedhashmap</groupId> | |||||
<artifactId>concurrentlinkedhashmap-lru</artifactId> | |||||
<version>1.4.2</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-core</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> |
@@ -0,0 +1,64 @@ | |||||
/* | |||||
* 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.command.handler; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSON; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
@CommandMapping(name = "topParams") | |||||
public class FetchTopParamsCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String resourceName = request.getParam("res"); | |||||
if (StringUtil.isBlank(resourceName)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("Invalid parameter: res")); | |||||
} | |||||
String idx = request.getParam("idx"); | |||||
int index; | |||||
try { | |||||
index = Integer.valueOf(idx); | |||||
} catch (Exception ex) { | |||||
return CommandResponse.ofFailure(ex, "Invalid parameter: idx"); | |||||
} | |||||
String n = request.getParam("n"); | |||||
int amount; | |||||
try { | |||||
amount = Integer.valueOf(n); | |||||
} catch (Exception ex) { | |||||
return CommandResponse.ofFailure(ex, "Invalid parameter: n"); | |||||
} | |||||
ParameterMetric metric = ParamFlowSlot.getHotParamMetricForName(resourceName); | |||||
if (metric == null) { | |||||
return CommandResponse.ofSuccess("{}"); | |||||
} | |||||
Map<Object, Double> values = metric.getTopPassParamCount(index, amount); | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(values)); | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
/* | |||||
* 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.command.handler; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import com.alibaba.fastjson.JSON; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
@CommandMapping(name = "getParamFlowRules") | |||||
public class GetParamFlowRulesCommandHandler implements CommandHandler<String> { | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
return CommandResponse.ofSuccess(JSON.toJSONString(ParamFlowRuleManager.getRules())); | |||||
} | |||||
} |
@@ -0,0 +1,95 @@ | |||||
/* | |||||
* 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.command.handler; | |||||
import java.net.URLDecoder; | |||||
import java.util.List; | |||||
import com.alibaba.csp.sentinel.command.CommandHandler; | |||||
import com.alibaba.csp.sentinel.command.CommandRequest; | |||||
import com.alibaba.csp.sentinel.command.CommandResponse; | |||||
import com.alibaba.csp.sentinel.command.annotation.CommandMapping; | |||||
import com.alibaba.csp.sentinel.datasource.WritableDataSource; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
import com.alibaba.fastjson.JSONArray; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
@CommandMapping(name = "setParamFlowRules") | |||||
public class ModifyParamFlowRulesCommandHandler implements CommandHandler<String> { | |||||
private static WritableDataSource<List<ParamFlowRule>> paramFlowWds = null; | |||||
@Override | |||||
public CommandResponse<String> handle(CommandRequest request) { | |||||
String data = request.getParam("data"); | |||||
if (StringUtil.isBlank(data)) { | |||||
return CommandResponse.ofFailure(new IllegalArgumentException("Bad data")); | |||||
} | |||||
try { | |||||
data = URLDecoder.decode(data, "utf-8"); | |||||
} catch (Exception e) { | |||||
RecordLog.info("Decode rule data error", e); | |||||
return CommandResponse.ofFailure(e, "decode rule data error"); | |||||
} | |||||
RecordLog.info(String.format("[API Server] Receiving rule change (type:parameter flow rule): %s", data)); | |||||
String result = SUCCESS_MSG; | |||||
List<ParamFlowRule> flowRules = JSONArray.parseArray(data, ParamFlowRule.class); | |||||
ParamFlowRuleManager.loadRules(flowRules); | |||||
if (!writeToDataSource(paramFlowWds, flowRules)) { | |||||
result = WRITE_DS_FAILURE_MSG; | |||||
} | |||||
return CommandResponse.ofSuccess(result); | |||||
} | |||||
/** | |||||
* Write target value to given data source. | |||||
* | |||||
* @param dataSource writable data source | |||||
* @param value target value to save | |||||
* @param <T> value type | |||||
* @return true if write successful or data source is empty; false if error occurs | |||||
*/ | |||||
private <T> boolean writeToDataSource(WritableDataSource<T> dataSource, T value) { | |||||
if (dataSource != null) { | |||||
try { | |||||
dataSource.write(value); | |||||
} catch (Exception e) { | |||||
RecordLog.warn("Write data source failed", e); | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
public synchronized static WritableDataSource<List<ParamFlowRule>> getWritableDataSource() { | |||||
return paramFlowWds; | |||||
} | |||||
public synchronized static void setWritableDataSource(WritableDataSource<List<ParamFlowRule>> hotParamWds) { | |||||
ModifyParamFlowRulesCommandHandler.paramFlowWds = hotParamWds; | |||||
} | |||||
private static final String SUCCESS_MSG = "success"; | |||||
private static final String WRITE_DS_FAILURE_MSG = "partial success (write data source failed)"; | |||||
} |
@@ -0,0 +1,35 @@ | |||||
/* | |||||
* 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.init; | |||||
import com.alibaba.csp.sentinel.slots.statistic.ParamFlowStatisticEntryCallback; | |||||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry; | |||||
/** | |||||
* Init function for adding callbacks to {@link StatisticSlotCallbackRegistry} to record metrics | |||||
* for frequent parameters in {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowStatisticSlotCallbackInit implements InitFunc { | |||||
@Override | |||||
public void init() { | |||||
StatisticSlotCallbackRegistry.addEntryCallback(ParamFlowStatisticEntryCallback.class.getName(), | |||||
new ParamFlowStatisticEntryCallback()); | |||||
} | |||||
} |
@@ -0,0 +1,52 @@ | |||||
/* | |||||
* 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.slots; | |||||
import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; | |||||
import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; | |||||
import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; | |||||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||||
import com.alibaba.csp.sentinel.slots.logger.LogSlot; | |||||
import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; | |||||
import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; | |||||
import com.alibaba.csp.sentinel.slots.system.SystemSlot; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class HotParamSlotChainBuilder implements SlotChainBuilder { | |||||
@Override | |||||
public ProcessorSlotChain build() { | |||||
ProcessorSlotChain chain = new DefaultProcessorSlotChain(); | |||||
chain.addLast(new NodeSelectorSlot()); | |||||
chain.addLast(new ClusterBuilderSlot()); | |||||
chain.addLast(new LogSlot()); | |||||
chain.addLast(new StatisticSlot()); | |||||
chain.addLast(new ParamFlowSlot()); | |||||
chain.addLast(new SystemSlot()); | |||||
chain.addLast(new AuthoritySlot()); | |||||
chain.addLast(new FlowSlot()); | |||||
chain.addLast(new DegradeSlot()); | |||||
return chain; | |||||
} | |||||
} |
@@ -0,0 +1,100 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.lang.reflect.Array; | |||||
import java.util.Collection; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
final class ParamFlowChecker { | |||||
static boolean passCheck(ResourceWrapper resourceWrapper, /*@Valid*/ ParamFlowRule rule, /*@Valid*/ int count, | |||||
Object... args) { | |||||
if (args == null) { | |||||
return true; | |||||
} | |||||
int paramIdx = rule.getParamIdx(); | |||||
if (args.length <= paramIdx) { | |||||
return true; | |||||
} | |||||
Object value = args[paramIdx]; | |||||
return passLocalCheck(resourceWrapper, rule, count, value); | |||||
} | |||||
private static ParameterMetric getHotParameters(ResourceWrapper resourceWrapper) { | |||||
// Should not be null. | |||||
return ParamFlowSlot.getParamMetric(resourceWrapper); | |||||
} | |||||
private static boolean passLocalCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { | |||||
try { | |||||
if (Collection.class.isAssignableFrom(value.getClass())) { | |||||
for (Object param : ((Collection)value)) { | |||||
if (!passSingleValueCheck(resourceWrapper, rule, count, param)) { | |||||
return false; | |||||
} | |||||
} | |||||
} else if (value.getClass().isArray()) { | |||||
int length = Array.getLength(value); | |||||
for (int i = 0; i < length; i++) { | |||||
Object param = Array.get(value, i); | |||||
if (!passSingleValueCheck(resourceWrapper, rule, count, param)) { | |||||
return false; | |||||
} | |||||
} | |||||
} else { | |||||
return passSingleValueCheck(resourceWrapper, rule, count, value); | |||||
} | |||||
} catch (Throwable e) { | |||||
RecordLog.info("[ParamFlowChecker] Unexpected error", e); | |||||
} | |||||
return true; | |||||
} | |||||
static boolean passSingleValueCheck(ResourceWrapper resourceWrapper, ParamFlowRule rule, int count, Object value) { | |||||
Set<Object> exclusionItems = rule.getParsedHotItems().keySet(); | |||||
if (rule.getBlockGrade() == RuleConstant.FLOW_GRADE_QPS) { | |||||
double curCount = getHotParameters(resourceWrapper).getPassParamQps(rule.getParamIdx(), value); | |||||
if (exclusionItems.contains(value)) { | |||||
// Pass check for exclusion items. | |||||
int itemQps = rule.getParsedHotItems().get(value); | |||||
return curCount + count <= itemQps; | |||||
} else if (curCount + count > rule.getCount()) { | |||||
if ((curCount - rule.getCount()) < 1 && (curCount - rule.getCount()) > 0) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
private ParamFlowChecker() {} | |||||
} |
@@ -0,0 +1,48 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
/** | |||||
* Block exception for frequent ("hot-spot") parameter flow control. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowException extends BlockException { | |||||
private final String resourceName; | |||||
public ParamFlowException(String resourceName, String message, Throwable cause) { | |||||
super(message, cause); | |||||
this.resourceName = resourceName; | |||||
} | |||||
public ParamFlowException(String resourceName, String message) { | |||||
super(message, message); | |||||
this.resourceName = resourceName; | |||||
} | |||||
public String getResourceName() { | |||||
return resourceName; | |||||
} | |||||
@Override | |||||
public Throwable fillInStackTrace() { | |||||
return this; | |||||
} | |||||
} |
@@ -0,0 +1,101 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
/** | |||||
* A flow control item for a specific parameter value. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowItem { | |||||
private String object; | |||||
private Integer count; | |||||
private String classType; | |||||
public ParamFlowItem() {} | |||||
public ParamFlowItem(String object, Integer count, String classType) { | |||||
this.object = object; | |||||
this.count = count; | |||||
this.classType = classType; | |||||
} | |||||
public static <T> ParamFlowItem newItem(T object, Integer count) { | |||||
if (object == null) { | |||||
throw new IllegalArgumentException("Invalid object: null"); | |||||
} | |||||
return new ParamFlowItem(object.toString(), count, object.getClass().getName()); | |||||
} | |||||
public String getObject() { | |||||
return object; | |||||
} | |||||
public ParamFlowItem setObject(String object) { | |||||
this.object = object; | |||||
return this; | |||||
} | |||||
public Integer getCount() { | |||||
return count; | |||||
} | |||||
public ParamFlowItem setCount(Integer count) { | |||||
this.count = count; | |||||
return this; | |||||
} | |||||
public String getClassType() { | |||||
return classType; | |||||
} | |||||
public ParamFlowItem setClassType(String classType) { | |||||
this.classType = classType; | |||||
return this; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { return true; } | |||||
if (o == null || getClass() != o.getClass()) { return false; } | |||||
ParamFlowItem item = (ParamFlowItem)o; | |||||
if (object != null ? !object.equals(item.object) : item.object != null) { return false; } | |||||
if (count != null ? !count.equals(item.count) : item.count != null) { return false; } | |||||
return classType != null ? classType.equals(item.classType) : item.classType == null; | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = object != null ? object.hashCode() : 0; | |||||
result = 31 * result + (count != null ? count.hashCode() : 0); | |||||
result = 31 * result + (classType != null ? classType.hashCode() : 0); | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "ParamFlowItem{" + | |||||
"object=" + object + | |||||
", count=" + count + | |||||
", classType='" + classType + '\'' + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,156 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.slots.block.AbstractRule; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
/** | |||||
* Rules for "hot-spot" frequent parameter flow control. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowRule extends AbstractRule { | |||||
public ParamFlowRule() {} | |||||
public ParamFlowRule(String resourceName) { | |||||
setResource(resourceName); | |||||
} | |||||
/** | |||||
* The threshold type of flow control (1: QPS). | |||||
*/ | |||||
private int blockGrade = RuleConstant.FLOW_GRADE_QPS; | |||||
/** | |||||
* Parameter index. | |||||
*/ | |||||
private Integer paramIdx; | |||||
/** | |||||
* The threshold count. | |||||
*/ | |||||
private double count; | |||||
/** | |||||
* Original exclusion items of parameters. | |||||
*/ | |||||
private List<ParamFlowItem> paramFlowItemList = new ArrayList<ParamFlowItem>(); | |||||
/** | |||||
* Parsed exclusion items of parameters. Only for internal use. | |||||
*/ | |||||
private Map<Object, Integer> hotItems = new HashMap<Object, Integer>(); | |||||
public int getBlockGrade() { | |||||
return blockGrade; | |||||
} | |||||
public ParamFlowRule setBlockGrade(int blockGrade) { | |||||
this.blockGrade = blockGrade; | |||||
return this; | |||||
} | |||||
public Integer getParamIdx() { | |||||
return paramIdx; | |||||
} | |||||
public ParamFlowRule setParamIdx(Integer paramIdx) { | |||||
this.paramIdx = paramIdx; | |||||
return this; | |||||
} | |||||
public double getCount() { | |||||
return count; | |||||
} | |||||
public ParamFlowRule setCount(double count) { | |||||
this.count = count; | |||||
return this; | |||||
} | |||||
public List<ParamFlowItem> getParamFlowItemList() { | |||||
return paramFlowItemList; | |||||
} | |||||
public ParamFlowRule setParamFlowItemList(List<ParamFlowItem> paramFlowItemList) { | |||||
this.paramFlowItemList = paramFlowItemList; | |||||
return this; | |||||
} | |||||
Map<Object, Integer> getParsedHotItems() { | |||||
return hotItems; | |||||
} | |||||
ParamFlowRule setParsedHotItems(Map<Object, Integer> hotItems) { | |||||
this.hotItems = hotItems; | |||||
return this; | |||||
} | |||||
@Override | |||||
@Deprecated | |||||
public boolean passCheck(Context context, DefaultNode node, int count, Object... args) { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { return true; } | |||||
if (o == null || getClass() != o.getClass()) { return false; } | |||||
if (!super.equals(o)) { return false; } | |||||
ParamFlowRule rule = (ParamFlowRule)o; | |||||
if (blockGrade != rule.blockGrade) { return false; } | |||||
if (Double.compare(rule.count, count) != 0) { return false; } | |||||
if (paramIdx != null ? !paramIdx.equals(rule.paramIdx) : rule.paramIdx != null) { return false; } | |||||
return paramFlowItemList != null ? paramFlowItemList.equals(rule.paramFlowItemList) : rule.paramFlowItemList == null; | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = super.hashCode(); | |||||
long temp; | |||||
result = 31 * result + blockGrade; | |||||
result = 31 * result + (paramIdx != null ? paramIdx.hashCode() : 0); | |||||
temp = Double.doubleToLongBits(count); | |||||
result = 31 * result + (int)(temp ^ (temp >>> 32)); | |||||
result = 31 * result + (paramFlowItemList != null ? paramFlowItemList.hashCode() : 0); | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "ParamFlowRule{" + | |||||
"resource=" + getResource() + | |||||
", limitApp=" + getLimitApp() + | |||||
", blockGrade=" + blockGrade + | |||||
", paramIdx=" + paramIdx + | |||||
", count=" + count + | |||||
", paramFlowItemList=" + paramFlowItemList + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,233 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; | |||||
import com.alibaba.csp.sentinel.property.PropertyListener; | |||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* Manager for frequent ("hot-spot") parameter flow rules. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public final class ParamFlowRuleManager { | |||||
private static final Map<String, List<ParamFlowRule>> paramFlowRules | |||||
= new ConcurrentHashMap<String, List<ParamFlowRule>>(); | |||||
private final static RulePropertyListener PROPERTY_LISTENER = new RulePropertyListener(); | |||||
private static SentinelProperty<List<ParamFlowRule>> currentProperty | |||||
= new DynamicSentinelProperty<List<ParamFlowRule>>(); | |||||
static { | |||||
currentProperty.addListener(PROPERTY_LISTENER); | |||||
} | |||||
/** | |||||
* Load parameter flow rules. Former rules will be replaced. | |||||
* | |||||
* @param rules new rules to load. | |||||
*/ | |||||
public static void loadRules(List<ParamFlowRule> rules) { | |||||
try { | |||||
currentProperty.updateValue(rules); | |||||
} catch (Throwable e) { | |||||
RecordLog.info("[ParamFlowRuleManager] Failed to load rules", e); | |||||
} | |||||
} | |||||
/** | |||||
* Listen to the {@link SentinelProperty} for {@link ParamFlowRule}s. The property is the source | |||||
* of {@link ParamFlowRule}s. Parameter flow rules can also be set by {@link #loadRules(List)} directly. | |||||
* | |||||
* @param property the property to listen | |||||
*/ | |||||
public static void register2Property(SentinelProperty<List<ParamFlowRule>> property) { | |||||
synchronized (PROPERTY_LISTENER) { | |||||
currentProperty.removeListener(PROPERTY_LISTENER); | |||||
property.addListener(PROPERTY_LISTENER); | |||||
currentProperty = property; | |||||
RecordLog.info("[ParamFlowRuleManager] New property has been registered to hot param rule manager"); | |||||
} | |||||
} | |||||
public static List<ParamFlowRule> getRulesOfResource(String resourceName) { | |||||
return paramFlowRules.get(resourceName); | |||||
} | |||||
public static boolean hasRules(String resourceName) { | |||||
List<ParamFlowRule> rules = paramFlowRules.get(resourceName); | |||||
return rules != null && !rules.isEmpty(); | |||||
} | |||||
/** | |||||
* Get a copy of the rules. | |||||
* | |||||
* @return a new copy of the rules. | |||||
*/ | |||||
public static List<ParamFlowRule> getRules() { | |||||
List<ParamFlowRule> rules = new ArrayList<ParamFlowRule>(); | |||||
for (Map.Entry<String, List<ParamFlowRule>> entry : paramFlowRules.entrySet()) { | |||||
rules.addAll(entry.getValue()); | |||||
} | |||||
return rules; | |||||
} | |||||
private static Object parseValue(String value, String classType) { | |||||
if (value == null) { | |||||
throw new IllegalArgumentException("Null value"); | |||||
} | |||||
if (StringUtil.isBlank(classType)) { | |||||
// If the class type is not provided, then treat it as string. | |||||
return value; | |||||
} | |||||
// Handle primitive type. | |||||
if (int.class.toString().equals(classType) || Integer.class.getName().equals(classType)) { | |||||
return Integer.parseInt(value); | |||||
} else if (boolean.class.toString().equals(classType) || Boolean.class.getName().equals(classType)) { | |||||
return Boolean.parseBoolean(value); | |||||
} else if (long.class.toString().equals(classType) || Long.class.getName().equals(classType)) { | |||||
return Long.parseLong(value); | |||||
} else if (double.class.toString().equals(classType) || Double.class.getName().equals(classType)) { | |||||
return Double.parseDouble(value); | |||||
} else if (float.class.toString().equals(classType) || Float.class.getName().equals(classType)) { | |||||
return Float.parseFloat(value); | |||||
} else if (byte.class.toString().equals(classType) || Byte.class.getName().equals(classType)) { | |||||
return Byte.parseByte(value); | |||||
} else if (short.class.toString().equals(classType) || Short.class.getName().equals(classType)) { | |||||
return Short.parseShort(value); | |||||
} else if (char.class.toString().equals(classType)) { | |||||
char[] array = value.toCharArray(); | |||||
return array.length > 0 ? array[0] : null; | |||||
} | |||||
return value; | |||||
} | |||||
static class RulePropertyListener implements PropertyListener<List<ParamFlowRule>> { | |||||
@Override | |||||
public void configUpdate(List<ParamFlowRule> list) { | |||||
Map<String, List<ParamFlowRule>> rules = aggregateHotParamRules(list); | |||||
if (rules != null) { | |||||
paramFlowRules.clear(); | |||||
paramFlowRules.putAll(rules); | |||||
} | |||||
RecordLog.info("[ParamFlowRuleManager] Hot spot parameter flow rules received: " + paramFlowRules); | |||||
} | |||||
@Override | |||||
public void configLoad(List<ParamFlowRule> list) { | |||||
Map<String, List<ParamFlowRule>> rules = aggregateHotParamRules(list); | |||||
if (rules != null) { | |||||
paramFlowRules.clear(); | |||||
paramFlowRules.putAll(rules); | |||||
} | |||||
RecordLog.info("[ParamFlowRuleManager] Hot spot parameter flow rules received: " + paramFlowRules); | |||||
} | |||||
private Map<String, List<ParamFlowRule>> aggregateHotParamRules(List<ParamFlowRule> list) { | |||||
Map<String, List<ParamFlowRule>> newRuleMap = new ConcurrentHashMap<String, List<ParamFlowRule>>(); | |||||
if (list == null || list.isEmpty()) { | |||||
// No parameter flow rules, so clear all the metrics. | |||||
ParamFlowSlot.getMetricsMap().clear(); | |||||
RecordLog.info("[ParamFlowRuleManager] No parameter flow rules, clearing all parameter metrics"); | |||||
return newRuleMap; | |||||
} | |||||
for (ParamFlowRule rule : list) { | |||||
if (!isValidRule(rule)) { | |||||
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid rule when loading new rules: " + rule); | |||||
continue; | |||||
} | |||||
if (StringUtil.isBlank(rule.getLimitApp())) { | |||||
rule.setLimitApp(FlowRule.LIMIT_APP_DEFAULT); | |||||
} | |||||
if (rule.getParamFlowItemList() == null) { | |||||
rule.setParamFlowItemList(new ArrayList<ParamFlowItem>()); | |||||
} | |||||
Map<Object, Integer> itemMap = parseHotItems(rule.getParamFlowItemList()); | |||||
rule.setParsedHotItems(itemMap); | |||||
String resourceName = rule.getResource(); | |||||
List<ParamFlowRule> ruleList = newRuleMap.get(resourceName); | |||||
if (ruleList == null) { | |||||
ruleList = new ArrayList<ParamFlowRule>(); | |||||
newRuleMap.put(resourceName, ruleList); | |||||
} | |||||
ruleList.add(rule); | |||||
} | |||||
// Clear unused hot param metrics. | |||||
Set<String> previousResources = paramFlowRules.keySet(); | |||||
for (String resource : previousResources) { | |||||
if (!newRuleMap.containsKey(resource)) { | |||||
ParamFlowSlot.clearHotParamMetricForName(resource); | |||||
} | |||||
} | |||||
return newRuleMap; | |||||
} | |||||
} | |||||
static Map<Object, Integer> parseHotItems(List<ParamFlowItem> items) { | |||||
Map<Object, Integer> itemMap = new HashMap<Object, Integer>(); | |||||
if (items == null || items.isEmpty()) { | |||||
return itemMap; | |||||
} | |||||
for (ParamFlowItem item : items) { | |||||
// Value should not be null. | |||||
Object value; | |||||
try { | |||||
value = parseValue(item.getObject(), item.getClassType()); | |||||
} catch (Exception ex) { | |||||
RecordLog.warn("[ParamFlowRuleManager] Failed to parse value for item: " + item, ex); | |||||
continue; | |||||
} | |||||
if (item.getCount() == null || item.getCount() < 0 || value == null) { | |||||
RecordLog.warn("[ParamFlowRuleManager] Ignoring invalid exclusion parameter item: " + item); | |||||
continue; | |||||
} | |||||
itemMap.put(value, item.getCount()); | |||||
} | |||||
return itemMap; | |||||
} | |||||
static boolean isValidRule(ParamFlowRule rule) { | |||||
return rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 | |||||
&& rule.getParamIdx() != null && rule.getParamIdx() >= 0; | |||||
} | |||||
private ParamFlowRuleManager() {} | |||||
} | |||||
@@ -0,0 +1,158 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
import com.alibaba.csp.sentinel.util.StringUtil; | |||||
/** | |||||
* A processor slot that is responsible for flow control by frequent ("hot spot") parameters. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
private static final Map<ResourceWrapper, ParameterMetric> metricsMap | |||||
= new ConcurrentHashMap<ResourceWrapper, ParameterMetric>(); | |||||
/** | |||||
* Lock for a specific resource. | |||||
*/ | |||||
private final Object LOCK = new Object(); | |||||
@Override | |||||
public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | |||||
throws Throwable { | |||||
if (!ParamFlowRuleManager.hasRules(resourceWrapper.getName())) { | |||||
fireEntry(context, resourceWrapper, node, count, args); | |||||
return; | |||||
} | |||||
checkFlow(resourceWrapper, count, args); | |||||
fireEntry(context, resourceWrapper, node, count, args); | |||||
} | |||||
@Override | |||||
public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { | |||||
fireExit(context, resourceWrapper, count, args); | |||||
} | |||||
void checkFlow(ResourceWrapper resourceWrapper, int count, Object... args) | |||||
throws BlockException { | |||||
if (ParamFlowRuleManager.hasRules(resourceWrapper.getName())) { | |||||
List<ParamFlowRule> rules = ParamFlowRuleManager.getRulesOfResource(resourceWrapper.getName()); | |||||
if (rules == null) { | |||||
return; | |||||
} | |||||
for (ParamFlowRule rule : rules) { | |||||
// Initialize the parameter metrics. | |||||
initHotParamMetricsFor(resourceWrapper, rule.getParamIdx()); | |||||
if (!ParamFlowChecker.passCheck(resourceWrapper, rule, count, args)) { | |||||
// Here we add the block count. | |||||
addBlockCount(resourceWrapper, count, args); | |||||
String message = ""; | |||||
if (args.length > rule.getParamIdx()) { | |||||
Object value = args[rule.getParamIdx()]; | |||||
message = String.valueOf(value); | |||||
} | |||||
throw new ParamFlowException(resourceWrapper.getName(), message); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private void addBlockCount(ResourceWrapper resourceWrapper, int count, Object... args) { | |||||
ParameterMetric parameterMetric = ParamFlowSlot.getParamMetric(resourceWrapper); | |||||
if (parameterMetric != null) { | |||||
parameterMetric.addBlock(count, args); | |||||
} | |||||
} | |||||
/** | |||||
* Init the parameter metric and index map for given resource. | |||||
* Package-private for test. | |||||
* | |||||
* @param resourceWrapper resource to init | |||||
* @param index index to initialize, which must be valid | |||||
*/ | |||||
void initHotParamMetricsFor(ResourceWrapper resourceWrapper, /*@Valid*/ int index) { | |||||
ParameterMetric metric; | |||||
// Assume that the resource is valid. | |||||
if ((metric = metricsMap.get(resourceWrapper)) == null) { | |||||
synchronized (LOCK) { | |||||
if ((metric = metricsMap.get(resourceWrapper)) == null) { | |||||
metric = new ParameterMetric(); | |||||
metricsMap.put(resourceWrapper, metric); | |||||
RecordLog.info("[ParamFlowSlot] Creating parameter metric for: " + resourceWrapper.getName()); | |||||
} | |||||
} | |||||
} | |||||
metric.initializeForIndex(index); | |||||
} | |||||
public static ParameterMetric getParamMetric(ResourceWrapper resourceWrapper) { | |||||
if (resourceWrapper == null || resourceWrapper.getName() == null) { | |||||
return null; | |||||
} | |||||
return metricsMap.get(resourceWrapper); | |||||
} | |||||
public static ParameterMetric getHotParamMetricForName(String resourceName) { | |||||
if (StringUtil.isBlank(resourceName)) { | |||||
return null; | |||||
} | |||||
for (EntryType nodeType : EntryType.values()) { | |||||
ParameterMetric metric = metricsMap.get(new StringResourceWrapper(resourceName, nodeType)); | |||||
if (metric != null) { | |||||
return metric; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
static void clearHotParamMetricForName(String resourceName) { | |||||
if (StringUtil.isBlank(resourceName)) { | |||||
return; | |||||
} | |||||
for (EntryType nodeType : EntryType.values()) { | |||||
metricsMap.remove(new StringResourceWrapper(resourceName, nodeType)); | |||||
} | |||||
RecordLog.info("[ParamFlowSlot] Clearing parameter metric for: " + resourceName); | |||||
} | |||||
public static Map<ResourceWrapper, ParameterMetric> getMetricsMap() { | |||||
return metricsMap; | |||||
} | |||||
} |
@@ -0,0 +1,147 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.lang.reflect.Array; | |||||
import java.util.Collection; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.node.IntervalProperty; | |||||
import com.alibaba.csp.sentinel.node.SampleCountProperty; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray; | |||||
/** | |||||
* Metrics for frequent ("hot spot") parameters. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParameterMetric { | |||||
private Map<Integer, HotParameterLeapArray> rollingParameters = | |||||
new ConcurrentHashMap<Integer, HotParameterLeapArray>(); | |||||
public Map<Integer, HotParameterLeapArray> getRollingParameters() { | |||||
return rollingParameters; | |||||
} | |||||
public synchronized void clear() { | |||||
rollingParameters.clear(); | |||||
} | |||||
public void initializeForIndex(int index) { | |||||
if (!rollingParameters.containsKey(index)) { | |||||
synchronized (this) { | |||||
// putIfAbsent | |||||
if (rollingParameters.get(index) == null) { | |||||
rollingParameters.put(index, new HotParameterLeapArray( | |||||
1000 / 2, IntervalProperty.INTERVAL)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
public void addPass(int count, Object... args) { | |||||
add(RollingParamEvent.REQUEST_PASSED, count, args); | |||||
} | |||||
public void addBlock(int count, Object... args) { | |||||
add(RollingParamEvent.REQUEST_BLOCKED, count, args); | |||||
} | |||||
@SuppressWarnings("rawtypes") | |||||
private void add(RollingParamEvent event, int count, Object... args) { | |||||
if (args == null) { | |||||
return; | |||||
} | |||||
try { | |||||
for (int index = 0; index < args.length; index++) { | |||||
HotParameterLeapArray param = rollingParameters.get(index); | |||||
if (param == null) { | |||||
continue; | |||||
} | |||||
Object arg = args[index]; | |||||
if (arg == null) { | |||||
continue; | |||||
} | |||||
if (Collection.class.isAssignableFrom(arg.getClass())) { | |||||
for (Object value : ((Collection)arg)) { | |||||
param.addValue(event, count, value); | |||||
} | |||||
} else if (arg.getClass().isArray()) { | |||||
int length = Array.getLength(arg); | |||||
for (int i = 0; i < length; i++) { | |||||
Object value = Array.get(arg, i); | |||||
param.addValue(event, count, value); | |||||
} | |||||
} else { | |||||
param.addValue(event, count, arg); | |||||
} | |||||
} | |||||
} catch (Throwable e) { | |||||
RecordLog.warn("[ParameterMetric] Param exception", e); | |||||
} | |||||
} | |||||
public double getPassParamQps(int index, Object value) { | |||||
try { | |||||
HotParameterLeapArray parameter = rollingParameters.get(index); | |||||
if (parameter == null || value == null) { | |||||
return -1; | |||||
} | |||||
return parameter.getRollingAvg(RollingParamEvent.REQUEST_PASSED, value); | |||||
} catch (Throwable e) { | |||||
RecordLog.info(e.getMessage(), e); | |||||
} | |||||
return -1; | |||||
} | |||||
public long getBlockParamQps(int index, Object value) { | |||||
try { | |||||
HotParameterLeapArray parameter = rollingParameters.get(index); | |||||
if (parameter == null || value == null) { | |||||
return -1; | |||||
} | |||||
return (long)rollingParameters.get(index).getRollingAvg(RollingParamEvent.REQUEST_BLOCKED, value); | |||||
} catch (Throwable e) { | |||||
RecordLog.info(e.getMessage(), e); | |||||
} | |||||
return -1; | |||||
} | |||||
public Map<Object, Double> getTopPassParamCount(int index, int number) { | |||||
try { | |||||
HotParameterLeapArray parameter = rollingParameters.get(index); | |||||
if (parameter == null) { | |||||
return new HashMap<Object, Double>(); | |||||
} | |||||
return parameter.getTopValues(RollingParamEvent.REQUEST_PASSED, number); | |||||
} catch (Throwable e) { | |||||
RecordLog.info(e.getMessage(), e); | |||||
} | |||||
return new HashMap<Object, Double>(); | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public enum RollingParamEvent { | |||||
/** | |||||
* Indicates that the request successfully passed the slot chain (entry). | |||||
*/ | |||||
REQUEST_PASSED, | |||||
/** | |||||
* Indicates that the request is blocked by a specific slot. | |||||
*/ | |||||
REQUEST_BLOCKED | |||||
} |
@@ -0,0 +1,49 @@ | |||||
/* | |||||
* 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.slots.statistic; | |||||
import com.alibaba.csp.sentinel.context.Context; | |||||
import com.alibaba.csp.sentinel.node.DefaultNode; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetric; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowStatisticEntryCallback implements ProcessorSlotEntryCallback<DefaultNode> { | |||||
@Override | |||||
public void onPass(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, Object... args) | |||||
throws Exception { | |||||
// The "hot spot" parameter metric is present only if parameter flow rules for the resource exist. | |||||
ParameterMetric parameterMetric = ParamFlowSlot.getParamMetric(resourceWrapper); | |||||
if (parameterMetric != null) { | |||||
parameterMetric.addPass(count, args); | |||||
} | |||||
} | |||||
@Override | |||||
public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param, | |||||
int count, Object... args) { | |||||
// Here we don't add block count here because checking the type of block exception can affect performance. | |||||
// We add the block count when throwing the ParamFlowException instead. | |||||
} | |||||
} |
@@ -0,0 +1,45 @@ | |||||
/* | |||||
* 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.slots.statistic.cache; | |||||
import java.util.Set; | |||||
/** | |||||
* A common cache map interface. | |||||
* | |||||
* @param <K> type of the key | |||||
* @param <V> type of the value | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public interface CacheMap<K, V> { | |||||
boolean containsKey(K key); | |||||
V get(K key); | |||||
V remove(K key); | |||||
V put(K key, V value); | |||||
V putIfAbsent(K key, V value); | |||||
long size(); | |||||
void clear(); | |||||
Set<K> ascendingKeySet(); | |||||
} |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
* 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.slots.statistic.cache; | |||||
import java.util.Set; | |||||
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; | |||||
import com.googlecode.concurrentlinkedhashmap.Weighers; | |||||
/** | |||||
* A {@link ConcurrentLinkedHashMap} wrapper for the universal {@link CacheMap}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ConcurrentLinkedHashMapWrapper<T, R> implements CacheMap<T, R> { | |||||
private static final int DEFAULT_CONCURRENCY_LEVEL = 16; | |||||
private final ConcurrentLinkedHashMap<T, R> map; | |||||
public ConcurrentLinkedHashMapWrapper(long size) { | |||||
if (size <= 0) { | |||||
throw new IllegalArgumentException("Cache max capacity should be positive: " + size); | |||||
} | |||||
this.map = new ConcurrentLinkedHashMap.Builder<T, R>() | |||||
.concurrencyLevel(DEFAULT_CONCURRENCY_LEVEL) | |||||
.maximumWeightedCapacity(size) | |||||
.weigher(Weighers.singleton()) | |||||
.build(); | |||||
} | |||||
public ConcurrentLinkedHashMapWrapper(ConcurrentLinkedHashMap<T, R> map) { | |||||
if (map == null) { | |||||
throw new IllegalArgumentException("Invalid map instance"); | |||||
} | |||||
this.map = map; | |||||
} | |||||
@Override | |||||
public boolean containsKey(T key) { | |||||
return map.containsKey(key); | |||||
} | |||||
@Override | |||||
public R get(T key) { | |||||
return map.get(key); | |||||
} | |||||
@Override | |||||
public R remove(T key) { | |||||
return map.remove(key); | |||||
} | |||||
@Override | |||||
public R put(T key, R value) { | |||||
return map.put(key, value); | |||||
} | |||||
@Override | |||||
public R putIfAbsent(T key, R value) { | |||||
return map.putIfAbsent(key, value); | |||||
} | |||||
@Override | |||||
public long size() { | |||||
return map.weightedSize(); | |||||
} | |||||
@Override | |||||
public void clear() { | |||||
map.clear(); | |||||
} | |||||
@Override | |||||
public Set<T> ascendingKeySet() { | |||||
return map.ascendingKeySet(); | |||||
} | |||||
} |
@@ -0,0 +1,67 @@ | |||||
/* | |||||
* 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.slots.statistic.data; | |||||
import java.util.Set; | |||||
import java.util.concurrent.atomic.AtomicInteger; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; | |||||
import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.cache.ConcurrentLinkedHashMapWrapper; | |||||
/** | |||||
* Represents metric bucket of frequent parameters in a period of time window. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamMapBucket { | |||||
private final CacheMap<Object, AtomicInteger>[] data; | |||||
@SuppressWarnings("unchecked") | |||||
public ParamMapBucket() { | |||||
RollingParamEvent[] events = RollingParamEvent.values(); | |||||
this.data = new CacheMap[events.length]; | |||||
for (RollingParamEvent event : events) { | |||||
data[event.ordinal()] = new ConcurrentLinkedHashMapWrapper<Object, AtomicInteger>(DEFAULT_MAX_CAPACITY); | |||||
} | |||||
} | |||||
public void reset() { | |||||
for (RollingParamEvent event : RollingParamEvent.values()) { | |||||
data[event.ordinal()].clear(); | |||||
} | |||||
} | |||||
public int get(RollingParamEvent event, Object value) { | |||||
AtomicInteger counter = data[event.ordinal()].get(value); | |||||
return counter == null ? 0 : counter.intValue(); | |||||
} | |||||
public ParamMapBucket add(RollingParamEvent event, int count, Object value) { | |||||
data[event.ordinal()].putIfAbsent(value, new AtomicInteger()); | |||||
AtomicInteger counter = data[event.ordinal()].get(value); | |||||
counter.addAndGet(count); | |||||
return this; | |||||
} | |||||
public Set<Object> ascendingKeySet(RollingParamEvent type) { | |||||
return data[type.ordinal()].ascendingKeySet(); | |||||
} | |||||
public static final int DEFAULT_MAX_CAPACITY = 200; | |||||
} |
@@ -0,0 +1,127 @@ | |||||
/* | |||||
* 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.slots.statistic.metric; | |||||
import java.util.ArrayList; | |||||
import java.util.Collections; | |||||
import java.util.Comparator; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Map.Entry; | |||||
import java.util.Set; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.ParamMapBucket; | |||||
/** | |||||
* The fundamental data structure for frequent parameters statistics in a time window. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||||
private int intervalInSec; | |||||
public HotParameterLeapArray(int windowLengthInMs, int intervalInSec) { | |||||
super(windowLengthInMs, intervalInSec); | |||||
this.intervalInSec = intervalInSec; | |||||
} | |||||
public int getIntervalInSec() { | |||||
return intervalInSec; | |||||
} | |||||
@Override | |||||
public ParamMapBucket newEmptyBucket() { | |||||
return new ParamMapBucket(); | |||||
} | |||||
@Override | |||||
protected WindowWrap<ParamMapBucket> resetWindowTo(WindowWrap<ParamMapBucket> w, long startTime) { | |||||
w.resetTo(startTime); | |||||
w.value().reset(); | |||||
return w; | |||||
} | |||||
public void addValue(RollingParamEvent event, int count, Object value) { | |||||
currentWindow().value().add(event, count, value); | |||||
} | |||||
public Map<Object, Double> getTopValues(RollingParamEvent event, int number) { | |||||
currentWindow(); | |||||
List<ParamMapBucket> buckets = this.values(); | |||||
Map<Object, Integer> result = new HashMap<Object, Integer>(); | |||||
for (ParamMapBucket b : buckets) { | |||||
Set<Object> subSet = b.ascendingKeySet(event); | |||||
for (Object o : subSet) { | |||||
Integer count = result.get(o); | |||||
if (count == null) { | |||||
count = b.get(event, o); | |||||
} else { | |||||
count += b.get(event, o); | |||||
} | |||||
result.put(o, count); | |||||
} | |||||
} | |||||
// After merge, get the top set one. | |||||
Set<Entry<Object, Integer>> set = result.entrySet(); | |||||
List<Entry<Object, Integer>> list = new ArrayList<Entry<Object, Integer>>(set); | |||||
Collections.sort(list, new Comparator<Entry<Object, Integer>>() { | |||||
@Override | |||||
public int compare(Entry<Object, Integer> a, | |||||
Entry<Object, Integer> b) { | |||||
return (b.getValue() == null ? 0 : b.getValue()) - (a.getValue() == null ? 0 : a.getValue()); | |||||
} | |||||
}); | |||||
Map<Object, Double> doubleResult = new HashMap<Object, Double>(); | |||||
int size = list.size() > number ? number : list.size(); | |||||
for (int i = 0; i < size; i++) { | |||||
Map.Entry<Object, Integer> x = list.get(i); | |||||
if (x.getValue() == 0) { | |||||
break; | |||||
} | |||||
doubleResult.put(x.getKey(), ((double)x.getValue()) / getIntervalInSec()); | |||||
} | |||||
return doubleResult; | |||||
} | |||||
public long getRollingSum(RollingParamEvent event, Object value) { | |||||
currentWindow(); | |||||
long sum = 0; | |||||
List<ParamMapBucket> buckets = this.values(); | |||||
for (ParamMapBucket b : buckets) { | |||||
sum += b.get(event, value); | |||||
} | |||||
return sum; | |||||
} | |||||
public double getRollingAvg(RollingParamEvent event, Object value) { | |||||
return ((double) getRollingSum(event, value)) / getIntervalInSec(); | |||||
} | |||||
} |
@@ -0,0 +1,3 @@ | |||||
com.alibaba.csp.sentinel.command.handler.GetParamFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler | |||||
com.alibaba.csp.sentinel.command.handler.FetchTopParamsCommandHandler |
@@ -0,0 +1 @@ | |||||
com.alibaba.csp.sentinel.init.ParamFlowStatisticSlotCallbackInit |
@@ -0,0 +1 @@ | |||||
com.alibaba.csp.sentinel.slots.HotParamSlotChainBuilder |
@@ -0,0 +1,193 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.Arrays; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
/** | |||||
* Test cases for {@link ParamFlowChecker}. | |||||
* | |||||
* @author Eric Zhao | |||||
*/ | |||||
public class ParamFlowCheckerTest { | |||||
@Test | |||||
public void testHotParamCheckerPassCheckExceedArgs() { | |||||
final String resourceName = "testHotParamCheckerPassCheckExceedArgs"; | |||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
int paramIdx = 1; | |||||
ParamFlowRule rule = new ParamFlowRule(); | |||||
rule.setResource(resourceName); | |||||
rule.setCount(10); | |||||
rule.setParamIdx(paramIdx); | |||||
assertTrue("The rule will pass if the paramIdx exceeds provided args", | |||||
ParamFlowChecker.passCheck(resourceWrapper, rule, 1, "abc")); | |||||
} | |||||
@Test | |||||
public void testSingleValueCheckQpsWithoutExceptionItems() { | |||||
final String resourceName = "testSingleValueCheckQpsWithoutExceptionItems"; | |||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
int paramIdx = 0; | |||||
long threshold = 5L; | |||||
ParamFlowRule rule = new ParamFlowRule(); | |||||
rule.setResource(resourceName); | |||||
rule.setCount(threshold); | |||||
rule.setParamIdx(paramIdx); | |||||
String valueA = "valueA"; | |||||
String valueB = "valueB"; | |||||
ParameterMetric metric = mock(ParameterMetric.class); | |||||
when(metric.getPassParamQps(paramIdx, valueA)).thenReturn((double)threshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)threshold + 1); | |||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); | |||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); | |||||
} | |||||
@Test | |||||
public void testSingleValueCheckQpsWithExceptionItems() { | |||||
final String resourceName = "testSingleValueCheckQpsWithExceptionItems"; | |||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
int paramIdx = 0; | |||||
long globalThreshold = 5L; | |||||
int thresholdB = 3; | |||||
int thresholdD = 7; | |||||
ParamFlowRule rule = new ParamFlowRule(); | |||||
rule.setResource(resourceName); | |||||
rule.setCount(globalThreshold); | |||||
rule.setParamIdx(paramIdx); | |||||
String valueA = "valueA"; | |||||
String valueB = "valueB"; | |||||
String valueC = "valueC"; | |||||
String valueD = "valueD"; | |||||
// Directly set parsed map for test. | |||||
Map<Object, Integer> map = new HashMap<Object, Integer>(); | |||||
map.put(valueB, thresholdB); | |||||
map.put(valueD, thresholdD); | |||||
rule.setParsedHotItems(map); | |||||
ParameterMetric metric = mock(ParameterMetric.class); | |||||
when(metric.getPassParamQps(paramIdx, valueA)).thenReturn((double)globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, valueC)).thenReturn((double)globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, valueD)).thenReturn((double)globalThreshold + 1); | |||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); | |||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC)); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); | |||||
when(metric.getPassParamQps(paramIdx, valueA)).thenReturn((double)globalThreshold); | |||||
when(metric.getPassParamQps(paramIdx, valueB)).thenReturn((double)thresholdB - 1L); | |||||
when(metric.getPassParamQps(paramIdx, valueC)).thenReturn((double)globalThreshold + 1); | |||||
when(metric.getPassParamQps(paramIdx, valueD)).thenReturn((double)globalThreshold - 1) | |||||
.thenReturn((double)thresholdD); | |||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueA)); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueB)); | |||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueC)); | |||||
assertTrue(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); | |||||
assertFalse(ParamFlowChecker.passSingleValueCheck(resourceWrapper, rule, 1, valueD)); | |||||
} | |||||
@Test | |||||
public void testPassLocalCheckForCollection() { | |||||
final String resourceName = "testPassLocalCheckForCollection"; | |||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
int paramIdx = 0; | |||||
double globalThreshold = 10; | |||||
ParamFlowRule rule = new ParamFlowRule(resourceName) | |||||
.setParamIdx(paramIdx) | |||||
.setCount(globalThreshold); | |||||
String v1 = "a", v2 = "B", v3 = "Cc"; | |||||
List<String> list = Arrays.asList(v1, v2, v3); | |||||
ParameterMetric metric = mock(ParameterMetric.class); | |||||
when(metric.getPassParamQps(paramIdx, v1)).thenReturn(globalThreshold - 2) | |||||
.thenReturn(globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, v2)).thenReturn(globalThreshold - 2) | |||||
.thenReturn(globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, v3)).thenReturn(globalThreshold - 1) | |||||
.thenReturn(globalThreshold); | |||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric); | |||||
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list)); | |||||
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, list)); | |||||
} | |||||
@Test | |||||
public void testPassLocalCheckForArray() { | |||||
final String resourceName = "testPassLocalCheckForArray"; | |||||
final ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
int paramIdx = 0; | |||||
double globalThreshold = 10; | |||||
ParamFlowRule rule = new ParamFlowRule(resourceName) | |||||
.setParamIdx(paramIdx) | |||||
.setCount(globalThreshold); | |||||
String v1 = "a", v2 = "B", v3 = "Cc"; | |||||
Object arr = new String[] {v1, v2, v3}; | |||||
ParameterMetric metric = mock(ParameterMetric.class); | |||||
when(metric.getPassParamQps(paramIdx, v1)).thenReturn(globalThreshold - 2) | |||||
.thenReturn(globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, v2)).thenReturn(globalThreshold - 2) | |||||
.thenReturn(globalThreshold - 1); | |||||
when(metric.getPassParamQps(paramIdx, v3)).thenReturn(globalThreshold - 1) | |||||
.thenReturn(globalThreshold); | |||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric); | |||||
assertTrue(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr)); | |||||
assertFalse(ParamFlowChecker.passCheck(resourceWrapper, rule, 1, arr)); | |||||
} | |||||
@Before | |||||
public void setUp() throws Exception { | |||||
ParamFlowSlot.getMetricsMap().clear(); | |||||
} | |||||
@After | |||||
public void tearDown() throws Exception { | |||||
ParamFlowSlot.getMetricsMap().clear(); | |||||
} | |||||
} |
@@ -0,0 +1,192 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.ArrayList; | |||||
import java.util.Arrays; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* Test cases for {@link ParamFlowRuleManager}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowRuleManagerTest { | |||||
@Before | |||||
public void setUp() { | |||||
ParamFlowRuleManager.loadRules(null); | |||||
} | |||||
@After | |||||
public void tearDown() { | |||||
ParamFlowRuleManager.loadRules(null); | |||||
} | |||||
@Test | |||||
public void testLoadHotParamRulesClearingUnusedMetrics() { | |||||
final String resA = "resA"; | |||||
ParamFlowRule ruleA = new ParamFlowRule(resA) | |||||
.setCount(1) | |||||
.setParamIdx(0); | |||||
ParamFlowRuleManager.loadRules(Collections.singletonList(ruleA)); | |||||
ParamFlowSlot.getMetricsMap().put(new StringResourceWrapper(resA, EntryType.IN), new ParameterMetric()); | |||||
assertNotNull(ParamFlowSlot.getHotParamMetricForName(resA)); | |||||
final String resB = "resB"; | |||||
ParamFlowRule ruleB = new ParamFlowRule(resB) | |||||
.setCount(2) | |||||
.setParamIdx(1); | |||||
ParamFlowRuleManager.loadRules(Collections.singletonList(ruleB)); | |||||
assertNull("The unused hot param metric should be cleared", ParamFlowSlot.getHotParamMetricForName(resA)); | |||||
} | |||||
@Test | |||||
public void testLoadHotParamRulesAndGet() { | |||||
final String resA = "abc"; | |||||
final String resB = "foo"; | |||||
final String resC = "baz"; | |||||
// Rule A to C is for resource A. | |||||
// Rule A is invalid. | |||||
ParamFlowRule ruleA = new ParamFlowRule(resA).setCount(10); | |||||
ParamFlowRule ruleB = new ParamFlowRule(resA) | |||||
.setCount(28) | |||||
.setParamIdx(1); | |||||
ParamFlowRule ruleC = new ParamFlowRule(resA) | |||||
.setCount(8) | |||||
.setParamIdx(1) | |||||
.setBlockGrade(RuleConstant.FLOW_GRADE_QPS); | |||||
// Rule D is for resource B. | |||||
ParamFlowRule ruleD = new ParamFlowRule(resB) | |||||
.setCount(9) | |||||
.setParamIdx(0) | |||||
.setParamFlowItemList(Arrays.asList(ParamFlowItem.newItem(7L, 6), ParamFlowItem.newItem(9L, 4))); | |||||
ParamFlowRuleManager.loadRules(Arrays.asList(ruleA, ruleB, ruleC, ruleD)); | |||||
// Test for ParamFlowRuleManager#hasRules | |||||
assertTrue(ParamFlowRuleManager.hasRules(resA)); | |||||
assertTrue(ParamFlowRuleManager.hasRules(resB)); | |||||
assertFalse(ParamFlowRuleManager.hasRules(resC)); | |||||
// Test for ParamFlowRuleManager#getRulesOfResource | |||||
List<ParamFlowRule> rulesForResA = ParamFlowRuleManager.getRulesOfResource(resA); | |||||
assertEquals(2, rulesForResA.size()); | |||||
assertFalse(rulesForResA.contains(ruleA)); | |||||
assertTrue(rulesForResA.contains(ruleB)); | |||||
assertTrue(rulesForResA.contains(ruleC)); | |||||
List<ParamFlowRule> rulesForResB = ParamFlowRuleManager.getRulesOfResource(resB); | |||||
assertEquals(1, rulesForResB.size()); | |||||
assertEquals(ruleD, rulesForResB.get(0)); | |||||
// Test for ParamFlowRuleManager#getRules | |||||
List<ParamFlowRule> allRules = ParamFlowRuleManager.getRules(); | |||||
assertFalse(allRules.contains(ruleA)); | |||||
assertTrue(allRules.contains(ruleB)); | |||||
assertTrue(allRules.contains(ruleC)); | |||||
assertTrue(allRules.contains(ruleD)); | |||||
} | |||||
@Test | |||||
public void testParseHotParamExceptionItemsFailure() { | |||||
String valueB = "Sentinel"; | |||||
Integer valueC = 6; | |||||
char valueD = 6; | |||||
float valueE = 11.11f; | |||||
// Null object will not be parsed. | |||||
ParamFlowItem itemA = new ParamFlowItem(null, 1, double.class.getName()); | |||||
// Hot item with empty class type will be treated as string. | |||||
ParamFlowItem itemB = new ParamFlowItem(valueB, 3, null); | |||||
ParamFlowItem itemE = new ParamFlowItem(String.valueOf(valueE), 3, ""); | |||||
// Bad count will not be parsed. | |||||
ParamFlowItem itemC = ParamFlowItem.newItem(valueC, -5); | |||||
ParamFlowItem itemD = new ParamFlowItem(String.valueOf(valueD), null, char.class.getName()); | |||||
List<ParamFlowItem> badItems = Arrays.asList(itemA, itemB, itemC, itemD, itemE); | |||||
Map<Object, Integer> parsedItems = ParamFlowRuleManager.parseHotItems(badItems); | |||||
// Value B and E will be parsed, but ignoring the type. | |||||
assertEquals(2, parsedItems.size()); | |||||
assertEquals(itemB.getCount(), parsedItems.get(valueB)); | |||||
assertFalse(parsedItems.containsKey(valueE)); | |||||
assertEquals(itemE.getCount(), parsedItems.get(String.valueOf(valueE))); | |||||
} | |||||
@Test | |||||
public void testParseHotParamExceptionItemsSuccess() { | |||||
// Test for empty list. | |||||
assertEquals(0, ParamFlowRuleManager.parseHotItems(null).size()); | |||||
assertEquals(0, ParamFlowRuleManager.parseHotItems(new ArrayList<ParamFlowItem>()).size()); | |||||
// Test for boxing objects and primitive types. | |||||
Double valueA = 1.1d; | |||||
String valueB = "Sentinel"; | |||||
Integer valueC = 6; | |||||
char valueD = 'c'; | |||||
ParamFlowItem itemA = ParamFlowItem.newItem(valueA, 1); | |||||
ParamFlowItem itemB = ParamFlowItem.newItem(valueB, 3); | |||||
ParamFlowItem itemC = ParamFlowItem.newItem(valueC, 5); | |||||
ParamFlowItem itemD = new ParamFlowItem().setObject(String.valueOf(valueD)) | |||||
.setClassType(char.class.getName()) | |||||
.setCount(7); | |||||
List<ParamFlowItem> items = Arrays.asList(itemA, itemB, itemC, itemD); | |||||
Map<Object, Integer> parsedItems = ParamFlowRuleManager.parseHotItems(items); | |||||
assertEquals(itemA.getCount(), parsedItems.get(valueA)); | |||||
assertEquals(itemB.getCount(), parsedItems.get(valueB)); | |||||
assertEquals(itemC.getCount(), parsedItems.get(valueC)); | |||||
assertEquals(itemD.getCount(), parsedItems.get(valueD)); | |||||
} | |||||
@Test | |||||
public void testCheckValidHotParamRule() { | |||||
// Null or empty resource; | |||||
ParamFlowRule rule1 = new ParamFlowRule(); | |||||
ParamFlowRule rule2 = new ParamFlowRule(""); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(null)); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(rule1)); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(rule2)); | |||||
// Invalid threshold count. | |||||
ParamFlowRule rule3 = new ParamFlowRule("abc") | |||||
.setCount(-1) | |||||
.setParamIdx(1); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(rule3)); | |||||
// Parameter index not set or invalid. | |||||
ParamFlowRule rule4 = new ParamFlowRule("abc") | |||||
.setCount(1); | |||||
ParamFlowRule rule5 = new ParamFlowRule("abc") | |||||
.setCount(1) | |||||
.setParamIdx(-1); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(rule4)); | |||||
assertFalse(ParamFlowRuleManager.isValidRule(rule5)); | |||||
ParamFlowRule goodRule = new ParamFlowRule("abc") | |||||
.setCount(10) | |||||
.setParamIdx(1); | |||||
assertTrue(ParamFlowRuleManager.isValidRule(goodRule)); | |||||
} | |||||
} |
@@ -0,0 +1,118 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.Collections; | |||||
import com.alibaba.csp.sentinel.EntryType; | |||||
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; | |||||
import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; | |||||
import org.junit.After; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
/** | |||||
* Test cases for {@link ParamFlowSlot}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamFlowSlotTest { | |||||
private final ParamFlowSlot paramFlowSlot = new ParamFlowSlot(); | |||||
@Test | |||||
public void testEntryWhenParamFlowRuleNotExists() throws Throwable { | |||||
String resourceName = "testEntryWhenParamFlowRuleNotExists"; | |||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, "abc"); | |||||
// The parameter metric instance will not be created. | |||||
assertNull(ParamFlowSlot.getParamMetric(resourceWrapper)); | |||||
} | |||||
@Test | |||||
public void testEntryWhenParamFlowExists() throws Throwable { | |||||
String resourceName = "testEntryWhenParamFlowExists"; | |||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
long argToGo = 1L; | |||||
double count = 10; | |||||
ParamFlowRule rule = new ParamFlowRule(resourceName) | |||||
.setCount(count) | |||||
.setParamIdx(0); | |||||
ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); | |||||
ParameterMetric metric = mock(ParameterMetric.class); | |||||
// First pass, then blocked. | |||||
when(metric.getPassParamQps(rule.getParamIdx(), argToGo)) | |||||
.thenReturn(count - 1) | |||||
.thenReturn(count); | |||||
// Insert the mock metric to control pass or block. | |||||
ParamFlowSlot.getMetricsMap().put(resourceWrapper, metric); | |||||
// The first entry will pass. | |||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, argToGo); | |||||
// The second entry will be blocked. | |||||
try { | |||||
paramFlowSlot.entry(null, resourceWrapper, null, 1, argToGo); | |||||
} catch (ParamFlowException ex) { | |||||
assertEquals(String.valueOf(argToGo), ex.getMessage()); | |||||
assertEquals(resourceName, ex.getResourceName()); | |||||
return; | |||||
} | |||||
fail("The second entry should be blocked"); | |||||
} | |||||
@Test | |||||
public void testGetNullParamMetric() { | |||||
assertNull(ParamFlowSlot.getParamMetric(null)); | |||||
} | |||||
@Test | |||||
public void testInitParamMetrics() { | |||||
int index = 1; | |||||
String resourceName = "res-" + System.currentTimeMillis(); | |||||
ResourceWrapper resourceWrapper = new StringResourceWrapper(resourceName, EntryType.IN); | |||||
assertNull(ParamFlowSlot.getParamMetric(resourceWrapper)); | |||||
paramFlowSlot.initHotParamMetricsFor(resourceWrapper, index); | |||||
ParameterMetric metric = ParamFlowSlot.getParamMetric(resourceWrapper); | |||||
assertNotNull(metric); | |||||
assertNotNull(metric.getRollingParameters().get(index)); | |||||
// Duplicate init. | |||||
paramFlowSlot.initHotParamMetricsFor(resourceWrapper, index); | |||||
assertSame(metric, ParamFlowSlot.getParamMetric(resourceWrapper)); | |||||
} | |||||
@Before | |||||
public void setUp() throws Exception { | |||||
ParamFlowRuleManager.loadRules(null); | |||||
ParamFlowSlot.getMetricsMap().clear(); | |||||
} | |||||
@After | |||||
public void tearDown() throws Exception { | |||||
// Clean the metrics map. | |||||
ParamFlowSlot.getMetricsMap().clear(); | |||||
ParamFlowRuleManager.loadRules(null); | |||||
} | |||||
} |
@@ -0,0 +1,75 @@ | |||||
/* | |||||
* 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.slots.block.flow.param; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
/** | |||||
* Test cases for {@link ParameterMetric}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParameterMetricTest { | |||||
@Test | |||||
public void testGetTopParamCount() { | |||||
ParameterMetric metric = new ParameterMetric(); | |||||
int index = 1; | |||||
int n = 10; | |||||
RollingParamEvent event = RollingParamEvent.REQUEST_PASSED; | |||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||||
Map<Object, Double> topValues = new HashMap<Object, Double>() {{ | |||||
put("a", 3d); | |||||
put("b", 7d); | |||||
}}; | |||||
when(leapArray.getTopValues(event, n)).thenReturn(topValues); | |||||
// Get when not initialized. | |||||
assertEquals(0, metric.getTopPassParamCount(index, n).size()); | |||||
metric.getRollingParameters().put(index, leapArray); | |||||
assertEquals(topValues, metric.getTopPassParamCount(index, n)); | |||||
} | |||||
@Test | |||||
public void testInitAndClearHotParameterMetric() { | |||||
ParameterMetric metric = new ParameterMetric(); | |||||
int index = 1; | |||||
metric.initializeForIndex(index); | |||||
HotParameterLeapArray leapArray = metric.getRollingParameters().get(index); | |||||
assertNotNull(leapArray); | |||||
metric.initializeForIndex(index); | |||||
assertSame(leapArray, metric.getRollingParameters().get(index)); | |||||
metric.clear(); | |||||
assertEquals(0, metric.getRollingParameters().size()); | |||||
} | |||||
private static final int PARAM_TYPE_NORMAL = 0; | |||||
private static final int PARAM_TYPE_ARRAY = 1; | |||||
private static final int PARAM_TYPE_COLLECTION = 2; | |||||
} |
@@ -0,0 +1,77 @@ | |||||
/* | |||||
* 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.slots.statistic.data; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
/** | |||||
* Test cases for {@link ParamMapBucket}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class ParamMapBucketTest { | |||||
@Test | |||||
public void testAddEviction() { | |||||
ParamMapBucket bucket = new ParamMapBucket(); | |||||
for (int i = 0; i < ParamMapBucket.DEFAULT_MAX_CAPACITY; i++) { | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, "param-" + i); | |||||
} | |||||
String lastParam = "param-end"; | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, lastParam); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-0")); | |||||
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, "param-1")); | |||||
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, lastParam)); | |||||
} | |||||
@Test | |||||
public void testAddGetResetCommon() { | |||||
ParamMapBucket bucket = new ParamMapBucket(); | |||||
double paramA = 1.1d; | |||||
double paramB = 2.2d; | |||||
double paramC = -19.7d; | |||||
// Block: A 5 | B 1 | C 6 | |||||
// Pass: A 0 | B 1 | C 7 | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 3, paramA); | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramB); | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 1, paramB); | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramA); | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, 6, paramC); | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 4, paramC); | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, 1, paramC); | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, 2, paramC); | |||||
assertEquals(5, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA)); | |||||
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB)); | |||||
assertEquals(6, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA)); | |||||
assertEquals(1, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB)); | |||||
assertEquals(7, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC)); | |||||
bucket.reset(); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramA)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramB)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_BLOCKED, paramC)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramA)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramB)); | |||||
assertEquals(0, bucket.get(RollingParamEvent.REQUEST_PASSED, paramC)); | |||||
} | |||||
} |
@@ -0,0 +1,132 @@ | |||||
/* | |||||
* 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.slots.statistic.metric; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.param.RollingParamEvent; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.ParamMapBucket; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.*; | |||||
import static org.mockito.Mockito.*; | |||||
/** | |||||
* Test cases for {@link HotParameterLeapArray}. | |||||
* | |||||
* @author Eric Zhao | |||||
* @since 0.2.0 | |||||
*/ | |||||
public class HotParameterLeapArrayTest { | |||||
@Test | |||||
public void testAddValueToBucket() { | |||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||||
String paramA = "paramA"; | |||||
int initialCountA = 3; | |||||
RollingParamEvent passEvent = RollingParamEvent.REQUEST_PASSED; | |||||
final ParamMapBucket bucket = new ParamMapBucket(); | |||||
bucket.add(passEvent, initialCountA, paramA); | |||||
doCallRealMethod().when(leapArray).addValue(any(RollingParamEvent.class), anyInt(), any(Object.class)); | |||||
when(leapArray.currentWindow()).thenReturn(new WindowWrap<ParamMapBucket>(0, 0, bucket)); | |||||
assertEquals(initialCountA, leapArray.currentWindow().value().get(passEvent, paramA)); | |||||
int delta = 2; | |||||
leapArray.addValue(passEvent, delta, paramA); | |||||
assertEquals(initialCountA + delta, leapArray.currentWindow().value().get(passEvent, paramA)); | |||||
} | |||||
@Test | |||||
public void testGetTopValues() { | |||||
int intervalInSec = 2; | |||||
int a1 = 3, a2 = 5; | |||||
String paramPrefix = "param-"; | |||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||||
when(leapArray.getIntervalInSec()).thenReturn(intervalInSec); | |||||
final ParamMapBucket b1 = generateBucket(a1, paramPrefix); | |||||
final ParamMapBucket b2 = generateBucket(a2, paramPrefix); | |||||
List<ParamMapBucket> buckets = new ArrayList<ParamMapBucket>() {{ | |||||
add(b1); | |||||
add(b2); | |||||
}}; | |||||
when(leapArray.values()).thenReturn(buckets); | |||||
when(leapArray.getTopValues(any(RollingParamEvent.class), any(int.class))).thenCallRealMethod(); | |||||
Map<Object, Double> top2Values = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a1 - 1); | |||||
// Top 2 should be 5 and 3 | |||||
assertEquals((double)5 * 10 / intervalInSec, top2Values.get(paramPrefix + 5), 0.01); | |||||
assertEquals((double)3 * 20 / intervalInSec, top2Values.get(paramPrefix + 3), 0.01); | |||||
Map<Object, Double> top4Values = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a2 - 1); | |||||
assertEquals(a2 - 1, top4Values.size()); | |||||
assertFalse(top4Values.containsKey(paramPrefix + 1)); | |||||
Map<Object, Double> topMoreValues = leapArray.getTopValues(RollingParamEvent.REQUEST_PASSED, a2 + 1); | |||||
assertEquals("This should contain all parameters but no more than " + a2, a2, topMoreValues.size()); | |||||
} | |||||
private ParamMapBucket generateBucket(int amount, String prefix) { | |||||
if (amount <= 0) { | |||||
throw new IllegalArgumentException("Bad amount"); | |||||
} | |||||
ParamMapBucket bucket = new ParamMapBucket(); | |||||
for (int i = 1; i <= amount; i++) { | |||||
bucket.add(RollingParamEvent.REQUEST_PASSED, i * 10, prefix + i); | |||||
bucket.add(RollingParamEvent.REQUEST_BLOCKED, i, prefix + i); | |||||
} | |||||
return bucket; | |||||
} | |||||
@Test | |||||
public void testGetRollingSum() { | |||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||||
String v1 = "a", v2 = "B", v3 = "Cc"; | |||||
int p1a = 19, p1b = 3; | |||||
int p2a = 6, p2c = 17; | |||||
RollingParamEvent passEvent = RollingParamEvent.REQUEST_PASSED; | |||||
final ParamMapBucket b1 = new ParamMapBucket() | |||||
.add(passEvent, p1a, v1) | |||||
.add(passEvent, p1b, v2); | |||||
final ParamMapBucket b2 = new ParamMapBucket() | |||||
.add(passEvent, p2a, v1) | |||||
.add(passEvent, p2c, v3); | |||||
List<ParamMapBucket> buckets = new ArrayList<ParamMapBucket>() {{ add(b1); add(b2); }}; | |||||
when(leapArray.values()).thenReturn(buckets); | |||||
when(leapArray.getRollingSum(any(RollingParamEvent.class), any(Object.class))).thenCallRealMethod(); | |||||
assertEquals(p1a + p2a, leapArray.getRollingSum(passEvent, v1)); | |||||
assertEquals(p1b, leapArray.getRollingSum(passEvent, v2)); | |||||
assertEquals(p2c, leapArray.getRollingSum(passEvent, v3)); | |||||
} | |||||
@Test | |||||
public void testGetRollingAvg() { | |||||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||||
when(leapArray.getRollingSum(any(RollingParamEvent.class), any(Object.class))).thenReturn(15L); | |||||
when(leapArray.getIntervalInSec()).thenReturn(1) | |||||
.thenReturn(2); | |||||
when(leapArray.getRollingAvg(any(RollingParamEvent.class), any(Object.class))).thenCallRealMethod(); | |||||
assertEquals(15.0d, leapArray.getRollingAvg(RollingParamEvent.REQUEST_PASSED, "abc"), 0.001); | |||||
assertEquals(15.0d / 2, leapArray.getRollingAvg(RollingParamEvent.REQUEST_PASSED, "abc"), 0.001); | |||||
} | |||||
} |