Add occupy mechanism for future buckets of sliding window to support "prioritized requests final pass" (#568)
* 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>
This commit is contained in:
parent
3c52bbc3c8
commit
044cdbb1bf
|
|
@ -40,7 +40,7 @@ public class ClusterMetricLeapArray extends LeapArray<ClusterMetricBucket> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClusterMetricBucket newEmptyBucket() {
|
||||
public ClusterMetricBucket newEmptyBucket(long timeMillis) {
|
||||
return new ClusterMetricBucket();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>>
|
|||
}
|
||||
|
||||
@Override
|
||||
public CacheMap<Object, C> newEmptyBucket() {
|
||||
public CacheMap<Object, C> newEmptyBucket(long timeMillis) {
|
||||
return new ConcurrentLinkedHashMapWrapper<>(maxCapacity);
|
||||
}
|
||||
|
||||
|
|
@ -48,5 +48,4 @@ public class ClusterParameterLeapArray<C> extends LeapArray<CacheMap<Object, C>>
|
|||
return w;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport;
|
|||
* @author leyou
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Node extends DebugSupport {
|
||||
public interface Node extends OccupySupport, DebugSupport {
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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.
|
||||
|
|
@ -116,7 +116,7 @@ public class StatisticNode implements Node {
|
|||
// The fetch operation is thread-safe under a single-thread scheduler pool.
|
||||
long currentTime = TimeUtil.currentTimeMillis();
|
||||
currentTime = currentTime - currentTime % 1000;
|
||||
Map<Long, MetricNode> metrics = new ConcurrentHashMap<Long, MetricNode>();
|
||||
Map<Long, MetricNode> metrics = new ConcurrentHashMap<>();
|
||||
List<MetricNode> nodesOfEverySecond = rollingCounterInMinute.details();
|
||||
long newLastFetchTime = lastFetchTime;
|
||||
// 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) {
|
||||
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
|
||||
|
|
@ -151,11 +151,6 @@ public class StatisticNode implements Node {
|
|||
return totalRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalPass() {
|
||||
return rollingCounterInMinute.pass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long blockRequest() {
|
||||
return rollingCounterInMinute.block();
|
||||
|
|
@ -201,6 +196,11 @@ public class StatisticNode implements Node {
|
|||
return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long totalPass() {
|
||||
return rollingCounterInMinute.pass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double successQps() {
|
||||
return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
|
|
@ -211,6 +211,11 @@ public class StatisticNode implements Node {
|
|||
return rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double occupiedPassQps() {
|
||||
return rollingCounterInSecond.occupiedPass() / rollingCounterInSecond.getWindowIntervalInSec();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double avgRt() {
|
||||
long successCount = rollingCounterInSecond.success();
|
||||
|
|
@ -256,7 +261,6 @@ public class StatisticNode implements Node {
|
|||
public void increaseExceptionQps(int count) {
|
||||
rollingCounterInSecond.addException(count);
|
||||
rollingCounterInMinute.addException(count);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -271,6 +275,57 @@ public class StatisticNode implements Node {
|
|||
|
||||
@Override
|
||||
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 rt;
|
||||
|
||||
/**
|
||||
* @since 1.5.0
|
||||
*/
|
||||
private long occupiedPassQps;
|
||||
|
||||
private String resource;
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public long getOccupiedPassQps() {
|
||||
return occupiedPassQps;
|
||||
}
|
||||
|
||||
public void setOccupiedPassQps(long occupiedPassQps) {
|
||||
this.occupiedPassQps = occupiedPassQps;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
|
@ -94,22 +107,17 @@ public class MetricNode {
|
|||
|
||||
@Override
|
||||
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>
|
||||
* timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt
|
||||
* timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps
|
||||
* </code>
|
||||
*
|
||||
* @return string format of this.
|
||||
|
|
@ -123,12 +131,13 @@ public class MetricNode {
|
|||
sb.append(blockQps).append("|");
|
||||
sb.append(successQps).append("|");
|
||||
sb.append(exceptionQps).append("|");
|
||||
sb.append(rt);
|
||||
sb.append(rt).append("|");
|
||||
sb.append(occupiedPassQps);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse {@link MetricNode} from thin string, see {@link #toThinString()} ()}
|
||||
* Parse {@link MetricNode} from thin string, see {@link #toThinString()}
|
||||
*
|
||||
* @param line
|
||||
* @return
|
||||
|
|
@ -143,14 +152,17 @@ public class MetricNode {
|
|||
node.setSuccessQps(Long.parseLong(strs[4]));
|
||||
node.setExceptionQps(Long.parseLong(strs[5]));
|
||||
node.setRt(Long.parseLong(strs[6]));
|
||||
if (strs.length == 8) {
|
||||
node.setOccupiedPassQps(Long.parseLong(strs[7]));
|
||||
}
|
||||
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>
|
||||
* 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>
|
||||
*
|
||||
* @return string format of this.
|
||||
|
|
@ -167,7 +179,8 @@ public class MetricNode {
|
|||
sb.append(getBlockQps()).append("|");
|
||||
sb.append(getSuccessQps()).append("|");
|
||||
sb.append(getExceptionQps()).append("|");
|
||||
sb.append(getRt());
|
||||
sb.append(getRt()).append("|");
|
||||
sb.append(getOccupiedPassQps());
|
||||
sb.append('\n');
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
@ -189,6 +202,9 @@ public class MetricNode {
|
|||
node.setSuccessQps(Long.parseLong(strs[5]));
|
||||
node.setExceptionQps(Long.parseLong(strs[6]));
|
||||
node.setRt(Long.parseLong(strs[7]));
|
||||
if (strs.length == 9) {
|
||||
node.setOccupiedPassQps(Long.parseLong(strs[8]));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ final class FlowRuleChecker {
|
|||
return true;
|
||||
}
|
||||
|
||||
return rule.getRater().canPass(selectedNode, acquireCount);
|
||||
return rule.getRater().canPass(selectedNode, acquireCount, prioritized);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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.flow.PriorityWaitException;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
|
||||
/**
|
||||
* Default throttling controller (immediately reject strategy).
|
||||
*
|
||||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class DefaultController implements TrafficShapingController {
|
||||
|
||||
|
|
@ -45,9 +49,22 @@ public class DefaultController implements TrafficShapingController {
|
|||
public boolean canPass(Node node, int acquireCount, boolean prioritized) {
|
||||
int curCount = avgUsedTokens(node);
|
||||
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 true;
|
||||
}
|
||||
|
||||
|
|
@ -55,10 +72,10 @@ public class DefaultController implements TrafficShapingController {
|
|||
if (node == null) {
|
||||
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 {
|
||||
Thread.sleep(timeMillis);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ public enum MetricEvent {
|
|||
EXCEPTION,
|
||||
SUCCESS,
|
||||
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.ProcessorSlotExitCallback;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException;
|
||||
import com.alibaba.csp.sentinel.util.TimeUtil;
|
||||
import com.alibaba.csp.sentinel.Constants;
|
||||
import com.alibaba.csp.sentinel.EntryType;
|
||||
|
|
@ -70,6 +71,21 @@ public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {
|
|||
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.
|
||||
for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) {
|
||||
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.
|
||||
* </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:
|
||||
* {@code sampleCount = intervalInMs / windowLengthInMs}.
|
||||
* </p>
|
||||
|
|
@ -54,8 +54,8 @@ public abstract class LeapArray<T> {
|
|||
/**
|
||||
* 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) {
|
||||
AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount);
|
||||
|
|
@ -66,7 +66,7 @@ public abstract class LeapArray<T> {
|
|||
this.intervalInMs = intervalInMs;
|
||||
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.
|
||||
*
|
||||
* @param timeMillis current time in milliseconds
|
||||
* @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.
|
||||
|
|
@ -94,7 +95,7 @@ public abstract class LeapArray<T> {
|
|||
*/
|
||||
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;
|
||||
// Calculate current index so we can map the timestamp to the leap array.
|
||||
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
|
||||
* 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)) {
|
||||
// Successfully updated, return the created bucket.
|
||||
return window;
|
||||
|
|
@ -193,7 +194,7 @@ public abstract class LeapArray<T> {
|
|||
}
|
||||
} else if (windowStart < old.windowStart()) {
|
||||
// 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public T getWindowValue(long time) {
|
||||
if (time < 0) {
|
||||
public T getWindowValue(long timeMillis) {
|
||||
if (timeMillis < 0) {
|
||||
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 old.value();
|
||||
return bucket.value();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -260,8 +262,12 @@ public abstract class LeapArray<T> {
|
|||
* @param windowWrap a non-null bucket
|
||||
* @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.
|
||||
*/
|
||||
public List<WindowWrap<T>> list() {
|
||||
return list(TimeUtil.currentTimeMillis());
|
||||
}
|
||||
|
||||
public List<WindowWrap<T>> list(long validTime) {
|
||||
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 || 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;
|
||||
}
|
||||
result.add(windowWrap);
|
||||
|
|
@ -292,12 +322,19 @@ public abstract class LeapArray<T> {
|
|||
* @return aggregated value list for entire sliding window
|
||||
*/
|
||||
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();
|
||||
List<T> result = new ArrayList<T>(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
WindowWrap<T> windowWrap = array.get(i);
|
||||
if (windowWrap == null || isWindowDeprecated(windowWrap)) {
|
||||
if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) {
|
||||
continue;
|
||||
}
|
||||
result.add(windowWrap.value());
|
||||
|
|
@ -359,4 +396,24 @@ public abstract class LeapArray<T> {
|
|||
public double getIntervalInSecond() {
|
||||
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
|
||||
public LongAdder newEmptyBucket() {
|
||||
public LongAdder newEmptyBucket(long time) {
|
||||
return new LongAdder();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,17 @@ public class WindowWrap<T> {
|
|||
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
|
||||
public String toString() {
|
||||
return "WindowWrap{" +
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ public class MetricBucket {
|
|||
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() {
|
||||
this.minRt = Constants.TIME_DROP_VALVE;
|
||||
}
|
||||
|
|
@ -70,6 +79,10 @@ public class MetricBucket {
|
|||
return get(MetricEvent.PASS);
|
||||
}
|
||||
|
||||
public long occupiedPass() {
|
||||
return get(MetricEvent.OCCUPIED_PASS);
|
||||
}
|
||||
|
||||
public long block() {
|
||||
return get(MetricEvent.BLOCK);
|
||||
}
|
||||
|
|
@ -94,6 +107,10 @@ public class MetricBucket {
|
|||
add(MetricEvent.PASS, n);
|
||||
}
|
||||
|
||||
public void addOccupiedPass(int n) {
|
||||
add(MetricEvent.OCCUPIED_PASS, n);
|
||||
}
|
||||
|
||||
public void addException(int n) {
|
||||
add(MetricEvent.EXCEPTION, n);
|
||||
}
|
||||
|
|
@ -114,4 +131,9 @@ public class MetricBucket {
|
|||
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.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.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 Eric Zhao
|
||||
*/
|
||||
public class ArrayMetric implements Metric {
|
||||
|
||||
private final MetricsLeapArray data;
|
||||
private final LeapArray<MetricBucket> data;
|
||||
|
||||
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.
|
||||
*/
|
||||
public ArrayMetric(MetricsLeapArray array) {
|
||||
public ArrayMetric(LeapArray<MetricBucket> array) {
|
||||
this.data = array;
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +115,17 @@ public class ArrayMetric implements Metric {
|
|||
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
|
||||
public long rt() {
|
||||
data.currentWindow();
|
||||
|
|
@ -133,7 +155,8 @@ public class ArrayMetric implements Metric {
|
|||
public List<MetricNode> details() {
|
||||
List<MetricNode> details = new ArrayList<MetricNode>();
|
||||
data.currentWindow();
|
||||
for (WindowWrap<MetricBucket> window : data.list()) {
|
||||
List<WindowWrap<MetricBucket>> list = data.list();
|
||||
for (WindowWrap<MetricBucket> window : list) {
|
||||
if (window == null) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -141,14 +164,16 @@ public class ArrayMetric implements Metric {
|
|||
node.setBlockQps(window.value().block());
|
||||
node.setExceptionQps(window.value().exception());
|
||||
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 {
|
||||
node.setRt(window.value().rt());
|
||||
}
|
||||
node.setTimestamp(window.windowStart());
|
||||
node.setOccupiedPassQps(window.value().occupiedPass());
|
||||
|
||||
details.add(node);
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +183,7 @@ public class ArrayMetric implements Metric {
|
|||
@Override
|
||||
public MetricBucket[] windows() {
|
||||
data.currentWindow();
|
||||
return data.values().toArray(new MetricBucket[data.values().size()]);
|
||||
return data.values().toArray(new MetricBucket[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -173,6 +198,17 @@ public class ArrayMetric implements Metric {
|
|||
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
|
||||
public void addSuccess(int count) {
|
||||
WindowWrap<MetricBucket> wrap = data.currentWindow();
|
||||
|
|
@ -192,18 +228,8 @@ public class ArrayMetric implements Metric {
|
|||
}
|
||||
|
||||
@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
|
||||
|
|
@ -226,6 +252,55 @@ public class ArrayMetric implements Metric {
|
|||
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
|
||||
public double getWindowIntervalInSec() {
|
||||
return data.getIntervalInSecond();
|
||||
|
|
|
|||
|
|
@ -16,24 +16,24 @@
|
|||
package com.alibaba.csp.sentinel.slots.statistic.metric;
|
||||
|
||||
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.data.MetricBucket;
|
||||
|
||||
/**
|
||||
* The fundamental data structure for metric statistics in a time span.
|
||||
*
|
||||
* @see LeapArray
|
||||
* @author jialiang.linjl
|
||||
* @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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricBucket newEmptyBucket() {
|
||||
public MetricBucket newEmptyBucket(long time) {
|
||||
return new MetricBucket();
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
|
|||
* @author jialiang.linjl
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public interface Metric {
|
||||
public interface Metric extends DebugSupport {
|
||||
|
||||
/**
|
||||
* Get total success count.
|
||||
|
|
@ -57,7 +57,7 @@ public interface Metric {
|
|||
long block();
|
||||
|
||||
/**
|
||||
* Get total pass count.
|
||||
* Get total pass count. not include {@link #occupiedPass()}
|
||||
*
|
||||
* @return pass count
|
||||
*/
|
||||
|
|
@ -92,22 +92,30 @@ public interface Metric {
|
|||
MetricBucket[] windows();
|
||||
|
||||
/**
|
||||
* Increment by one the current exception count.
|
||||
* Add current exception count.
|
||||
*
|
||||
* @param n count to add
|
||||
*/
|
||||
void addException(int n);
|
||||
|
||||
/**
|
||||
* Increment by one the current block count.
|
||||
* Add current block count.
|
||||
*
|
||||
* @param n count to add
|
||||
*/
|
||||
void addBlock(int n);
|
||||
|
||||
/**
|
||||
* Increment by one the current success count.
|
||||
* Add current completed count.
|
||||
*
|
||||
* @param n count to add
|
||||
*/
|
||||
void addSuccess(int n);
|
||||
|
||||
/**
|
||||
* Increment by one the current pass count.
|
||||
* Add current pass count.
|
||||
*
|
||||
* @param n count to add
|
||||
*/
|
||||
void addPass(int n);
|
||||
|
||||
|
|
@ -118,13 +126,65 @@ public interface Metric {
|
|||
*/
|
||||
void addRT(long rt);
|
||||
|
||||
/**
|
||||
* Get the sliding window length in seconds.
|
||||
*
|
||||
* @return the sliding window length
|
||||
*/
|
||||
double getWindowIntervalInSec();
|
||||
|
||||
/**
|
||||
* Get sample count of the sliding window.
|
||||
*
|
||||
* @return sample count of the sliding window.
|
||||
*/
|
||||
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);
|
||||
|
||||
void debugQps();
|
||||
// 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();
|
||||
|
||||
// Tool methods.
|
||||
|
||||
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 {
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ public class StatisticNodeTest {
|
|||
|
||||
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++) {
|
||||
bizTasks.add(new BizTask(node, taskBizExecuteCount));
|
||||
}
|
||||
|
|
@ -88,8 +88,8 @@ public class StatisticNodeTest {
|
|||
log("all biz task done, waiting 3 second to exit");
|
||||
sleep(3000);
|
||||
|
||||
bizEs.shutdown();
|
||||
tickEs.shutdown();
|
||||
bizEs.shutdownNow();
|
||||
tickEs.shutdownNow();
|
||||
|
||||
// now no biz method execute, so there is no curThreadNum,passQps,successQps
|
||||
assertEquals(0, node.curThreadNum(), 0.01);
|
||||
|
|
@ -192,7 +192,7 @@ public class StatisticNodeTest {
|
|||
log(SDF.format(new Date()) + " curThreadNum=" + node.curThreadNum() + ",passQps=" + node.passQps()
|
||||
+ ",successQps=" + node.successQps() + ",maxSuccessQps=" + node.maxSuccessQps()
|
||||
+ ",totalRequest=" + node.totalRequest() + ",totalSuccess=" + node.totalSuccess()
|
||||
+ ",avgRt=" + node.avgRt() + ",minRt=" + node.minRt());
|
||||
+ ", avgRt=" + String.format("%.2f", node.avgRt()) + ", minRt=" + node.minRt());
|
||||
}
|
||||
|
||||
private static void log(Object obj) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class LeapArrayTest extends AbstractTimeBasedTest {
|
|||
int sampleCount = intervalInMs / windowLengthInMs;
|
||||
LeapArray<AtomicInteger> leapArray = new LeapArray<AtomicInteger>(sampleCount, intervalInMs) {
|
||||
@Override
|
||||
public AtomicInteger newEmptyBucket() {
|
||||
public AtomicInteger newEmptyBucket(long time) {
|
||||
return new AtomicInteger(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,20 +13,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.csp.sentinel.base.metric;
|
||||
package com.alibaba.csp.sentinel.slots.statistic.metric;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket;
|
||||
|
||||
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.metric.ArrayMetric;
|
||||
import com.alibaba.csp.sentinel.slots.statistic.metric.MetricsLeapArray;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test cases for {@link ArrayMetric}.
|
||||
|
|
@ -39,7 +37,7 @@ public class ArrayMetricTest {
|
|||
|
||||
@Test
|
||||
public void testOperateArrayMetric() {
|
||||
MetricsLeapArray leapArray = mock(MetricsLeapArray.class);
|
||||
BucketLeapArray leapArray = mock(BucketLeapArray.class);
|
||||
final WindowWrap<MetricBucket> windowWrap = new WindowWrap<MetricBucket>(windowLengthInMs, 0, new MetricBucket());
|
||||
when(leapArray.currentWindow()).thenReturn(windowWrap);
|
||||
when(leapArray.values()).thenReturn(new ArrayList<MetricBucket>() {{ add(windowWrap.value()); }});
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* 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.HashSet;
|
||||
|
|
@ -21,22 +21,20 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
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.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 static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test cases for {@link MetricsLeapArray}.
|
||||
* Test cases for {@link BucketLeapArray}.
|
||||
*
|
||||
* @author Eric Zhao
|
||||
*/
|
||||
public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
||||
public class BucketLeapArrayTest {
|
||||
|
||||
private final int windowLengthInMs = 1000;
|
||||
private final int intervalInSec = 2;
|
||||
|
|
@ -45,7 +43,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
@Test
|
||||
public void testNewWindow() {
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long time = TimeUtil.currentTimeMillis();
|
||||
WindowWrap<MetricBucket> window = leapArray.currentWindow(time);
|
||||
|
||||
|
|
@ -57,7 +55,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
@Test
|
||||
public void testLeapArrayWindowStart() {
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long firstTime = TimeUtil.currentTimeMillis();
|
||||
long previousWindowStart = firstTime - firstTime % windowLengthInMs;
|
||||
|
||||
|
|
@ -69,7 +67,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
@Test
|
||||
public void testWindowAfterOneInterval() {
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long firstTime = TimeUtil.currentTimeMillis();
|
||||
long previousWindowStart = firstTime - firstTime % windowLengthInMs;
|
||||
WindowWrap<MetricBucket> window = leapArray.currentWindow(previousWindowStart);
|
||||
|
|
@ -109,7 +107,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
@Deprecated
|
||||
public void testWindowDeprecatedRefresh() {
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
final int len = sampleCount;
|
||||
long firstTime = TimeUtil.currentTimeMillis();
|
||||
List<WindowWrap<MetricBucket>> firstIterWindowList = new ArrayList<WindowWrap<MetricBucket>>(len);
|
||||
|
|
@ -129,7 +127,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
public void testMultiThreadUpdateEmptyWindow() throws Exception {
|
||||
final long time = TimeUtil.currentTimeMillis();
|
||||
final int nThreads = 16;
|
||||
final MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
final BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
final CountDownLatch latch = new CountDownLatch(nThreads);
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
|
|
@ -150,8 +148,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
@Test
|
||||
public void testGetPreviousWindow() {
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long time = TimeUtil.currentTimeMillis();
|
||||
WindowWrap<MetricBucket> previousWindow = leapArray.currentWindow(time);
|
||||
assertNull(leapArray.getPreviousWindow(time));
|
||||
|
|
@ -168,10 +165,8 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
final int windowLengthInMs = 100;
|
||||
final int intervalInMs = 1000;
|
||||
final int sampleCount = intervalInMs / windowLengthInMs;
|
||||
|
||||
setCurrentMillis(System.currentTimeMillis());
|
||||
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long time = TimeUtil.currentTimeMillis();
|
||||
|
||||
Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>();
|
||||
|
|
@ -184,7 +179,7 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
assertTrue(windowWraps.contains(wrap));
|
||||
}
|
||||
|
||||
sleep(windowLengthInMs + intervalInMs);
|
||||
Thread.sleep(windowLengthInMs + intervalInMs);
|
||||
|
||||
// This will replace the deprecated bucket, so all deprecated buckets will be reset.
|
||||
leapArray.currentWindow(time + windowLengthInMs + intervalInMs).value().addPass(1);
|
||||
|
|
@ -198,8 +193,8 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
final int intervalInSec = 1;
|
||||
final int intervalInMs = intervalInSec * 1000;
|
||||
final int sampleCount = intervalInMs / windowLengthInMs;
|
||||
|
||||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs);
|
||||
|
||||
BucketLeapArray leapArray = new BucketLeapArray(sampleCount, intervalInMs);
|
||||
long time = TimeUtil.currentTimeMillis();
|
||||
|
||||
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 + windowLengthInMs));
|
||||
|
||||
sleep(intervalInMs + windowLengthInMs * 3);
|
||||
Thread.sleep(intervalInMs + windowLengthInMs * 3);
|
||||
|
||||
List<WindowWrap<MetricBucket>> list = leapArray.list();
|
||||
for (WindowWrap<MetricBucket> wrap : list) {
|
||||
|
|
@ -220,4 +215,4 @@ public class MetricsLeapArrayTest extends AbstractTimeBasedTest {
|
|||
|
||||
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
|
||||
public ParamMapBucket newEmptyBucket() {
|
||||
public ParamMapBucket newEmptyBucket(long timeMillis) {
|
||||
return new ParamMapBucket();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue