- The constructor now accept `sampleCount` and `windowIntervalMs` so that it can match the two basic properties Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -18,25 +18,27 @@ 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; | |||
import com.alibaba.csp.sentinel.slots.block.RuleConstant; | |||
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; | |||
/*** | |||
/** | |||
* QPS statistics interval. | |||
* | |||
* @author youji.zj | |||
* @author jialiang.linjl | |||
* @author CarpenterLee | |||
* @author Carpenter Lee | |||
* @author Eric Zhao | |||
*/ | |||
public class IntervalProperty { | |||
/** | |||
* <p>Interval in milliseconds. This variable determines sensitivity of the QPS calculation.</p> | |||
* <p> | |||
* Interval in seconds. This variable determines sensitivity of the QPS calculation. | |||
* </p> | |||
* DO NOT MODIFY this value directly, use {@link #updateInterval(int)}, otherwise the modification will not | |||
* take effect. | |||
* </p> | |||
*/ | |||
public static volatile int INTERVAL = 1; | |||
public static volatile int INTERVAL = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; | |||
public static void register2Property(SentinelProperty<Integer> property) { | |||
property.addListener(new SimplePropertyListener<Integer>() { | |||
@@ -60,7 +62,7 @@ public class IntervalProperty { | |||
INTERVAL = newInterval; | |||
ClusterBuilderSlot.resetClusterNodes(); | |||
} | |||
RecordLog.info("INTERVAL updated to: " + INTERVAL); | |||
RecordLog.info("[IntervalProperty] INTERVAL updated to: " + INTERVAL); | |||
} | |||
} |
@@ -92,14 +92,14 @@ public class StatisticNode implements Node { | |||
* Holds statistics of the recent {@code INTERVAL} seconds. The {@code INTERVAL} is divided into time spans | |||
* by given {@code sampleCount}. | |||
*/ | |||
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT, | |||
private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, | |||
IntervalProperty.INTERVAL); | |||
/** | |||
* 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(1000, 60); | |||
private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000); | |||
/** | |||
* The counter for thread count. | |||
@@ -142,7 +142,7 @@ public class StatisticNode implements Node { | |||
@Override | |||
public void reset() { | |||
rollingCounterInSecond = new ArrayMetric(1000 / SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); | |||
rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); | |||
} | |||
@Override | |||
@@ -158,7 +158,7 @@ public class StatisticNode implements Node { | |||
@Override | |||
public long blockQps() { | |||
return rollingCounterInSecond.block() / IntervalProperty.INTERVAL; | |||
return rollingCounterInSecond.block() / (long) rollingCounterInSecond.getWindowIntervalInSec(); | |||
} | |||
@Override | |||
@@ -183,7 +183,7 @@ public class StatisticNode implements Node { | |||
@Override | |||
public long exceptionQps() { | |||
return rollingCounterInSecond.exception() / IntervalProperty.INTERVAL; | |||
return rollingCounterInSecond.exception() / (long) rollingCounterInSecond.getWindowIntervalInSec(); | |||
} | |||
@Override | |||
@@ -193,17 +193,17 @@ public class StatisticNode implements Node { | |||
@Override | |||
public long passQps() { | |||
return rollingCounterInSecond.pass() / IntervalProperty.INTERVAL; | |||
return rollingCounterInSecond.pass() / (long) rollingCounterInSecond.getWindowIntervalInSec(); | |||
} | |||
@Override | |||
public long successQps() { | |||
return rollingCounterInSecond.success() / IntervalProperty.INTERVAL; | |||
return rollingCounterInSecond.success() / (long) rollingCounterInSecond.getWindowIntervalInSec(); | |||
} | |||
@Override | |||
public long maxSuccessQps() { | |||
return rollingCounterInSecond.maxSuccess() * SampleCountProperty.SAMPLE_COUNT; | |||
return rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount(); | |||
} | |||
@Override | |||
@@ -51,5 +51,8 @@ public final class RuleConstant { | |||
public static final String LIMIT_APP_DEFAULT = "default"; | |||
public static final String LIMIT_APP_OTHER = "other"; | |||
public static final int DEFAULT_SAMPLE_COUNT = 2; | |||
public static final int DEFAULT_WINDOW_INTERVAL_MS = 1000; | |||
private RuleConstant() {} | |||
} |
@@ -54,20 +54,17 @@ public abstract class LeapArray<T> { | |||
/** | |||
* The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. | |||
* | |||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | |||
* @param intervalInSec the total time span of this {@link LeapArray} in seconds. | |||
* @param sampleCount bucket count of the sliding window | |||
* @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds | |||
*/ | |||
public LeapArray(int windowLengthInMs, int intervalInSec) { | |||
// TODO: change `intervalInSec` to `intervalInMs` | |||
AssertUtil.isTrue(windowLengthInMs > 0, "bucket length is invalid: " + windowLengthInMs); | |||
int intervalInMs = intervalInSec * 1000; | |||
AssertUtil.isTrue(intervalInMs > windowLengthInMs, | |||
"total time span of the window should be greater than bucket length"); | |||
AssertUtil.isTrue(intervalInMs % windowLengthInMs == 0, "time span needs to be evenly divided"); | |||
this.windowLengthInMs = windowLengthInMs; | |||
public LeapArray(int sampleCount, int intervalInMs) { | |||
AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount); | |||
AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive"); | |||
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); | |||
this.windowLengthInMs = intervalInMs / sampleCount; | |||
this.intervalInMs = intervalInMs; | |||
this.sampleCount = intervalInMs / windowLengthInMs; | |||
this.sampleCount = sampleCount; | |||
this.array = new AtomicReferenceArray<WindowWrap<T>>(sampleCount); | |||
} | |||
@@ -345,12 +342,21 @@ public abstract class LeapArray<T> { | |||
return sampleCount; | |||
} | |||
/** | |||
* Get total interval length of the sliding window in milliseconds. | |||
* | |||
* @return interval in second | |||
*/ | |||
public int getIntervalInMs() { | |||
return intervalInMs; | |||
} | |||
/** | |||
* Get total interval length of the sliding window. | |||
* | |||
* @return interval in second | |||
*/ | |||
public int getIntervalInSecond() { | |||
return intervalInMs / 1000; | |||
public double getIntervalInSecond() { | |||
return intervalInMs / 1000.0; | |||
} | |||
} |
@@ -33,12 +33,8 @@ public class ArrayMetric implements Metric { | |||
private final MetricsLeapArray data; | |||
/** | |||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | |||
* @param intervalInSec the total time span of this {@link ArrayMetric} in seconds. | |||
*/ | |||
public ArrayMetric(int windowLengthInMs, int intervalInSec) { | |||
this.data = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
public ArrayMetric(int sampleCount, int intervalInMs) { | |||
this.data = new MetricsLeapArray(sampleCount, intervalInMs); | |||
} | |||
/** | |||
@@ -229,4 +225,14 @@ public class ArrayMetric implements Metric { | |||
} | |||
return wrap.value().pass(); | |||
} | |||
@Override | |||
public double getWindowIntervalInSec() { | |||
return data.getIntervalInSecond(); | |||
} | |||
@Override | |||
public int getSampleCount() { | |||
return data.getSampleCount(); | |||
} | |||
} |
@@ -118,6 +118,10 @@ public interface Metric { | |||
*/ | |||
void addRT(long rt); | |||
double getWindowIntervalInSec(); | |||
int getSampleCount(); | |||
// Tool methods. | |||
void debugQps(); | |||
@@ -28,12 +28,8 @@ import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||
*/ | |||
public class MetricsLeapArray extends LeapArray<MetricBucket> { | |||
/** | |||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | |||
* @param intervalInSec the total time span of this {@link MetricsLeapArray} in seconds. | |||
*/ | |||
public MetricsLeapArray(int windowLengthInMs, int intervalInSec) { | |||
super(windowLengthInMs, intervalInSec); | |||
public MetricsLeapArray(int sampleCount, int intervalInMs) { | |||
super(sampleCount, intervalInMs); | |||
} | |||
@Override | |||
@@ -36,7 +36,6 @@ import static org.mockito.Mockito.*; | |||
public class ArrayMetricTest { | |||
private final int windowLengthInMs = 500; | |||
private final int intervalInSec = 1; | |||
@Test | |||
public void testOperateArrayMetric() { | |||
@@ -39,10 +39,12 @@ public class MetricsLeapArrayTest { | |||
private final int windowLengthInMs = 1000; | |||
private final int intervalInSec = 2; | |||
private final int intervalInMs = intervalInSec * 1000; | |||
private final int sampleCount = intervalInMs / windowLengthInMs; | |||
@Test | |||
public void testNewWindow() { | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long time = TimeUtil.currentTimeMillis(); | |||
WindowWrap<MetricBucket> window = leapArray.currentWindow(time); | |||
@@ -54,7 +56,7 @@ public class MetricsLeapArrayTest { | |||
@Test | |||
public void testLeapArrayWindowStart() { | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long firstTime = TimeUtil.currentTimeMillis(); | |||
long previousWindowStart = firstTime - firstTime % windowLengthInMs; | |||
@@ -66,7 +68,7 @@ public class MetricsLeapArrayTest { | |||
@Test | |||
public void testWindowAfterOneInterval() { | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long firstTime = TimeUtil.currentTimeMillis(); | |||
long previousWindowStart = firstTime - firstTime % windowLengthInMs; | |||
WindowWrap<MetricBucket> window = leapArray.currentWindow(previousWindowStart); | |||
@@ -106,8 +108,8 @@ public class MetricsLeapArrayTest { | |||
@Deprecated | |||
public void testWindowDeprecatedRefresh() { | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
final int len = intervalInSec * 1000 / windowLengthInMs; | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
final int len = sampleCount; | |||
long firstTime = TimeUtil.currentTimeMillis(); | |||
List<WindowWrap<MetricBucket>> firstIterWindowList = new ArrayList<WindowWrap<MetricBucket>>(len); | |||
for (int i = 0; i < len; i++) { | |||
@@ -126,7 +128,7 @@ public class MetricsLeapArrayTest { | |||
public void testMultiThreadUpdateEmptyWindow() throws Exception { | |||
final long time = TimeUtil.currentTimeMillis(); | |||
final int nThreads = 16; | |||
final MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
final MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
final CountDownLatch latch = new CountDownLatch(nThreads); | |||
Runnable task = new Runnable() { | |||
@Override | |||
@@ -147,7 +149,7 @@ public class MetricsLeapArrayTest { | |||
@Test | |||
public void testGetPreviousWindow() { | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long time = TimeUtil.currentTimeMillis(); | |||
WindowWrap<MetricBucket> previousWindow = leapArray.currentWindow(time); | |||
assertNull(leapArray.getPreviousWindow(time)); | |||
@@ -162,10 +164,10 @@ public class MetricsLeapArrayTest { | |||
@Test | |||
public void testListWindowsResetOld() throws Exception { | |||
final int windowLengthInMs = 100; | |||
final int intervalInSec = 1; | |||
final int intervalInMs = intervalInSec * 1000; | |||
final int intervalInMs = 1000; | |||
final int sampleCount = intervalInMs / windowLengthInMs; | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long time = TimeUtil.currentTimeMillis(); | |||
Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | |||
@@ -190,8 +192,10 @@ public class MetricsLeapArrayTest { | |||
public void testListWindowsNewBucket() throws Exception { | |||
final int windowLengthInMs = 100; | |||
final int intervalInSec = 1; | |||
final int intervalInMs = intervalInSec * 1000; | |||
final int sampleCount = intervalInMs / windowLengthInMs; | |||
MetricsLeapArray leapArray = new MetricsLeapArray(windowLengthInMs, intervalInSec); | |||
MetricsLeapArray leapArray = new MetricsLeapArray(sampleCount, intervalInMs); | |||
long time = TimeUtil.currentTimeMillis(); | |||
Set<WindowWrap<MetricBucket>> windowWraps = new HashSet<WindowWrap<MetricBucket>>(); | |||
@@ -29,9 +29,9 @@ public class LeapArrayTest { | |||
@Test | |||
public void testGetValidHead() { | |||
int windowLengthInMs = 100; | |||
int intervalInSec = 1; | |||
int sampleCount = intervalInSec * 1000 / windowLengthInMs; | |||
LeapArray<AtomicInteger> leapArray = new LeapArray<AtomicInteger>(windowLengthInMs, intervalInSec) { | |||
int intervalInMs = 1000; | |||
int sampleCount = intervalInMs / windowLengthInMs; | |||
LeapArray<AtomicInteger> leapArray = new LeapArray<AtomicInteger>(sampleCount, intervalInMs) { | |||
@Override | |||
public AtomicInteger newEmptyBucket() { | |||
return new AtomicInteger(0); | |||
@@ -16,7 +16,6 @@ | |||
package com.alibaba.csp.sentinel.slots.block.flow.param; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
@@ -25,6 +25,7 @@ import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.node.IntervalProperty; | |||
import com.alibaba.csp.sentinel.node.SampleCountProperty; | |||
import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
/** | |||
* Metrics for frequent ("hot spot") parameters. | |||
@@ -34,6 +35,21 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.HotParameterLeapArray; | |||
*/ | |||
public class ParameterMetric { | |||
private final int sampleCount; | |||
private final int intervalMs; | |||
public ParameterMetric() { | |||
this(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); | |||
} | |||
public ParameterMetric(int sampleCount, int intervalInMs) { | |||
AssertUtil.isTrue(sampleCount > 0, "sampleCount should be positive"); | |||
AssertUtil.isTrue(intervalInMs > 0, "window interval should be positive"); | |||
AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); | |||
this.sampleCount = sampleCount; | |||
this.intervalMs = intervalInMs; | |||
} | |||
private Map<Integer, HotParameterLeapArray> rollingParameters = | |||
new ConcurrentHashMap<Integer, HotParameterLeapArray>(); | |||
@@ -50,8 +66,7 @@ public class ParameterMetric { | |||
synchronized (this) { | |||
// putIfAbsent | |||
if (rollingParameters.get(index) == null) { | |||
rollingParameters.put(index, new HotParameterLeapArray( | |||
1000 / SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL)); | |||
rollingParameters.put(index, new HotParameterLeapArray(sampleCount, intervalMs)); | |||
} | |||
} | |||
} | |||
@@ -37,15 +37,8 @@ import com.alibaba.csp.sentinel.slots.statistic.data.ParamMapBucket; | |||
*/ | |||
public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||
private int intervalInSec; | |||
public HotParameterLeapArray(int windowLengthInMs, int intervalInSec) { | |||
super(windowLengthInMs, intervalInSec); | |||
this.intervalInSec = intervalInSec; | |||
} | |||
public int getIntervalInSec() { | |||
return intervalInSec; | |||
public HotParameterLeapArray(int sampleCount, int intervalInMs) { | |||
super(sampleCount, intervalInMs); | |||
} | |||
@Override | |||
@@ -116,7 +109,7 @@ public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||
if (x.getValue() == 0) { | |||
break; | |||
} | |||
doubleResult.put(x.getKey(), ((double)x.getValue()) / getIntervalInSec()); | |||
doubleResult.put(x.getKey(), ((double)x.getValue()) / getIntervalInSecond()); | |||
} | |||
return doubleResult; | |||
@@ -136,6 +129,6 @@ public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||
} | |||
public double getRollingAvg(RollingParamEvent event, Object value) { | |||
return ((double) getRollingSum(event, value)) / getIntervalInSec(); | |||
return ((double) getRollingSum(event, value)) / getIntervalInSecond(); | |||
} | |||
} |
@@ -60,7 +60,7 @@ public class HotParameterLeapArrayTest { | |||
int a1 = 3, a2 = 5; | |||
String paramPrefix = "param-"; | |||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||
when(leapArray.getIntervalInSec()).thenReturn(intervalInSec); | |||
when(leapArray.getIntervalInSecond()).thenReturn((double) intervalInSec); | |||
final ParamMapBucket b1 = generateBucket(a1, paramPrefix); | |||
final ParamMapBucket b2 = generateBucket(a2, paramPrefix); | |||
@@ -122,8 +122,8 @@ public class HotParameterLeapArrayTest { | |||
public void testGetRollingAvg() { | |||
HotParameterLeapArray leapArray = mock(HotParameterLeapArray.class); | |||
when(leapArray.getRollingSum(any(RollingParamEvent.class), any(Object.class))).thenReturn(15L); | |||
when(leapArray.getIntervalInSec()).thenReturn(1) | |||
.thenReturn(2); | |||
when(leapArray.getIntervalInSecond()).thenReturn(1d) | |||
.thenReturn(2d); | |||
when(leapArray.getRollingAvg(any(RollingParamEvent.class), any(Object.class))).thenCallRealMethod(); | |||
assertEquals(15.0d, leapArray.getRollingAvg(RollingParamEvent.REQUEST_PASSED, "abc"), 0.001); | |||