@@ -26,11 +26,72 @@ import com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; | |||||
import com.alibaba.csp.sentinel.slots.statistic.metric.Metric; | import com.alibaba.csp.sentinel.slots.statistic.metric.Metric; | ||||
/** | /** | ||||
* <p>The statistic node keep three kinds of real-time statistics metrics:</p> | |||||
* <ol> | |||||
* <li>metrics in second level ({@code rollingCounterInSecond})</li> | |||||
* <li>metrics in minute level ({@code rollingCounterInMinute})</li> | |||||
* <li>thread count</li> | |||||
* </ol> | |||||
* | |||||
* <p> | |||||
* Sentinel use sliding window to record and count the resource statistics in real-time. | |||||
* The sliding window infrastructure behind the {@link ArrayMetric} is {@code LeapArray}. | |||||
* </p> | |||||
* | |||||
* <p> | |||||
* case 1: When the first request comes in, Sentinel will create a new window bucket of | |||||
* a specified time-span to store running statics, such as total response time(rt), | |||||
* incoming request(QPS), block request(bq), etc. And the time-span is defined by sample count. | |||||
* </p> | |||||
* <pre> | |||||
* 0 100ms | |||||
* +-------+--→ Sliding Windows | |||||
* ^ | |||||
* | | |||||
* request | |||||
* </pre> | |||||
* <p> | |||||
* Sentinel use the statics of the valid buckets to decide whether this request can be passed. | |||||
* For example, if a rule defines that only 100 requests can be passed, | |||||
* it will sum all qps in valid buckets, and compare it to the threshold defined in rule. | |||||
* </p> | |||||
* | |||||
* <p>case 2: continuous requests</p> | |||||
* <pre> | |||||
* 0 100ms 200ms 300ms | |||||
* +-------+-------+-------+-----→ Sliding Windows | |||||
* ^ | |||||
* | | |||||
* request | |||||
* </pre> | |||||
* | |||||
* <p>case 3: requests keeps coming, and previous buckets become invalid</p> | |||||
* <pre> | |||||
* 0 100ms 200ms 800ms 900ms 1000ms 1300ms | |||||
* +-------+-------+ ...... +-------+-------+ ...... +-------+-----→ Sliding Windows | |||||
* ^ | |||||
* | | |||||
* request | |||||
* </pre> | |||||
* | |||||
* <p>The sliding window should become:</p> | |||||
* <pre> | |||||
* 300ms 800ms 900ms 1000ms 1300ms | |||||
* + ...... +-------+ ...... +-------+-----→ Sliding Windows | |||||
* ^ | |||||
* | | |||||
* request | |||||
* </pre> | |||||
* | |||||
* @author qinan.qn | * @author qinan.qn | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
*/ | */ | ||||
public class StatisticNode implements Node { | 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(1000 / SampleCountProperty.SAMPLE_COUNT, | ||||
IntervalProperty.INTERVAL); | IntervalProperty.INTERVAL); | ||||
@@ -40,6 +101,9 @@ public class StatisticNode implements Node { | |||||
*/ | */ | ||||
private transient Metric rollingCounterInMinute = new ArrayMetric(1000, 60); | private transient Metric rollingCounterInMinute = new ArrayMetric(1000, 60); | ||||
/** | |||||
* The counter for thread count. | |||||
*/ | |||||
private AtomicInteger curThreadNum = new AtomicInteger(0); | private AtomicInteger curThreadNum = new AtomicInteger(0); | ||||
private long lastFetchTime = -1; | private long lastFetchTime = -1; | ||||
@@ -32,10 +32,10 @@ import com.alibaba.csp.sentinel.util.TimeUtil; | |||||
* {@link #sampleCount} = intervalInMs / windowLengthInMs. | * {@link #sampleCount} = intervalInMs / windowLengthInMs. | ||||
* </p> | * </p> | ||||
* | * | ||||
* @param <T> type of data bucket. | |||||
* @param <T> type of statistic data | |||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
* @author CarpenterLee | |||||
* @author Carpenter Lee | |||||
*/ | */ | ||||
public abstract class LeapArray<T> { | public abstract class LeapArray<T> { | ||||
@@ -48,7 +48,8 @@ public abstract class LeapArray<T> { | |||||
private final ReentrantLock updateLock = new ReentrantLock(); | private final ReentrantLock updateLock = new ReentrantLock(); | ||||
/** | /** | ||||
* The total bucket count is: {@link #sampleCount} = intervalInSec * 1000 / windowLengthInMs. | |||||
* The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. | |||||
* | |||||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | * @param windowLengthInMs a single window bucket's time length in milliseconds. | ||||
* @param intervalInSec the total time span of this {@link LeapArray} in seconds. | * @param intervalInSec the total time span of this {@link LeapArray} in seconds. | ||||
*/ | */ | ||||
@@ -61,74 +62,129 @@ public abstract class LeapArray<T> { | |||||
} | } | ||||
/** | /** | ||||
* Get the window at current timestamp. | |||||
* Get the bucket at current timestamp. | |||||
* | * | ||||
* @return the window at current timestamp | |||||
* @return the bucket at current timestamp | |||||
*/ | */ | ||||
public WindowWrap<T> currentWindow() { | public WindowWrap<T> currentWindow() { | ||||
return currentWindow(TimeUtil.currentTimeMillis()); | return currentWindow(TimeUtil.currentTimeMillis()); | ||||
} | } | ||||
/** | /** | ||||
* Create a new bucket. | |||||
* Create a new statistic value for bucket. | |||||
* | * | ||||
* @return the new empty bucket | * @return the new empty bucket | ||||
*/ | */ | ||||
public abstract T newEmptyBucket(); | public abstract T newEmptyBucket(); | ||||
/** | /** | ||||
* Reset current window to provided start time and reset all counters. | |||||
* Reset given bucket to provided start time and reset the value. | |||||
* | * | ||||
* @param startTime the start time of the window | |||||
* @param windowWrap current window | |||||
* @return new clean window wrap | |||||
* @param startTime the start time of the bucket | |||||
* @param windowWrap current bucket | |||||
* @return new clean bucket at given start time | |||||
*/ | */ | ||||
protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime); | protected abstract WindowWrap<T> resetWindowTo(WindowWrap<T> windowWrap, long startTime); | ||||
/** | /** | ||||
* Get window at provided timestamp. | |||||
* Get bucket item at provided timestamp. | |||||
* | * | ||||
* @param time a valid timestamp | * @param time a valid timestamp | ||||
* @return the window at provided timestamp | |||||
* @return current bucket item at provided timestamp | |||||
*/ | */ | ||||
public WindowWrap<T> currentWindow(long time) { | public WindowWrap<T> currentWindow(long time) { | ||||
long timeId = time / windowLengthInMs; | long timeId = time / windowLengthInMs; | ||||
// Calculate current index. | |||||
// Calculate current index so we can map the timestamp to the leap array. | |||||
int idx = (int)(timeId % array.length()); | int idx = (int)(timeId % array.length()); | ||||
// Cut the time to current window start. | |||||
time = time - time % windowLengthInMs; | |||||
// Calculate current bucket start time. | |||||
long windowStart = time - time % windowLengthInMs; | |||||
/* | |||||
* Get bucket item at given time from the array. | |||||
* | |||||
* (1) Bucket is absent, then just create a new bucket and CAS update to circular array. | |||||
* (2) Bucket is up-to-date, then just return the bucket. | |||||
* (3) Bucket is deprecated, then reset current bucket and clean all deprecated buckets. | |||||
*/ | |||||
while (true) { | while (true) { | ||||
WindowWrap<T> old = array.get(idx); | WindowWrap<T> old = array.get(idx); | ||||
if (old == null) { | if (old == null) { | ||||
WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket()); | |||||
/* | |||||
* B0 B1 B2 NULL B4 | |||||
* ||_______|_______|_______|_______|_______||___ | |||||
* 200 400 600 800 1000 1200 timestamp | |||||
* ^ | |||||
* time=888 | |||||
* bucket is empty, so create new and update | |||||
* | |||||
* If the old bucket is absent, then we create a new bucket at {@code windowStart}, | |||||
* 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()); | |||||
if (array.compareAndSet(idx, null, window)) { | if (array.compareAndSet(idx, null, window)) { | ||||
// Successfully updated, return the created bucket. | |||||
return window; | return window; | ||||
} else { | } else { | ||||
// Contention failed, the thread will yield its time slice to wait for bucket available. | |||||
Thread.yield(); | Thread.yield(); | ||||
} | } | ||||
} else if (time == old.windowStart()) { | |||||
} else if (windowStart == old.windowStart()) { | |||||
/* | |||||
* B0 B1 B2 B3 B4 | |||||
* ||_______|_______|_______|_______|_______||___ | |||||
* 200 400 600 800 1000 1200 timestamp | |||||
* ^ | |||||
* time=888 | |||||
* startTime of Bucket 3: 800, so it's up-to-date | |||||
* | |||||
* If current {@code windowStart} is equal to the start timestamp of old bucket, | |||||
* that means the time is within the bucket, so directly return the bucket. | |||||
*/ | |||||
return old; | return old; | ||||
} else if (time > old.windowStart()) { | |||||
} else if (windowStart > old.windowStart()) { | |||||
/* | |||||
* (old) | |||||
* B0 B1 B2 NULL B4 | |||||
* |_______||_______|_______|_______|_______|_______||___ | |||||
* ... 1200 1400 1600 1800 2000 2200 timestamp | |||||
* ^ | |||||
* time=1676 | |||||
* startTime of Bucket 2: 400, deprecated, should be reset | |||||
* | |||||
* If the start timestamp of old bucket is behind provided time, that means | |||||
* the bucket is deprecated. We have to reset the bucket to current {@code windowStart}. | |||||
* Note that the reset and clean-up operations are hard to be atomic, | |||||
* so we need a update lock to guarantee the correctness of bucket update. | |||||
* | |||||
* The update lock is fine-grained and will take effect only when | |||||
* bucket is deprecated, so in most cases it won't lead to performance loss. | |||||
*/ | |||||
if (updateLock.tryLock()) { | if (updateLock.tryLock()) { | ||||
try { | try { | ||||
// if (old is deprecated) then [LOCK] resetTo currentTime. | |||||
return resetWindowTo(old, time); | |||||
// Successfully get the update lock, now we reset the bucket. | |||||
return resetWindowTo(old, windowStart); | |||||
} finally { | } finally { | ||||
updateLock.unlock(); | updateLock.unlock(); | ||||
} | } | ||||
} else { | } else { | ||||
// Contention failed, the thread will yield its time slice to wait for bucket available. | |||||
Thread.yield(); | Thread.yield(); | ||||
} | } | ||||
} else if (time < old.windowStart()) { | |||||
// Cannot go through here. | |||||
return new WindowWrap<T>(windowLengthInMs, time, newEmptyBucket()); | |||||
} else if (windowStart < old.windowStart()) { | |||||
// Should not go through here, as the provided time is already behind. | |||||
return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket()); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Get the previous bucket item before provided timestamp. | |||||
* | |||||
* @param time a valid timestamp | |||||
* @return the previous bucket item before provided timestamp | |||||
*/ | |||||
public WindowWrap<T> getPreviousWindow(long time) { | public WindowWrap<T> getPreviousWindow(long time) { | ||||
long timeId = (time - windowLengthInMs) / windowLengthInMs; | long timeId = (time - windowLengthInMs) / windowLengthInMs; | ||||
int idx = (int)(timeId % array.length()); | int idx = (int)(timeId % array.length()); | ||||
@@ -146,10 +202,21 @@ public abstract class LeapArray<T> { | |||||
return wrap; | return wrap; | ||||
} | } | ||||
/** | |||||
* Get the previous bucket item for current timestamp. | |||||
* | |||||
* @return the previous bucket item for current timestamp | |||||
*/ | |||||
public WindowWrap<T> getPreviousWindow() { | public WindowWrap<T> getPreviousWindow() { | ||||
return getPreviousWindow(System.currentTimeMillis()); | |||||
return getPreviousWindow(TimeUtil.currentTimeMillis()); | |||||
} | } | ||||
/** | |||||
* Get statistic value from bucket for provided timestamp. | |||||
* | |||||
* @param time a valid timestamp | |||||
* @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null | |||||
*/ | |||||
public T getWindowValue(long time) { | public T getWindowValue(long time) { | ||||
long timeId = time / windowLengthInMs; | long timeId = time / windowLengthInMs; | ||||
int idx = (int)(timeId % array.length()); | int idx = (int)(timeId % array.length()); | ||||
@@ -162,10 +229,23 @@ public abstract class LeapArray<T> { | |||||
return old.value(); | return old.value(); | ||||
} | } | ||||
private boolean isWindowDeprecated(WindowWrap<T> windowWrap) { | |||||
/** | |||||
* Check if a bucket is deprecated, which means that the bucket | |||||
* has been behind for at least an entire window time span. | |||||
* | |||||
* @param windowWrap a non-null bucket | |||||
* @return true if the bucket is deprecated; otherwise false | |||||
*/ | |||||
private boolean isWindowDeprecated(/*@NonNull*/ WindowWrap<T> windowWrap) { | |||||
return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; | return TimeUtil.currentTimeMillis() - windowWrap.windowStart() >= intervalInMs; | ||||
} | } | ||||
/** | |||||
* Get valid bucket list for entire sliding window. | |||||
* The list will only contain "valid" buckets. | |||||
* | |||||
* @return valid bucket list for entire sliding window. | |||||
*/ | |||||
public List<WindowWrap<T>> list() { | public List<WindowWrap<T>> list() { | ||||
List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(); | List<WindowWrap<T>> result = new ArrayList<WindowWrap<T>>(); | ||||
@@ -180,6 +260,12 @@ public abstract class LeapArray<T> { | |||||
return result; | return result; | ||||
} | } | ||||
/** | |||||
* Get aggregated value list for entire sliding window. | |||||
* The list will only contain value from "valid" buckets. | |||||
* | |||||
* @return aggregated value list for entire sliding window | |||||
*/ | |||||
public List<T> values() { | public List<T> values() { | ||||
List<T> result = new ArrayList<T>(); | List<T> result = new ArrayList<T>(); | ||||
@@ -192,4 +278,22 @@ public abstract class LeapArray<T> { | |||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
/** | |||||
* Get sample count (total amount of buckets). | |||||
* | |||||
* @return sample count | |||||
*/ | |||||
public int getSampleCount() { | |||||
return sampleCount; | |||||
} | |||||
/** | |||||
* Get total interval length of the sliding window. | |||||
* | |||||
* @return interval in second | |||||
*/ | |||||
public int getIntervalInSecond() { | |||||
return intervalInMs / 1000; | |||||
} | |||||
} | } |
@@ -18,7 +18,7 @@ package com.alibaba.csp.sentinel.slots.statistic.base; | |||||
import com.alibaba.csp.sentinel.Constants; | import com.alibaba.csp.sentinel.Constants; | ||||
/** | /** | ||||
* Represents metrics data in a period of time window. | |||||
* Represents metrics data in a period of time span. | |||||
* | * | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
* @author Eric Zhao | * @author Eric Zhao | ||||
@@ -42,9 +42,9 @@ public class MetricBucket { | |||||
} | } | ||||
/** | /** | ||||
* Clean the adders and reset window to provided start time. | |||||
* Reset the adders. | |||||
* | * | ||||
* @return new clean window | |||||
* @return new metric bucket in initial state | |||||
*/ | */ | ||||
public MetricBucket reset() { | public MetricBucket reset() { | ||||
pass.reset(); | pass.reset(); | ||||
@@ -25,24 +25,24 @@ package com.alibaba.csp.sentinel.slots.statistic.base; | |||||
public class WindowWrap<T> { | public class WindowWrap<T> { | ||||
/** | /** | ||||
* a single window bucket's time length in milliseconds. | |||||
* Time length of a single window bucket in milliseconds. | |||||
*/ | */ | ||||
private final long windowLengthInMs; | private final long windowLengthInMs; | ||||
/** | /** | ||||
* Start time of the window in milliseconds. | |||||
* Start timestamp of the window in milliseconds. | |||||
*/ | */ | ||||
private long windowStart; | private long windowStart; | ||||
/** | /** | ||||
* Statistic value. | |||||
* Statistic data. | |||||
*/ | */ | ||||
private T value; | private T value; | ||||
/** | /** | ||||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | * @param windowLengthInMs a single window bucket's time length in milliseconds. | ||||
* @param windowStart the start timestamp of the window | |||||
* @param value window data | |||||
* @param windowStart the start timestamp of the window | |||||
* @param value statistic data | |||||
*/ | */ | ||||
public WindowWrap(long windowLengthInMs, long windowStart, T value) { | public WindowWrap(long windowLengthInMs, long windowStart, T value) { | ||||
this.windowLengthInMs = windowLengthInMs; | this.windowLengthInMs = windowLengthInMs; | ||||
@@ -66,6 +66,12 @@ public class WindowWrap<T> { | |||||
this.value = value; | this.value = value; | ||||
} | } | ||||
/** | |||||
* Reset start timestamp of current bucket to provided time. | |||||
* | |||||
* @param startTime valid start timestamp | |||||
* @return bucket after reset | |||||
*/ | |||||
public WindowWrap<T> resetTo(long startTime) { | public WindowWrap<T> resetTo(long startTime) { | ||||
this.windowStart = startTime; | this.windowStart = startTime; | ||||
return this; | return this; | ||||
@@ -34,8 +34,6 @@ public class ArrayMetric implements Metric { | |||||
private final MetricsLeapArray data; | private final MetricsLeapArray data; | ||||
/** | /** | ||||
* Constructor | |||||
* | |||||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | * @param windowLengthInMs a single window bucket's time length in milliseconds. | ||||
* @param intervalInSec the total time span of this {@link ArrayMetric} in seconds. | * @param intervalInSec the total time span of this {@link ArrayMetric} in seconds. | ||||
*/ | */ | ||||
@@ -35,6 +35,11 @@ public interface Metric { | |||||
*/ | */ | ||||
long success(); | long success(); | ||||
/** | |||||
* Get max success count. | |||||
* | |||||
* @return max success count | |||||
*/ | |||||
long maxSuccess(); | long maxSuccess(); | ||||
/** | /** | ||||
@@ -59,7 +64,7 @@ public interface Metric { | |||||
long pass(); | long pass(); | ||||
/** | /** | ||||
* Get total RT. | |||||
* Get total response time. | |||||
* | * | ||||
* @return total RT | * @return total RT | ||||
*/ | */ | ||||
@@ -72,6 +77,11 @@ public interface Metric { | |||||
*/ | */ | ||||
long minRt(); | long minRt(); | ||||
/** | |||||
* Get aggregated metric nodes of all resources. | |||||
* | |||||
* @return metric node list of all resources | |||||
*/ | |||||
List<MetricNode> details(); | List<MetricNode> details(); | ||||
/** | /** | ||||
@@ -20,7 +20,7 @@ import com.alibaba.csp.sentinel.slots.statistic.base.MetricBucket; | |||||
import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | ||||
/** | /** | ||||
* The fundamental data structure for metric statistics in a time window. | |||||
* The fundamental data structure for metric statistics in a time span. | |||||
* | * | ||||
* @see LeapArray | * @see LeapArray | ||||
* @author jialiang.linjl | * @author jialiang.linjl | ||||
@@ -29,8 +29,6 @@ import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; | |||||
public class MetricsLeapArray extends LeapArray<MetricBucket> { | public class MetricsLeapArray extends LeapArray<MetricBucket> { | ||||
/** | /** | ||||
* Constructor | |||||
* | |||||
* @param windowLengthInMs a single window bucket's time length in milliseconds. | * @param windowLengthInMs a single window bucket's time length in milliseconds. | ||||
* @param intervalInSec the total time span of this {@link MetricsLeapArray} in seconds. | * @param intervalInSec the total time span of this {@link MetricsLeapArray} in seconds. | ||||
*/ | */ | ||||
@@ -45,6 +43,7 @@ public class MetricsLeapArray extends LeapArray<MetricBucket> { | |||||
@Override | @Override | ||||
protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long startTime) { | protected WindowWrap<MetricBucket> resetWindowTo(WindowWrap<MetricBucket> w, long startTime) { | ||||
// Update the start time and reset value. | |||||
w.resetTo(startTime); | w.resetTo(startTime); | ||||
w.value().reset(); | w.value().reset(); | ||||
return w; | return w; | ||||
@@ -41,5 +41,5 @@ public interface CacheMap<K, V> { | |||||
void clear(); | void clear(); | ||||
Set<K> ascendingKeySet(); | |||||
Set<K> keySet(boolean ascending); | |||||
} | } |
@@ -86,7 +86,11 @@ public class ConcurrentLinkedHashMapWrapper<T, R> implements CacheMap<T, R> { | |||||
} | } | ||||
@Override | @Override | ||||
public Set<T> ascendingKeySet() { | |||||
return map.ascendingKeySet(); | |||||
public Set<T> keySet(boolean ascending) { | |||||
if (ascending) { | |||||
return map.ascendingKeySet(); | |||||
} else { | |||||
return map.descendingKeySet(); | |||||
} | |||||
} | } | ||||
} | } |
@@ -66,7 +66,11 @@ public class ParamMapBucket { | |||||
} | } | ||||
public Set<Object> ascendingKeySet(RollingParamEvent type) { | public Set<Object> ascendingKeySet(RollingParamEvent type) { | ||||
return data[type.ordinal()].ascendingKeySet(); | |||||
return data[type.ordinal()].keySet(true); | |||||
} | |||||
public Set<Object> descendingKeySet(RollingParamEvent type) { | |||||
return data[type.ordinal()].keySet(false); | |||||
} | } | ||||
public static final int DEFAULT_MAX_CAPACITY = 200; | public static final int DEFAULT_MAX_CAPACITY = 200; | ||||
@@ -60,10 +60,24 @@ public class HotParameterLeapArray extends LeapArray<ParamMapBucket> { | |||||
return w; | return w; | ||||
} | } | ||||
/** | |||||
* Add event count for specific parameter value. | |||||
* | |||||
* @param event target event | |||||
* @param count count to add | |||||
* @param value parameter value | |||||
*/ | |||||
public void addValue(RollingParamEvent event, int count, Object value) { | public void addValue(RollingParamEvent event, int count, Object value) { | ||||
currentWindow().value().add(event, count, value); | currentWindow().value().add(event, count, value); | ||||
} | } | ||||
/** | |||||
* Get "top-N" value-QPS map of provided event. | |||||
* | |||||
* @param event target event | |||||
* @param number max number of values | |||||
* @return "top-N" value map | |||||
*/ | |||||
public Map<Object, Double> getTopValues(RollingParamEvent event, int number) { | public Map<Object, Double> getTopValues(RollingParamEvent event, int number) { | ||||
currentWindow(); | currentWindow(); | ||||
List<ParamMapBucket> buckets = this.values(); | List<ParamMapBucket> buckets = this.values(); | ||||