* Rename: MetricsLeapArray -> BucketLeapArray * Add implementation for `FutureBucketLeapArray`, a kind of `BucketLeapArray` that only reserves for future buckets, which is used for calculating occupied future tokens. * Add OccupiableBucketLeapArray that combines common BucketLeapArray with FutureBucketLeapArray. The rollingNumberInSecond in StatisticNode now uses OccupiableBucketLeapArray by default. * Add OccupySupport interface. Node now implements OccupySupport interface. * Add occupy-related methods in Metric and ArrayMetric. * Handle prioritized requests in default traffic shaping controller. * Update default occupyTimeout to 500ms Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -40,7 +40,7 @@ public class ClusterMetricLeapArray extends LeapArray<ClusterMetricBucket> { | |||||
} | } | ||||
@Override | @Override | ||||
public ClusterMetricBucket newEmptyBucket() { | |||||
public ClusterMetricBucket newEmptyBucket(long timeMillis) { | |||||
return new ClusterMetricBucket(); | return new ClusterMetricBucket(); | ||||
} | } | ||||
@@ -37,7 +37,7 @@ public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>> | |||||
} | } | ||||
@Override | @Override | ||||
public CacheMap<Object, C> newEmptyBucket() { | |||||
public CacheMap<Object, C> newEmptyBucket(long timeMillis) { | |||||
return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); | return new ConcurrentLinkedHashMapWrapper<>(maxCapacity); | ||||
} | } | ||||
@@ -48,5 +48,4 @@ public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>> | |||||
return w; | return w; | ||||
} | } | ||||
} | } |
@@ -28,7 +28,7 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport; | |||||
* @author leyou | * @author leyou | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
*/ | */ | ||||
public interface Node extends DebugSupport { | |||||
public interface Node extends OccupySupport, DebugSupport { | |||||
/** | /** | ||||
* Get incoming request per minute ({@code pass + block}). | * Get incoming request per minute ({@code pass + block}). | ||||
@@ -0,0 +1,70 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.node; | |||||
/** | |||||
* @author Eric Zhao | |||||
* @since 1.5.0 | |||||
*/ | |||||
public interface OccupySupport { | |||||
/** | |||||
* Try to occupy latter time windows' tokens. If occupy success, a value less than | |||||
* {@code occupyTimeout} in {@link OccupyTimeoutProperty} will be return. | |||||
* | |||||
* <p> | |||||
* Each time we occupy tokens of the future window, current thread should sleep for the | |||||
* corresponding time for smoothing QPS. We can't occupy tokens of the future with unlimited, | |||||
* the sleep time limit is {@code occupyTimeout} in {@link OccupyTimeoutProperty}. | |||||
* </p> | |||||
* | |||||
* @param currentTime current time millis. | |||||
* @param acquireCount tokens count to acquire. | |||||
* @param threshold qps threshold. | |||||
* @return time should sleep. Time >= {@code occupyTimeout} in {@link OccupyTimeoutProperty} means | |||||
* occupy fail, in this case, the request should be rejected immediately. | |||||
*/ | |||||
long tryOccupyNext(long currentTime, int acquireCount, double threshold); | |||||
/** | |||||
* Get current waiting amount. Useful for debug. | |||||
* | |||||
* @return current waiting amount | |||||
*/ | |||||
long waiting(); | |||||
/** | |||||
* Add request that occupied. | |||||
* | |||||
* @param futureTime future timestamp that the acquireCount should be added on. | |||||
* @param acquireCount tokens count. | |||||
*/ | |||||
void addWaitingRequest(long futureTime, int acquireCount); | |||||
/** | |||||
* Add occupied pass request, which represents pass requests that borrow the latter windows' token. | |||||
* | |||||
* @param acquireCount tokens count. | |||||
*/ | |||||
void addOccupiedPass(int acquireCount); | |||||
/** | |||||
* Get current occupied pass QPS. | |||||
* | |||||
* @return current occupied pass QPS | |||||
*/ | |||||
double occupiedPassQps(); | |||||
} |
@@ -0,0 +1,79 @@ | |||||
/* | |||||
* Copyright 1999-2019 Alibaba Group Holding Ltd. | |||||
* | |||||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||||
* you may not use this file except in compliance with the License. | |||||
* You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package com.alibaba.csp.sentinel.node; | |||||
import com.alibaba.csp.sentinel.log.RecordLog; | |||||
import com.alibaba.csp.sentinel.property.SentinelProperty; | |||||
import com.alibaba.csp.sentinel.property.SimplePropertyListener; | |||||
/** | |||||
* @author jialiang.linjl | |||||
* @author Carpenter Lee | |||||
* @since 1.5.0 | |||||
*/ | |||||
public class OccupyTimeoutProperty { | |||||
/** | |||||
* <p> | |||||
* Max occupy timeout in milliseconds. Requests with priority can occupy tokens of the future statistic | |||||
* window, and {@code occupyTimeout} limit the max time length that can be occupied. | |||||
* </p> | |||||
* <p> | |||||
* Note that the timeout value should never be greeter than {@link IntervalProperty#INTERVAL}. | |||||
* </p> | |||||
* DO NOT MODIFY this value directly, use {@link #updateTimeout(int)}, | |||||
* otherwise the modification will not take effect. | |||||
*/ | |||||
private static volatile int occupyTimeout = 500; | |||||
public static void register2Property(SentinelProperty<Integer> property) { | |||||
property.addListener(new SimplePropertyListener<Integer>() { | |||||
@Override | |||||
public void configUpdate(Integer value) { | |||||
if (value != null) { | |||||
updateTimeout(value); | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
public static int getOccupyTimeout() { | |||||
return occupyTimeout; | |||||
} | |||||
/** | |||||
* Update the timeout value.</br> | |||||
* Note that the time out should never greeter than {@link IntervalProperty#INTERVAL}, | |||||
* or it will be ignored. | |||||
* | |||||
* @param newInterval new value. | |||||
*/ | |||||
public static void updateTimeout(int newInterval) { | |||||
if (newInterval < 0) { | |||||
RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout); | |||||
return; | |||||
} | |||||
if (newInterval > IntervalProperty.INTERVAL) { | |||||
RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout | |||||
+ ", should <= " + IntervalProperty.INTERVAL); | |||||
return; | |||||
} | |||||
if (newInterval != occupyTimeout) { | |||||
occupyTimeout = newInterval; | |||||
} | |||||
RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: " + occupyTimeout); | |||||
} | |||||
} |
@@ -99,7 +99,7 @@ public class StatisticNode implements Node { | |||||
* Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds, | * Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds, | ||||
* meaning each bucket per second, in this way we can get accurate statistics of each second. | * meaning each bucket per second, in this way we can get accurate statistics of each second. | ||||
*/ | */ | ||||
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000); | |||||
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false); | |||||
/** | /** | ||||
* The counter for thread count. | * The counter for thread count. | ||||
@@ -116,7 +116,7 @@ public class StatisticNode implements Node { | |||||
// The fetch operation is thread-safe under a single-thread scheduler pool. | // The fetch operation is thread-safe under a single-thread scheduler pool. | ||||
long currentTime = TimeUtil.currentTimeMillis(); | long currentTime = TimeUtil.currentTimeMillis(); | ||||
currentTime = currentTime - currentTime % 1000; | currentTime = currentTime - currentTime % 1000; | ||||
Map<Long, MetricNode> metrics = new ConcurrentHashMap<Long, MetricNode>(); | |||||
Map<Long, MetricNode> metrics = new ConcurrentHashMap<>(); | |||||
List<MetricNode> nodesOfEverySecond = rollingCounterInMinute.details(); | List<MetricNode> nodesOfEverySecond = rollingCounterInMinute.details(); | ||||
long newLastFetchTime = lastFetchTime; | long newLastFetchTime = lastFetchTime; | ||||
// Iterate metrics of all resources, filter valid metrics (not-empty and up-to-date). | // Iterate metrics of all resources, filter valid metrics (not-empty and up-to-date). | ||||
@@ -137,7 +137,7 @@ public class StatisticNode implements Node { | |||||
private boolean isValidMetricNode(MetricNode node) { | private boolean isValidMetricNode(MetricNode node) { | ||||
return node.getPassQps() > 0 || node.getBlockQps() > 0 || node.getSuccessQps() > 0 | return node.getPassQps() > 0 || node.getBlockQps() > 0 || node.getSuccessQps() > 0 | ||||
|| node.getExceptionQps() > 0 || node.getRt() > 0; | |||||
|| node.getExceptionQps() > 0 || node.getRt() > 0 || node.getOccupiedPassQps() > 0; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -151,11 +151,6 @@ public class StatisticNode implements Node { | |||||
return totalRequest; | return totalRequest; | ||||
} | } | ||||
@Override | |||||
public long totalPass() { | |||||
return rollingCounterInMinute.pass(); | |||||
} | |||||
@Override | @Override | ||||
public long blockRequest() { | public long blockRequest() { | ||||
return rollingCounterInMinute.block(); | return rollingCounterInMinute.block(); | ||||
@@ -201,6 +196,11 @@ public class StatisticNode implements Node { | |||||
return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec(); | return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec(); | ||||
} | } | ||||
@Override | |||||
public long totalPass() { | |||||
return rollingCounterInMinute.pass(); | |||||
} | |||||
@Override | @Override | ||||
public double successQps() { | public double successQps() { | ||||
return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec(); | return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec(); | ||||
@@ -211,6 +211,11 @@ public class StatisticNode implements Node { | |||||
return rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount(); | return rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount(); | ||||
} | } | ||||
@Override | |||||
public double occupiedPassQps() { | |||||
return rollingCounterInSecond.occupiedPass() / rollingCounterInSecond.getWindowIntervalInSec(); | |||||
} | |||||
@Override | @Override | ||||
public double avgRt() { | public double avgRt() { | ||||
long successCount = rollingCounterInSecond.success(); | long successCount = rollingCounterInSecond.success(); | ||||
@@ -256,7 +261,6 @@ public class StatisticNode implements Node { | |||||
public void increaseExceptionQps(int count) { | public void increaseExceptionQps(int count) { | ||||
rollingCounterInSecond.addException(count); | rollingCounterInSecond.addException(count); | ||||
rollingCounterInMinute.addException(count); | rollingCounterInMinute.addException(count); | ||||
} | } | ||||
@Override | @Override | ||||
@@ -271,6 +275,57 @@ public class StatisticNode implements Node { | |||||
@Override | @Override | ||||
public void debug() { | public void debug() { | ||||
rollingCounterInSecond.debugQps(); | |||||
rollingCounterInSecond.debug(); | |||||
} | |||||
@Override | |||||
public long tryOccupyNext(long currentTime, int acquireCount, double threshold) { | |||||
double maxCount = threshold * IntervalProperty.INTERVAL / 1000; | |||||
long currentBorrow = rollingCounterInSecond.waiting(); | |||||
if (currentBorrow >= maxCount) { | |||||
return OccupyTimeoutProperty.getOccupyTimeout(); | |||||
} | |||||
int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT; | |||||
long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL; | |||||
int idx = 0; | |||||
/* | |||||
* Note: here {@code currentPass} may be less than it really is NOW, because time difference | |||||
* since call rollingCounterInSecond.pass(). So in high concurrency, the following code may | |||||
* lead more tokens be borrowed. | |||||
*/ | |||||
long currentPass = rollingCounterInSecond.pass(); | |||||
while (earliestTime < currentTime) { | |||||
long waitInMs = idx * windowLength + windowLength - currentTime % windowLength; | |||||
if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) { | |||||
break; | |||||
} | |||||
long windowPass = rollingCounterInSecond.getWindowPass(earliestTime); | |||||
if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) { | |||||
return waitInMs; | |||||
} | |||||
earliestTime += windowLength; | |||||
currentPass -= windowPass; | |||||
idx++; | |||||
} | |||||
return OccupyTimeoutProperty.getOccupyTimeout(); | |||||
} | |||||
@Override | |||||
public long waiting() { | |||||
return rollingCounterInSecond.waiting(); | |||||
} | |||||
@Override | |||||
public void addWaitingRequest(long futureTime, int acquireCount) { | |||||
rollingCounterInSecond.addWaiting(futureTime, acquireCount); | |||||
} | |||||
@Override | |||||
public void addOccupiedPass(int acquireCount) { | |||||
rollingCounterInMinute.addOccupiedPass(acquireCount); | |||||
rollingCounterInMinute.addPass(acquireCount); | |||||
} | } | ||||
} | } |
@@ -34,12 +34,25 @@ public class MetricNode { | |||||
private long exceptionQps; | private long exceptionQps; | ||||
private long rt; | private long rt; | ||||
/** | |||||
* @since 1.5.0 | |||||
*/ | |||||
private long occupiedPassQps; | |||||
private String resource; | private String resource; | ||||
public long getTimestamp() { | public long getTimestamp() { | ||||
return timestamp; | return timestamp; | ||||
} | } | ||||
public long getOccupiedPassQps() { | |||||
return occupiedPassQps; | |||||
} | |||||
public void setOccupiedPassQps(long occupiedPassQps) { | |||||
this.occupiedPassQps = occupiedPassQps; | |||||
} | |||||
public void setTimestamp(long timestamp) { | public void setTimestamp(long timestamp) { | ||||
this.timestamp = timestamp; | this.timestamp = timestamp; | ||||
} | } | ||||
@@ -94,22 +107,17 @@ public class MetricNode { | |||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
return "MetricNode{" + | |||||
"timestamp=" + timestamp + | |||||
", passQps=" + passQps + | |||||
", blockQps=" + blockQps + | |||||
", successQps=" + successQps + | |||||
", exceptionQps=" + exceptionQps + | |||||
", rt=" + rt + | |||||
", resource='" + resource + '\'' + | |||||
'}'; | |||||
return "MetricNode{" + "timestamp=" + timestamp + ", passQps=" + passQps + ", blockQps=" + blockQps | |||||
+ ", successQps=" + successQps + ", exceptionQps=" + exceptionQps + ", rt=" + rt | |||||
+ ", occupiedPassQps=" + occupiedPassQps + ", resource='" | |||||
+ resource + '\'' + '}'; | |||||
} | } | ||||
/** | /** | ||||
* To formatting string. All "|" in {@link #resource} will be replaced with "_", format is: | |||||
* <br/> | |||||
* To formatting string. All "|" in {@link #resource} will be replaced with | |||||
* "_", format is: <br/> | |||||
* <code> | * <code> | ||||
* timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt | |||||
* timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps | |||||
* </code> | * </code> | ||||
* | * | ||||
* @return string format of this. | * @return string format of this. | ||||
@@ -123,12 +131,13 @@ public class MetricNode { | |||||
sb.append(blockQps).append("|"); | sb.append(blockQps).append("|"); | ||||
sb.append(successQps).append("|"); | sb.append(successQps).append("|"); | ||||
sb.append(exceptionQps).append("|"); | sb.append(exceptionQps).append("|"); | ||||
sb.append(rt); | |||||
sb.append(rt).append("|"); | |||||
sb.append(occupiedPassQps); | |||||
return sb.toString(); | return sb.toString(); | ||||
} | } | ||||
/** | /** | ||||
* Parse {@link MetricNode} from thin string, see {@link #toThinString()} ()} | |||||
* Parse {@link MetricNode} from thin string, see {@link #toThinString()} | |||||
* | * | ||||
* @param line | * @param line | ||||
* @return | * @return | ||||
@@ -143,14 +152,17 @@ public class MetricNode { | |||||
node.setSuccessQps(Long.parseLong(strs[4])); | node.setSuccessQps(Long.parseLong(strs[4])); | ||||
node.setExceptionQps(Long.parseLong(strs[5])); | node.setExceptionQps(Long.parseLong(strs[5])); | ||||
node.setRt(Long.parseLong(strs[6])); | node.setRt(Long.parseLong(strs[6])); | ||||
if (strs.length == 8) { | |||||
node.setOccupiedPassQps(Long.parseLong(strs[7])); | |||||
} | |||||
return node; | return node; | ||||
} | } | ||||
/** | /** | ||||
* To formatting string. All "|" in {@link MetricNode#resource} will be replaced with "_", format is: | |||||
* <br/> | |||||
* To formatting string. All "|" in {@link MetricNode#resource} will be | |||||
* replaced with "_", format is: <br/> | |||||
* <code> | * <code> | ||||
* timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt\n | |||||
* timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps\n | |||||
* </code> | * </code> | ||||
* | * | ||||
* @return string format of this. | * @return string format of this. | ||||
@@ -167,7 +179,8 @@ public class MetricNode { | |||||
sb.append(getBlockQps()).append("|"); | sb.append(getBlockQps()).append("|"); | ||||
sb.append(getSuccessQps()).append("|"); | sb.append(getSuccessQps()).append("|"); | ||||
sb.append(getExceptionQps()).append("|"); | sb.append(getExceptionQps()).append("|"); | ||||
sb.append(getRt()); | |||||
sb.append(getRt()).append("|"); | |||||
sb.append(getOccupiedPassQps()); | |||||
sb.append('\n'); | sb.append('\n'); | ||||
return sb.toString(); | return sb.toString(); | ||||
} | } | ||||
@@ -189,6 +202,9 @@ public class MetricNode { | |||||
node.setSuccessQps(Long.parseLong(strs[5])); | node.setSuccessQps(Long.parseLong(strs[5])); | ||||
node.setExceptionQps(Long.parseLong(strs[6])); | node.setExceptionQps(Long.parseLong(strs[6])); | ||||
node.setRt(Long.parseLong(strs[7])); | node.setRt(Long.parseLong(strs[7])); | ||||
if (strs.length == 9) { | |||||
node.setOccupiedPassQps(Long.parseLong(strs[8])); | |||||
} | |||||
return node; | return node; | ||||
} | } | ||||
@@ -61,7 +61,7 @@ final class FlowRuleChecker { | |||||
return true; | return true; | ||||
} | } | ||||
return rule.getRater().canPass(selectedNode, acquireCount); | |||||
return rule.getRater().canPass(selectedNode, acquireCount, prioritized); | |||||
} | } | ||||
static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { | static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { | ||||
@@ -0,0 +1,40 @@ | |||||
/* | |||||
* 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; | |||||
/** | |||||
* An exception that marks previous prioritized request has been waiting till now, then should pass. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @since 1.5.0 | |||||
*/ | |||||
public class PriorityWaitException extends RuntimeException { | |||||
private final long waitInMs; | |||||
public PriorityWaitException(long waitInMs) { | |||||
this.waitInMs = waitInMs; | |||||
} | |||||
public long getWaitInMs() { | |||||
return waitInMs; | |||||
} | |||||
@Override | |||||
public Throwable fillInStackTrace() { | |||||
return this; | |||||
} | |||||
} |
@@ -16,13 +16,17 @@ | |||||
package com.alibaba.csp.sentinel.slots.block.flow.controller; | package com.alibaba.csp.sentinel.slots.block.flow.controller; | ||||
import com.alibaba.csp.sentinel.node.Node; | import com.alibaba.csp.sentinel.node.Node; | ||||
import com.alibaba.csp.sentinel.node.OccupyTimeoutProperty; | |||||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | import com.alibaba.csp.sentinel.slots.block.RuleConstant; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; | |||||
import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; | import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; | ||||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
/** | /** | ||||
* Default throttling controller (immediately reject strategy). | * Default throttling controller (immediately reject strategy). | ||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | |||||
*/ | */ | ||||
public class DefaultController implements TrafficShapingController { | public class DefaultController implements TrafficShapingController { | ||||
@@ -45,9 +49,22 @@ public class DefaultController implements TrafficShapingController { | |||||
public boolean canPass(Node node, int acquireCount, boolean prioritized) { | public boolean canPass(Node node, int acquireCount, boolean prioritized) { | ||||
int curCount = avgUsedTokens(node); | int curCount = avgUsedTokens(node); | ||||
if (curCount + acquireCount > count) { | if (curCount + acquireCount > count) { | ||||
if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) { | |||||
long currentTime; | |||||
long waitInMs; | |||||
currentTime = TimeUtil.currentTimeMillis(); | |||||
waitInMs = node.tryOccupyNext(currentTime, acquireCount, count); | |||||
if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) { | |||||
node.addWaitingRequest(currentTime + waitInMs, acquireCount); | |||||
node.addOccupiedPass(acquireCount); | |||||
sleep(waitInMs); | |||||
// PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}. | |||||
throw new PriorityWaitException(waitInMs); | |||||
} | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
@@ -55,10 +72,10 @@ public class DefaultController implements TrafficShapingController { | |||||
if (node == null) { | if (node == null) { | ||||
return DEFAULT_AVG_USED_TOKENS; | return DEFAULT_AVG_USED_TOKENS; | ||||
} | } | ||||
return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int) node.passQps(); | |||||
return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps()); | |||||
} | } | ||||
private void sleep(int timeMillis) { | |||||
private void sleep(long timeMillis) { | |||||
try { | try { | ||||
Thread.sleep(timeMillis); | Thread.sleep(timeMillis); | ||||
} catch (InterruptedException e) { | } catch (InterruptedException e) { | ||||
@@ -31,7 +31,9 @@ public enum MetricEvent { | |||||
EXCEPTION, | EXCEPTION, | ||||
SUCCESS, | SUCCESS, | ||||
RT, | RT, | ||||
OCCUPIED_PASS, | |||||
OCCUPIED_BLOCK, | |||||
WAITING | |||||
/** | |||||
* Passed in future quota (pre-occupied, since 1.5.0). | |||||
*/ | |||||
OCCUPIED_PASS | |||||
} | } |
@@ -19,6 +19,7 @@ import java.util.Collection; | |||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; | ||||
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; | import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; | ||||
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; | |||||
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; | ||||
@@ -70,6 +71,21 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { | |||||
Constants.ENTRY_NODE.addPassRequest(count); | Constants.ENTRY_NODE.addPassRequest(count); | ||||
} | } | ||||
// Handle pass event with registered entry callback handlers. | |||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { | |||||
handler.onPass(context, resourceWrapper, node, count, args); | |||||
} | |||||
} catch (PriorityWaitException ex) { | |||||
node.increaseThreadNum(); | |||||
if (context.getCurEntry().getOriginNode() != null) { | |||||
// Add count for origin node. | |||||
context.getCurEntry().getOriginNode().increaseThreadNum(); | |||||
} | |||||
if (resourceWrapper.getType() == EntryType.IN) { | |||||
// Add count for global inbound entry node for global statistics. | |||||
Constants.ENTRY_NODE.increaseThreadNum(); | |||||
} | |||||
// Handle pass event with registered entry callback handlers. | // Handle pass event with registered entry callback handlers. | ||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { | for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { | ||||
handler.onPass(context, resourceWrapper, node, count, args); | handler.onPass(context, resourceWrapper, node, count, args); | ||||
@@ -28,7 +28,7 @@ import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
* Basic data structure for statistic metrics in Sentinel. | * Basic data structure for statistic metrics in Sentinel. | ||||
* </p> | * </p> | ||||
* <p> | * <p> | ||||
* Leap array use sliding window algorithm to count data. Each bucket cover {code windowLengthInMs} time span, | |||||
* Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span, | |||||
* and the total time span is {@link #intervalInMs}, so the total bucket amount is: | * and the total time span is {@link #intervalInMs}, so the total bucket amount is: | ||||
* {@code sampleCount = intervalInMs / windowLengthInMs}. | * {@code sampleCount = intervalInMs / windowLengthInMs}. | ||||
* </p> | * </p> | ||||
@@ -54,8 +54,8 @@ public abstract class LeapArray<T> { | |||||
/** | /** | ||||
* The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. | * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. | ||||
* | * | ||||
* @param sampleCount bucket count of the sliding window | |||||
* @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds | |||||
* @param sampleCount bucket count of the sliding window | |||||
* @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds | |||||
*/ | */ | ||||
public LeapArray(int sampleCount, int intervalInMs) { | public LeapArray(int sampleCount, int intervalInMs) { | ||||
AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount); | AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount); | ||||
@@ -66,7 +66,7 @@ public abstract class LeapArray<T> { | |||||
this.intervalInMs = intervalInMs; | this.intervalInMs = intervalInMs; | ||||
this.sampleCount = sampleCount; | this.sampleCount = sampleCount; | ||||
this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount); | |||||
this.array = new AtomicReferenceArray<>(sampleCount); | |||||
} | } | ||||
/** | /** | ||||
@@ -81,9 +81,10 @@ public abstract class LeapArray<T> { | |||||
/** | /** | ||||
* Create a new statistic value for bucket. | * Create a new statistic value for bucket. | ||||
* | * | ||||
* @param timeMillis current time in milliseconds | |||||
* @return the new empty bucket | * @return the new empty bucket | ||||
*/ | */ | ||||
public abstract T newEmptyBucket(); | |||||
public abstract T newEmptyBucket(long timeMillis); | |||||
/** | /** | ||||
* Reset given bucket to provided start time and reset the value. | * Reset given bucket to provided start time and reset the value. | ||||
@@ -94,7 +95,7 @@ public abstract class LeapArray<T> { | |||||
*/ | */ | ||||
protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime); | protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime); | ||||
protected int calculateTimeIdx(/*@Valid*/ long timeMillis) { | |||||
private int calculateTimeIdx(/*@Valid*/ long timeMillis) { | |||||
long timeId = timeMillis / windowLengthInMs; | long timeId = timeMillis / windowLengthInMs; | ||||
// Calculate current index so we can map the timestamp to the leap array. | // Calculate current index so we can map the timestamp to the leap array. | ||||
return (int)(timeId % array.length()); | return (int)(timeId % array.length()); | ||||
@@ -141,7 +142,7 @@ public abstract class LeapArray<T> { | |||||
* then try to update circular array via a CAS operation. Only one thread can | * then try to update circular array via a CAS operation. Only one thread can | ||||
* succeed to update, while other threads yield its time slice. | * succeed to update, while other threads yield its time slice. | ||||
*/ | */ | ||||
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket()); | |||||
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); | |||||
if (array.compareAndSet(idx, null, window)) { | if (array.compareAndSet(idx, null, window)) { | ||||
// Successfully updated, return the created bucket. | // Successfully updated, return the created bucket. | ||||
return window; | return window; | ||||
@@ -193,7 +194,7 @@ public abstract class LeapArray<T> { | |||||
} | } | ||||
} else if (windowStart < old.windowStart()) { | } else if (windowStart < old.windowStart()) { | ||||
// Should not go through here, as the provided time is already behind. | // Should not go through here, as the provided time is already behind. | ||||
return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket()); | |||||
return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -236,21 +237,22 @@ public abstract class LeapArray<T> { | |||||
/** | /** | ||||
* Get statistic value from bucket for provided timestamp. | * Get statistic value from bucket for provided timestamp. | ||||
* | * | ||||
* @param time a valid timestamp in milliseconds | |||||
* @param timeMillis a valid timestamp in milliseconds | |||||
* @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null | * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null | ||||
*/ | */ | ||||
public T getWindowValue(long time) { | |||||
if (time < 0) { | |||||
public T getWindowValue(long timeMillis) { | |||||
if (timeMillis < 0) { | |||||
return null; | return null; | ||||
} | } | ||||
int idx = calculateTimeIdx(time); | |||||
int idx = calculateTimeIdx(timeMillis); | |||||
WindowWrap<T> old = array.get(idx); | |||||
if (old == null || isWindowDeprecated(old)) { | |||||
WindowWrap<T> bucket = array.get(idx); | |||||
if (bucket == null || !bucket.isTimeInWindow(timeMillis)) { | |||||
return null; | return null; | ||||
} | } | ||||
return old.value(); | |||||
return bucket.value(); | |||||
} | } | ||||
/** | /** | ||||
@@ -260,8 +262,12 @@ public abstract class LeapArray<T> { | |||||
* @param windowWrap a non-null bucket | * @param windowWrap a non-null bucket | ||||
* @return true if the bucket is deprecated; otherwise false | * @return true if the bucket is deprecated; otherwise false | ||||
*/ | */ | ||||
protected boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) { | |||||
return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; | |||||
public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) { | |||||
return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap); | |||||
} | |||||
public boolean isWindowDeprecated(long time, WindowWrap<T> windowWrap) { | |||||
return time - windowWrap.windowStart() > intervalInMs; | |||||
} | } | ||||
/** | /** | ||||
@@ -271,12 +277,36 @@ public abstract class LeapArray<T> { | |||||
* @return valid bucket list for entire sliding window. | * @return valid bucket list for entire sliding window. | ||||
*/ | */ | ||||
public List<WindowWrap<T>> list() { | public List<WindowWrap<T>> list() { | ||||
return list(TimeUtil.currentTimeMillis()); | |||||
} | |||||
public List<WindowWrap<T>> list(long validTime) { | |||||
int size = array.length(); | int size = array.length(); | ||||
List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(size); | List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(size); | ||||
for (int i = 0; i < size; i++) { | for (int i = 0; i < size; i++) { | ||||
WindowWrap<T> windowWrap = array.get(i); | WindowWrap<T> windowWrap = array.get(i); | ||||
if (windowWrap == null || isWindowDeprecated(windowWrap)) { | |||||
if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) { | |||||
continue; | |||||
} | |||||
result.add(windowWrap); | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* Get all buckets for entire sliding window including deprecated buckets. | |||||
* | |||||
* @return all buckets for entire sliding window | |||||
*/ | |||||
public List<WindowWrap<T>> listAll() { | |||||
int size = array.length(); | |||||
List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(size); | |||||
for (int i = 0; i < size; i++) { | |||||
WindowWrap<T> windowWrap = array.get(i); | |||||
if (windowWrap == null) { | |||||
continue; | continue; | ||||
} | } | ||||
result.add(windowWrap); | result.add(windowWrap); | ||||
@@ -292,12 +322,19 @@ public abstract class LeapArray<T> { | |||||
* @return aggregated value list for entire sliding window | * @return aggregated value list for entire sliding window | ||||
*/ | */ | ||||
public List<T> values() { | public List<T> values() { | ||||
return values(TimeUtil.currentTimeMillis()); | |||||
} | |||||
public List<T> values(long timeMillis) { | |||||
if (timeMillis < 0) { | |||||
return new ArrayList<T>(); | |||||
} | |||||
int size = array.length(); | int size = array.length(); | ||||
List<T> result = new ArrayList<T>(size); | List<T> result = new ArrayList<T>(size); | ||||
for (int i = 0; i < size; i++) { | for (int i = 0; i < size; i++) { | ||||
WindowWrap<T> windowWrap = array.get(i); | WindowWrap<T> windowWrap = array.get(i); | ||||
if (windowWrap == null || isWindowDeprecated(windowWrap)) { | |||||
if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) { | |||||
continue; | continue; | ||||
} | } | ||||
result.add(windowWrap.value()); | result.add(windowWrap.value()); | ||||
@@ -359,4 +396,24 @@ public abstract class LeapArray<T> { | |||||
public double getIntervalInSecond() { | public double getIntervalInSecond() { | ||||
return intervalInMs / 1000.0; | return intervalInMs / 1000.0; | ||||
} | } | ||||
public void debug(long time) { | |||||
StringBuilder sb = new StringBuilder(); | |||||
List<WindowWrap<T>> lists = list(time); | |||||
sb.append("Thread_").append(Thread.currentThread().getId()).append("_"); | |||||
for (WindowWrap<T> window : lists) { | |||||
sb.append(window.windowStart()).append(":").append(window.value().toString()); | |||||
} | |||||
System.out.println(sb.toString()); | |||||
} | |||||
public long currentWaiting() { | |||||
// TODO: default method. Should remove this later. | |||||
return 0; | |||||
} | |||||
public void addWaiting(long time, int acquireCount) { | |||||
// Do nothing by default. | |||||
throw new UnsupportedOperationException(); | |||||
} | |||||
} | } |
@@ -25,7 +25,7 @@ public class UnaryLeapArray extends LeapArray<LongAdder> { | |||||
} | } | ||||
@Override | @Override | ||||
public LongAdder newEmptyBucket() { | |||||
public LongAdder newEmptyBucket(long time) { | |||||
return new LongAdder(); | return new LongAdder(); | ||||
} | } | ||||
@@ -77,6 +77,17 @@ public class WindowWrap<T> { | |||||
return this; | return this; | ||||
} | } | ||||
/** | |||||
* Check whether given timestamp is in current bucket. | |||||
* | |||||
* @param timeMillis valid timestamp in ms | |||||
* @return true if the given time is in current bucket, otherwise false | |||||
* @since 1.5.0 | |||||
*/ | |||||
public boolean isTimeInWindow(long timeMillis) { | |||||
return windowStart <= timeMillis && timeMillis < windowStart + windowLengthInMs; | |||||
} | |||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
return "WindowWrap{" + | return "WindowWrap{" + | ||||
@@ -40,6 +40,15 @@ public class MetricBucket { | |||||
initMinRt(); | initMinRt(); | ||||
} | } | ||||
public MetricBucket reset(MetricBucket bucket) { | |||||
for (MetricEvent event : MetricEvent.values()) { | |||||
counters[event.ordinal()].reset(); | |||||
counters[event.ordinal()].add(bucket.get(event)); | |||||
} | |||||
initMinRt(); | |||||
return this; | |||||
} | |||||
private void initMinRt() { | private void initMinRt() { | ||||
this.minRt = Constants.TIME_DROP_VALVE; | this.minRt = Constants.TIME_DROP_VALVE; | ||||
} | } | ||||
@@ -70,6 +79,10 @@ public class MetricBucket { | |||||
return get(MetricEvent.PASS); | return get(MetricEvent.PASS); | ||||
} | } | ||||
public long occupiedPass() { | |||||
return get(MetricEvent.OCCUPIED_PASS); | |||||
} | |||||
public long block() { | public long block() { | ||||
return get(MetricEvent.BLOCK); | return get(MetricEvent.BLOCK); | ||||
} | } | ||||
@@ -94,6 +107,10 @@ public class MetricBucket { | |||||
add(MetricEvent.PASS, n); | add(MetricEvent.PASS, n); | ||||
} | } | ||||
public void addOccupiedPass(int n) { | |||||
add(MetricEvent.OCCUPIED_PASS, n); | |||||
} | |||||
public void addException(int n) { | public void addException(int n) { | ||||
add(MetricEvent.EXCEPTION, n); | add(MetricEvent.EXCEPTION, n); | ||||
} | } | ||||
@@ -114,4 +131,9 @@ public class MetricBucket { | |||||
minRt = rt; | minRt = rt; | ||||
} | } | ||||
} | } | ||||
@Override | |||||
public String toString() { | |||||
return "p: " + pass() + ", b: " + block() + ", w: " + occupiedPass(); | |||||
} | |||||
} | } |
@@ -20,27 +20,38 @@ import java.util.List; | |||||
import com.alibaba.csp.sentinel.Constants; | import com.alibaba.csp.sentinel.Constants; | ||||
import com.alibaba.csp.sentinel.node.metric.MetricNode; | import com.alibaba.csp.sentinel.node.metric.MetricNode; | ||||
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | ||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | ||||
import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray; | |||||
/** | /** | ||||
* The basic metric class in Sentinel using a {@link MetricsLeapArray} internal. | |||||
* The basic metric class in Sentinel using a {@link BucketLeapArray} internal. | |||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
*/ | */ | ||||
public class ArrayMetric implements Metric { | public class ArrayMetric implements Metric { | ||||
private final MetricsLeapArray data; | |||||
private final LeapArray<MetricBucket> data; | |||||
public ArrayMetric(int sampleCount, int intervalInMs) { | public ArrayMetric(int sampleCount, int intervalInMs) { | ||||
this.data = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
} | |||||
public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) { | |||||
if (enableOccupy) { | |||||
this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
} else { | |||||
this.data = new BucketLeapArray(sampleCount, intervalInMs); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* For unit test. | * For unit test. | ||||
*/ | */ | ||||
public ArrayMetric(MetricsLeapArray array) { | |||||
public ArrayMetric(LeapArray<MetricBucket> array) { | |||||
this.data = array; | this.data = array; | ||||
} | } | ||||
@@ -104,6 +115,17 @@ public class ArrayMetric implements Metric { | |||||
return pass; | return pass; | ||||
} | } | ||||
@Override | |||||
public long occupiedPass() { | |||||
data.currentWindow(); | |||||
long pass = 0; | |||||
List<MetricBucket> list = data.values(); | |||||
for (MetricBucket window : list) { | |||||
pass += window.occupiedPass(); | |||||
} | |||||
return pass; | |||||
} | |||||
@Override | @Override | ||||
public long rt() { | public long rt() { | ||||
data.currentWindow(); | data.currentWindow(); | ||||
@@ -133,7 +155,8 @@ public class ArrayMetric implements Metric { | |||||
public List<MetricNode> details() { | public List<MetricNode> details() { | ||||
List<MetricNode> details = new ArrayList<MetricNode>(); | List<MetricNode> details = new ArrayList<MetricNode>(); | ||||
data.currentWindow(); | data.currentWindow(); | ||||
for (WindowWrap<MetricBucket> window : data.list()) { | |||||
List<WindowWrap<MetricBucket>> list = data.list(); | |||||
for (WindowWrap<MetricBucket> window : list) { | |||||
if (window == null) { | if (window == null) { | ||||
continue; | continue; | ||||
} | } | ||||
@@ -141,14 +164,16 @@ public class ArrayMetric implements Metric { | |||||
node.setBlockQps(window.value().block()); | node.setBlockQps(window.value().block()); | ||||
node.setExceptionQps(window.value().exception()); | node.setExceptionQps(window.value().exception()); | ||||
node.setPassQps(window.value().pass()); | node.setPassQps(window.value().pass()); | ||||
long passQps = window.value().success(); | |||||
node.setSuccessQps(passQps); | |||||
if (passQps != 0) { | |||||
node.setRt(window.value().rt() / passQps); | |||||
long successQps = window.value().success(); | |||||
node.setSuccessQps(successQps); | |||||
if (successQps != 0) { | |||||
node.setRt(window.value().rt() / successQps); | |||||
} else { | } else { | ||||
node.setRt(window.value().rt()); | node.setRt(window.value().rt()); | ||||
} | } | ||||
node.setTimestamp(window.windowStart()); | node.setTimestamp(window.windowStart()); | ||||
node.setOccupiedPassQps(window.value().occupiedPass()); | |||||
details.add(node); | details.add(node); | ||||
} | } | ||||
@@ -158,7 +183,7 @@ public class ArrayMetric implements Metric { | |||||
@Override | @Override | ||||
public MetricBucket[] windows() { | public MetricBucket[] windows() { | ||||
data.currentWindow(); | data.currentWindow(); | ||||
return data.values().toArray(new MetricBucket[data.values().size()]); | |||||
return data.values().toArray(new MetricBucket[0]); | |||||
} | } | ||||
@Override | @Override | ||||
@@ -173,6 +198,17 @@ public class ArrayMetric implements Metric { | |||||
wrap.value().addBlock(count); | wrap.value().addBlock(count); | ||||
} | } | ||||
@Override | |||||
public void addWaiting(long time, int acquireCount) { | |||||
data.addWaiting(time, acquireCount); | |||||
} | |||||
@Override | |||||
public void addOccupiedPass(int acquireCount) { | |||||
WindowWrap<MetricBucket> wrap = data.currentWindow(); | |||||
wrap.value().addOccupiedPass(acquireCount); | |||||
} | |||||
@Override | @Override | ||||
public void addSuccess(int count) { | public void addSuccess(int count) { | ||||
WindowWrap<MetricBucket> wrap = data.currentWindow(); | WindowWrap<MetricBucket> wrap = data.currentWindow(); | ||||
@@ -192,18 +228,8 @@ public class ArrayMetric implements Metric { | |||||
} | } | ||||
@Override | @Override | ||||
public void debugQps() { | |||||
data.currentWindow(); | |||||
StringBuilder sb = new StringBuilder(); | |||||
sb.append(Thread.currentThread().getId()).append("_"); | |||||
for (WindowWrap<MetricBucket> windowWrap : data.list()) { | |||||
sb.append(windowWrap.windowStart()).append(":").append(windowWrap.value().pass()).append(":") | |||||
.append(windowWrap.value().block()); | |||||
sb.append(","); | |||||
} | |||||
System.out.println(sb); | |||||
public void debug() { | |||||
data.debug(System.currentTimeMillis()); | |||||
} | } | ||||
@Override | @Override | ||||
@@ -226,6 +252,55 @@ public class ArrayMetric implements Metric { | |||||
return wrap.value().pass(); | return wrap.value().pass(); | ||||
} | } | ||||
public void add(MetricEvent event, long count) { | |||||
data.currentWindow().value().add(event, count); | |||||
} | |||||
public long getCurrentCount(MetricEvent event) { | |||||
return data.currentWindow().value().get(event); | |||||
} | |||||
/** | |||||
* Get total sum for provided event in {@code intervalInSec}. | |||||
* | |||||
* @param event event to calculate | |||||
* @return total sum for event | |||||
*/ | |||||
public long getSum(MetricEvent event) { | |||||
data.currentWindow(); | |||||
long sum = 0; | |||||
List<MetricBucket> buckets = data.values(); | |||||
for (MetricBucket bucket : buckets) { | |||||
sum += bucket.get(event); | |||||
} | |||||
return sum; | |||||
} | |||||
/** | |||||
* Get average count for provided event per second. | |||||
* | |||||
* @param event event to calculate | |||||
* @return average count per second for event | |||||
*/ | |||||
public double getAvg(MetricEvent event) { | |||||
return getSum(event) / data.getIntervalInSecond(); | |||||
} | |||||
@Override | |||||
public long getWindowPass(long timeMillis) { | |||||
MetricBucket bucket = data.getWindowValue(timeMillis); | |||||
if (bucket == null) { | |||||
return 0L; | |||||
} | |||||
return bucket.pass(); | |||||
} | |||||
@Override | |||||
public long waiting() { | |||||
return data.currentWaiting(); | |||||
} | |||||
@Override | @Override | ||||
public double getWindowIntervalInSec() { | public double getWindowIntervalInSec() { | ||||
return data.getIntervalInSecond(); | return data.getIntervalInSecond(); | ||||
@@ -16,24 +16,24 @@ | |||||
package com.alibaba.csp.sentinel.slots.statistic.metric; | package com.alibaba.csp.sentinel.slots.statistic.metric; | ||||
import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; | import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; | ||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | ||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
/** | /** | ||||
* The fundamental data structure for metric statistics in a time span. | * The fundamental data structure for metric statistics in a time span. | ||||
* | * | ||||
* @see LeapArray | |||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
* @see LeapArray | |||||
*/ | */ | ||||
public class MetricsLeapArray extends LeapArray<MetricBucket> { | |||||
public class BucketLeapArray extends LeapArray<MetricBucket> { | |||||
public MetricsLeapArray(int sampleCount, int intervalInMs) { | |||||
public BucketLeapArray(int sampleCount, int intervalInMs) { | |||||
super(sampleCount, intervalInMs); | super(sampleCount, intervalInMs); | ||||
} | } | ||||
@Override | @Override | ||||
public MetricBucket newEmptyBucket() { | |||||
public MetricBucket newEmptyBucket(long time) { | |||||
return new MetricBucket(); | return new MetricBucket(); | ||||
} | } | ||||
@@ -26,7 +26,7 @@ import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
*/ | */ | ||||
public interface Metric { | |||||
public interface Metric extends DebugSupport { | |||||
/** | /** | ||||
* Get total success count. | * Get total success count. | ||||
@@ -57,7 +57,7 @@ public interface Metric { | |||||
long block(); | long block(); | ||||
/** | /** | ||||
* Get total pass count. | |||||
* Get total pass count. not include {@link #occupiedPass()} | |||||
* | * | ||||
* @return pass count | * @return pass count | ||||
*/ | */ | ||||
@@ -92,22 +92,30 @@ public interface Metric { | |||||
MetricBucket[] windows(); | MetricBucket[] windows(); | ||||
/** | /** | ||||
* Increment by one the current exception count. | |||||
* Add current exception count. | |||||
* | |||||
* @param n count to add | |||||
*/ | */ | ||||
void addException(int n); | void addException(int n); | ||||
/** | /** | ||||
* Increment by one the current block count. | |||||
* Add current block count. | |||||
* | |||||
* @param n count to add | |||||
*/ | */ | ||||
void addBlock(int n); | void addBlock(int n); | ||||
/** | /** | ||||
* Increment by one the current success count. | |||||
* Add current completed count. | |||||
* | |||||
* @param n count to add | |||||
*/ | */ | ||||
void addSuccess(int n); | void addSuccess(int n); | ||||
/** | /** | ||||
* Increment by one the current pass count. | |||||
* Add current pass count. | |||||
* | |||||
* @param n count to add | |||||
*/ | */ | ||||
void addPass(int n); | void addPass(int n); | ||||
@@ -118,13 +126,65 @@ public interface Metric { | |||||
*/ | */ | ||||
void addRT(long rt); | void addRT(long rt); | ||||
/** | |||||
* Get the sliding window length in seconds. | |||||
* | |||||
* @return the sliding window length | |||||
*/ | |||||
double getWindowIntervalInSec(); | double getWindowIntervalInSec(); | ||||
/** | |||||
* Get sample count of the sliding window. | |||||
* | |||||
* @return sample count of the sliding window. | |||||
*/ | |||||
int getSampleCount(); | int getSampleCount(); | ||||
// Tool methods. | |||||
/** | |||||
* Note: this operation will not perform refreshing, so will not generate new buckets. | |||||
* | |||||
* @param timeMillis valid time in ms | |||||
* @return pass count of the bucket exactly associated to provided timestamp, or 0 if the timestamp is invalid | |||||
* @since 1.5.0 | |||||
*/ | |||||
long getWindowPass(long timeMillis); | |||||
// Occupy-based (@since 1.5.0) | |||||
/** | |||||
* Add occupied pass, which represents pass requests that borrow the latter windows' token. | |||||
* | |||||
* @param acquireCount tokens count. | |||||
* @since 1.5.0 | |||||
*/ | |||||
void addOccupiedPass(int acquireCount); | |||||
/** | |||||
* Add request that occupied. | |||||
* | |||||
* @param futureTime future timestamp that the acquireCount should be added on. | |||||
* @param acquireCount tokens count. | |||||
* @since 1.5.0 | |||||
*/ | |||||
void addWaiting(long futureTime, int acquireCount); | |||||
/** | |||||
* Get waiting pass account | |||||
* | |||||
* @return waiting pass count | |||||
* @since 1.5.0 | |||||
*/ | |||||
long waiting(); | |||||
/** | |||||
* Get occupied pass count. | |||||
* | |||||
* @return occupied pass count | |||||
* @since 1.5.0 | |||||
*/ | |||||
long occupiedPass(); | |||||
void debugQps(); | |||||
// Tool methods. | |||||
long previousWindowBlock(); | long previousWindowBlock(); | ||||
@@ -0,0 +1,53 @@ | |||||
/* | |||||
* 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.occupy; | |||||
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.MetricBucket; | |||||
/** | |||||
* A kind of {@code BucketLeapArray} that only reserves for future buckets. | |||||
* | |||||
* @author jialiang.linjl | |||||
* @since 1.5.0 | |||||
*/ | |||||
public class FutureBucketLeapArray extends LeapArray<MetricBucket> { | |||||
public FutureBucketLeapArray(int sampleCount, int intervalInMs) { | |||||
// This class is the original "BorrowBucketArray". | |||||
super(sampleCount, intervalInMs); | |||||
} | |||||
@Override | |||||
public MetricBucket newEmptyBucket(long time) { | |||||
return new MetricBucket(); | |||||
} | |||||
@Override | |||||
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long startTime) { | |||||
// Update the start time and reset value. | |||||
w.resetTo(startTime); | |||||
w.value().reset(); | |||||
return w; | |||||
} | |||||
@Override | |||||
public boolean isWindowDeprecated(long time, WindowWrap<MetricBucket> windowWrap) { | |||||
// Tricky: will only calculate for future. | |||||
return time >= windowWrap.windowStart(); | |||||
} | |||||
} |
@@ -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.statistic.metric.occupy; | |||||
import java.util.List; | |||||
import com.alibaba.csp.sentinel.slots.statistic.MetricEvent; | |||||
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.MetricBucket; | |||||
/** | |||||
* @author jialiang.linjl | |||||
* @since 1.5.0 | |||||
*/ | |||||
public class OccupiableBucketLeapArray extends LeapArray<MetricBucket> { | |||||
private final FutureBucketLeapArray borrowArray; | |||||
public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) { | |||||
// This class is the original "CombinedBucketArray". | |||||
super(sampleCount, intervalInMs); | |||||
this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs); | |||||
} | |||||
@Override | |||||
public MetricBucket newEmptyBucket(long time) { | |||||
MetricBucket newBucket = new MetricBucket(); | |||||
MetricBucket borrowBucket = borrowArray.getWindowValue(time); | |||||
if (borrowBucket != null) { | |||||
newBucket.reset(borrowBucket); | |||||
} | |||||
return newBucket; | |||||
} | |||||
@Override | |||||
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long time) { | |||||
// Update the start time and reset value. | |||||
w.resetTo(time); | |||||
MetricBucket borrowBucket = borrowArray.getWindowValue(time); | |||||
if (borrowBucket != null) { | |||||
w.value().reset(); | |||||
w.value().addPass((int)borrowBucket.pass()); | |||||
} else { | |||||
w.value().reset(); | |||||
} | |||||
return w; | |||||
} | |||||
@Override | |||||
public long currentWaiting() { | |||||
borrowArray.currentWindow(); | |||||
long currentWaiting = 0; | |||||
List<MetricBucket> list = borrowArray.values(); | |||||
for (MetricBucket window : list) { | |||||
currentWaiting += window.pass(); | |||||
} | |||||
return currentWaiting; | |||||
} | |||||
@Override | |||||
public void addWaiting(long time, int acquireCount) { | |||||
WindowWrap<MetricBucket> window = borrowArray.currentWindow(time); | |||||
window.value().add(MetricEvent.PASS, acquireCount); | |||||
} | |||||
@Override | |||||
public void debug(long time) { | |||||
StringBuilder sb = new StringBuilder(); | |||||
List<WindowWrap<MetricBucket>> lists = listAll(); | |||||
sb.append("a_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); | |||||
for (WindowWrap<MetricBucket> window : lists) { | |||||
sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); | |||||
} | |||||
sb.append("\n"); | |||||
lists = borrowArray.listAll(); | |||||
sb.append("b_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); | |||||
for (WindowWrap<MetricBucket> window : lists) { | |||||
sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); | |||||
} | |||||
System.out.println(sb.toString()); | |||||
} | |||||
} |
@@ -39,7 +39,7 @@ import static org.junit.Assert.assertTrue; | |||||
*/ | */ | ||||
public class StatisticNodeTest { | public class StatisticNodeTest { | ||||
private static final String LOG_PREFIX = "[StatisticNodeTest]"; | |||||
private static final String LOG_PREFIX = "[StatisticNodeTest] "; | |||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss"); | private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-HH-dd HH:mm:ss"); | ||||
@@ -74,7 +74,7 @@ public class StatisticNodeTest { | |||||
tickEs.submit(new TickTask(node)); | tickEs.submit(new TickTask(node)); | ||||
List<BizTask> bizTasks = new ArrayList<BizTask>(taskBizExecuteCount); | |||||
List<BizTask> bizTasks = new ArrayList<>(taskBizExecuteCount); | |||||
for (int i = 0; i < taskCount; i++) { | for (int i = 0; i < taskCount; i++) { | ||||
bizTasks.add(new BizTask(node, taskBizExecuteCount)); | bizTasks.add(new BizTask(node, taskBizExecuteCount)); | ||||
} | } | ||||
@@ -88,8 +88,8 @@ public class StatisticNodeTest { | |||||
log("all biz task done, waiting 3 second to exit"); | log("all biz task done, waiting 3 second to exit"); | ||||
sleep(3000); | sleep(3000); | ||||
bizEs.shutdown(); | |||||
tickEs.shutdown(); | |||||
bizEs.shutdownNow(); | |||||
tickEs.shutdownNow(); | |||||
// now no biz method execute, so there is no curThreadNum,passQps,successQps | // now no biz method execute, so there is no curThreadNum,passQps,successQps | ||||
assertEquals(0, node.curThreadNum(), 0.01); | assertEquals(0, node.curThreadNum(), 0.01); | ||||
@@ -192,7 +192,7 @@ public class StatisticNodeTest { | |||||
log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps() | log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps() | ||||
+ ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps() | + ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps() | ||||
+ ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess() | + ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess() | ||||
+ ",avgRt=" + node.avgRt() + ",minRt=" + node.minRt()); | |||||
+ ", avgRt=" + String.format("%.2f", node.avgRt()) + ", minRt=" + node.minRt()); | |||||
} | } | ||||
private static void log(Object obj) { | private static void log(Object obj) { | ||||
@@ -35,7 +35,7 @@ public class LeapArrayTest extends AbstractTimeBasedTest { | |||||
int sampleCount = intervalInMs / windowLengthInMs; | int sampleCount = intervalInMs / windowLengthInMs; | ||||
LeapArray<AtomicInteger> leapArray = new LeapArray<AtomicInteger>(sampleCount, intervalInMs) { | LeapArray<AtomicInteger> leapArray = new LeapArray<AtomicInteger>(sampleCount, intervalInMs) { | ||||
@Override | @Override | ||||
public AtomicInteger newEmptyBucket() { | |||||
public AtomicInteger newEmptyBucket(long time) { | |||||
return new AtomicInteger(0); | return new AtomicInteger(0); | ||||
} | } | ||||
@@ -13,20 +13,18 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * limitations under the License. | ||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.base.metric; | |||||
package com.alibaba.csp.sentinel.slots.statistic.metric; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import org.junit.Test; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | ||||
import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
import static org.junit.Assert.*; | |||||
import org.junit.Test; | |||||
import static org.mockito.Mockito.*; | |||||
import static org.junit.Assert.*; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.when; | |||||
/** | /** | ||||
* Test cases for {@link ArrayMetric}. | * Test cases for {@link ArrayMetric}. | ||||
@@ -39,7 +37,7 @@ public class ArrayMetricTest { | |||||
@Test | @Test | ||||
public void testOperateArrayMetric() { | public void testOperateArrayMetric() { | ||||
MetricsLeapArray leapArray = mock(MetricsLeapArray.class); | |||||
BucketLeapArray leapArray = mock(BucketLeapArray.class); | |||||
final WindowWrap<MetricBucket> windowWrap = new WindowWrap<MetricBucket>(windowLengthInMs, 0, new MetricBucket()); | final WindowWrap<MetricBucket> windowWrap = new WindowWrap<MetricBucket>(windowLengthInMs, 0, new MetricBucket()); | ||||
when(leapArray.currentWindow()).thenReturn(windowWrap); | when(leapArray.currentWindow()).thenReturn(windowWrap); | ||||
when(leapArray.values()).thenReturn(new ArrayList<MetricBucket>() {{ add(windowWrap.value()); }}); | when(leapArray.values()).thenReturn(new ArrayList<MetricBucket>() {{ add(windowWrap.value()); }}); |
@@ -13,7 +13,7 @@ | |||||
* See the License for the specific language governing permissions and | * See the License for the specific language governing permissions and | ||||
* limitations under the License. | * limitations under the License. | ||||
*/ | */ | ||||
package com.alibaba.csp.sentinel.base.metric; | |||||
package com.alibaba.csp.sentinel.slots.statistic.metric; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
@@ -21,22 +21,20 @@ import java.util.List; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.concurrent.CountDownLatch; | import java.util.concurrent.CountDownLatch; | ||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | ||||
import com.alibaba.csp.sentinel.util.TimeUtil; | import com.alibaba.csp.sentinel.util.TimeUtil; | ||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray; | |||||
import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
import static org.junit.Assert.*; | import static org.junit.Assert.*; | ||||
/** | /** | ||||
* Test cases for {@link MetricsLeapArray}. | |||||
* Test cases for {@link BucketLeapArray}. | |||||
* | * | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
*/ | */ | ||||
public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
public class BucketLeapArrayTest { | |||||
private final int windowLengthInMs = 1000; | private final int windowLengthInMs = 1000; | ||||
private final int intervalInSec = 2; | private final int intervalInSec = 2; | ||||
@@ -45,7 +43,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
@Test | @Test | ||||
public void testNewWindow() { | public void testNewWindow() { | ||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long time = TimeUtil.currentTimeMillis(); | long time = TimeUtil.currentTimeMillis(); | ||||
WindowWrap<MetricBucket> window = leapArray.currentWindow(time); | WindowWrap<MetricBucket> window = leapArray.currentWindow(time); | ||||
@@ -57,7 +55,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
@Test | @Test | ||||
public void testLeapArrayWindowStart() { | public void testLeapArrayWindowStart() { | ||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long firstTime = TimeUtil.currentTimeMillis(); | long firstTime = TimeUtil.currentTimeMillis(); | ||||
long previousWindowStart = firstTime - firstTime % windowLengthInMs; | long previousWindowStart = firstTime - firstTime % windowLengthInMs; | ||||
@@ -69,7 +67,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
@Test | @Test | ||||
public void testWindowAfterOneInterval() { | public void testWindowAfterOneInterval() { | ||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long firstTime = TimeUtil.currentTimeMillis(); | long firstTime = TimeUtil.currentTimeMillis(); | ||||
long previousWindowStart = firstTime - firstTime % windowLengthInMs; | long previousWindowStart = firstTime - firstTime % windowLengthInMs; | ||||
WindowWrap<MetricBucket> window = leapArray.currentWindow(previousWindowStart); | WindowWrap<MetricBucket> window = leapArray.currentWindow(previousWindowStart); | ||||
@@ -109,7 +107,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
@Deprecated | @Deprecated | ||||
public void testWindowDeprecatedRefresh() { | public void testWindowDeprecatedRefresh() { | ||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
final int len = sampleCount; | final int len = sampleCount; | ||||
long firstTime = TimeUtil.currentTimeMillis(); | long firstTime = TimeUtil.currentTimeMillis(); | ||||
List<WindowWrap<MetricBucket>> firstIterWindowList = new ArrayList<WindowWrap<MetricBucket>>(len); | List<WindowWrap<MetricBucket>> firstIterWindowList = new ArrayList<WindowWrap<MetricBucket>>(len); | ||||
@@ -129,7 +127,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
public void testMultiThreadUpdateEmptyWindow() throws Exception { | public void testMultiThreadUpdateEmptyWindow() throws Exception { | ||||
final long time = TimeUtil.currentTimeMillis(); | final long time = TimeUtil.currentTimeMillis(); | ||||
final int nThreads = 16; | final int nThreads = 16; | ||||
final MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
final BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
final CountDownLatch latch = new CountDownLatch(nThreads); | final CountDownLatch latch = new CountDownLatch(nThreads); | ||||
Runnable task = new Runnable() { | Runnable task = new Runnable() { | ||||
@Override | @Override | ||||
@@ -150,8 +148,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
@Test | @Test | ||||
public void testGetPreviousWindow() { | public void testGetPreviousWindow() { | ||||
setCurrentMillis(System.currentTimeMillis()); | |||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long time = TimeUtil.currentTimeMillis(); | long time = TimeUtil.currentTimeMillis(); | ||||
WindowWrap<MetricBucket> previousWindow = leapArray.currentWindow(time); | WindowWrap<MetricBucket> previousWindow = leapArray.currentWindow(time); | ||||
assertNull(leapArray.getPreviousWindow(time)); | assertNull(leapArray.getPreviousWindow(time)); | ||||
@@ -168,10 +165,8 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
final int windowLengthInMs = 100; | final int windowLengthInMs = 100; | ||||
final int intervalInMs = 1000; | final int intervalInMs = 1000; | ||||
final int sampleCount = intervalInMs / windowLengthInMs; | final int sampleCount = intervalInMs / windowLengthInMs; | ||||
setCurrentMillis(System.currentTimeMillis()); | |||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long time = TimeUtil.currentTimeMillis(); | long time = TimeUtil.currentTimeMillis(); | ||||
Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | ||||
@@ -184,7 +179,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
assertTrue(windowWraps.contains(wrap)); | assertTrue(windowWraps.contains(wrap)); | ||||
} | } | ||||
sleep(windowLengthInMs + intervalInMs); | |||||
Thread.sleep(windowLengthInMs + intervalInMs); | |||||
// This will replace the deprecated bucket, so all deprecated buckets will be reset. | // This will replace the deprecated bucket, so all deprecated buckets will be reset. | ||||
leapArray.currentWindow(time + windowLengthInMs + intervalInMs).value().addPass(1); | leapArray.currentWindow(time + windowLengthInMs + intervalInMs).value().addPass(1); | ||||
@@ -198,8 +193,8 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
final int intervalInSec = 1; | final int intervalInSec = 1; | ||||
final int intervalInMs = intervalInSec * 1000; | final int intervalInMs = intervalInSec * 1000; | ||||
final int sampleCount = intervalInMs / windowLengthInMs; | final int sampleCount = intervalInMs / windowLengthInMs; | ||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs); | |||||
long time = TimeUtil.currentTimeMillis(); | long time = TimeUtil.currentTimeMillis(); | ||||
Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | ||||
@@ -207,7 +202,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
windowWraps.add(leapArray.currentWindow(time)); | windowWraps.add(leapArray.currentWindow(time)); | ||||
windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); | windowWraps.add(leapArray.currentWindow(time + windowLengthInMs)); | ||||
sleep(intervalInMs + windowLengthInMs * 3); | |||||
Thread.sleep(intervalInMs + windowLengthInMs * 3); | |||||
List<WindowWrap<MetricBucket>> list = leapArray.list(); | List<WindowWrap<MetricBucket>> list = leapArray.list(); | ||||
for (WindowWrap<MetricBucket> wrap : list) { | for (WindowWrap<MetricBucket> wrap : list) { | ||||
@@ -220,4 +215,4 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest { | |||||
assertEquals(1, leapArray.list().size()); | assertEquals(1, leapArray.list().size()); | ||||
} | } | ||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package com.alibaba.csp.sentinel.slots.statistic.metric; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.FutureBucketLeapArray; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.assertEquals; | |||||
/** | |||||
* Test cases for {@link FutureBucketLeapArray}. | |||||
* | |||||
* @author jialiang.linjl | |||||
*/ | |||||
public class FutureBucketLeapArrayTest { | |||||
private final int windowLengthInMs = 200; | |||||
private final int intervalInSec = 2; | |||||
private final int intervalInMs = intervalInSec * 1000; | |||||
private final int sampleCount = intervalInMs / windowLengthInMs; | |||||
@Test | |||||
public void testFutureMetricLeapArray() { | |||||
FutureBucketLeapArray array = new FutureBucketLeapArray(sampleCount, intervalInMs); | |||||
long currentTime = TimeUtil.currentTimeMillis(); | |||||
for (int i = 0; i < intervalInSec * 1000; i = i + windowLengthInMs) { | |||||
array.currentWindow(i + currentTime).value().addPass(1); | |||||
assertEquals(array.values(i + currentTime).size(), 0); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,127 @@ | |||||
package com.alibaba.csp.sentinel.slots.statistic.metric; | |||||
import java.util.List; | |||||
import java.util.concurrent.CountDownLatch; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray; | |||||
import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.assertEquals; | |||||
/** | |||||
* Test cases for {@link OccupiableBucketLeapArray}. | |||||
* | |||||
* @author jialiang.linjl | |||||
*/ | |||||
public class OccupiableBucketLeapArrayTest { | |||||
private final int windowLengthInMs = 200; | |||||
private final int intervalInSec = 2; | |||||
private final int intervalInMs = intervalInSec * 1000; | |||||
private final int sampleCount = intervalInMs / windowLengthInMs; | |||||
@Test | |||||
public void testNewWindow() { | |||||
long currentTime = TimeUtil.currentTimeMillis(); | |||||
OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
WindowWrap<MetricBucket> currentWindow = leapArray.currentWindow(currentTime); | |||||
currentWindow.value().addPass(1); | |||||
assertEquals(currentWindow.value().pass(), 1L); | |||||
leapArray.addWaiting(currentTime + windowLengthInMs, 1); | |||||
assertEquals(leapArray.currentWaiting(), 1); | |||||
assertEquals(currentWindow.value().pass(), 1L); | |||||
} | |||||
@Test | |||||
public void testWindowInOneInterval() { | |||||
OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
long currentTime = TimeUtil.currentTimeMillis(); | |||||
WindowWrap<MetricBucket> currentWindow = leapArray.currentWindow(currentTime); | |||||
currentWindow.value().addPass(1); | |||||
assertEquals(currentWindow.value().pass(), 1L); | |||||
leapArray.addWaiting(currentTime + windowLengthInMs, 2); | |||||
assertEquals(leapArray.currentWaiting(), 2); | |||||
assertEquals(currentWindow.value().pass(), 1L); | |||||
leapArray.currentWindow(currentTime + windowLengthInMs); | |||||
List<MetricBucket> values = leapArray.values(currentTime + windowLengthInMs); | |||||
assertEquals(values.size(), 2); | |||||
long sum = 0; | |||||
for (MetricBucket bucket : values) { | |||||
sum += bucket.pass(); | |||||
} | |||||
assertEquals(sum, 3); | |||||
} | |||||
@Test | |||||
public void testMultiThreadUpdateEmptyWindow() throws Exception { | |||||
final long time = TimeUtil.currentTimeMillis(); | |||||
final int nThreads = 16; | |||||
final OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
final CountDownLatch latch = new CountDownLatch(nThreads); | |||||
Runnable task = new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
leapArray.currentWindow(time).value().addPass(1); | |||||
leapArray.addWaiting(time + windowLengthInMs, 1); | |||||
latch.countDown(); | |||||
} | |||||
}; | |||||
for (int i = 0; i < nThreads; i++) { | |||||
new Thread(task).start(); | |||||
} | |||||
latch.await(); | |||||
assertEquals(nThreads, leapArray.currentWindow(time).value().pass()); | |||||
assertEquals(nThreads, leapArray.currentWaiting()); | |||||
leapArray.currentWindow(time + windowLengthInMs); | |||||
long sum = 0; | |||||
List<MetricBucket> values = leapArray.values(time + windowLengthInMs); | |||||
for (MetricBucket bucket : values) { | |||||
sum += bucket.pass(); | |||||
} | |||||
assertEquals(values.size(), 2); | |||||
assertEquals(sum, nThreads * 2); | |||||
} | |||||
@Test | |||||
public void testWindowAfterOneInterval() { | |||||
OccupiableBucketLeapArray leapArray = new OccupiableBucketLeapArray(sampleCount, intervalInMs); | |||||
long currentTime = TimeUtil.currentTimeMillis(); | |||||
System.out.println(currentTime); | |||||
for (int i = 0; i < intervalInSec * 1000 / windowLengthInMs; i++) { | |||||
WindowWrap<MetricBucket> currentWindow = leapArray.currentWindow(currentTime + i * windowLengthInMs); | |||||
currentWindow.value().addPass(1); | |||||
leapArray.addWaiting(currentTime + (i + 1) * windowLengthInMs, 1); | |||||
System.out.println(currentTime + i * windowLengthInMs); | |||||
leapArray.debug(currentTime + i * windowLengthInMs); | |||||
} | |||||
System.out.println(currentTime + intervalInSec * 1000); | |||||
List<MetricBucket> values = leapArray | |||||
.values(currentTime - currentTime % windowLengthInMs + intervalInSec * 1000); | |||||
leapArray.debug(currentTime + intervalInSec * 1000); | |||||
assertEquals(values.size(), intervalInSec * 1000 / windowLengthInMs); | |||||
long sum = 0; | |||||
for (MetricBucket bucket : values) { | |||||
sum += bucket.pass(); | |||||
} | |||||
assertEquals(sum, 2 * intervalInSec * 1000 / windowLengthInMs - 1); | |||||
assertEquals(leapArray.currentWaiting(), 10); | |||||
} | |||||
} |
@@ -42,7 +42,7 @@ public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||||
} | } | ||||
@Override | @Override | ||||
public ParamMapBucket newEmptyBucket() { | |||||
public ParamMapBucket newEmptyBucket(long timeMillis) { | |||||
return new ParamMapBucket(); | return new ParamMapBucket(); | ||||
} | } | ||||