Skip to content

Commit 51e3d4b

Browse files
committed
add ut
1 parent a71331b commit 51e3d4b

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.catalog;
19+
20+
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.Test;
22+
23+
import java.lang.reflect.Constructor;
24+
import java.lang.reflect.Field;
25+
import java.lang.reflect.Method;
26+
import java.util.ArrayList;
27+
import java.util.Comparator;
28+
import java.util.List;
29+
30+
31+
/**
32+
* Unit tests for CloudTabletAccessStats sliding window logic.
33+
*
34+
* <p>We use reflection to test the private static inner class SlidingWindowCounter,
35+
* focusing on time-window counting, bucket expiration, cleanup and cache invalidation.
36+
*/
37+
public class TabletAccessStatsTest {
38+
39+
private static class CounterHarness {
40+
private final Object counter;
41+
private final Method add;
42+
private final Method getCount;
43+
private final Method cleanup;
44+
private final Method hasRecentActivity;
45+
private final Method getLastAccessTime;
46+
47+
CounterHarness(int numBuckets) throws Exception {
48+
Class<?> clazz = Class.forName(
49+
"org.apache.doris.catalog.TabletAccessStats$SlidingWindowCounter");
50+
Constructor<?> ctor = clazz.getDeclaredConstructor(int.class);
51+
ctor.setAccessible(true);
52+
this.counter = ctor.newInstance(numBuckets);
53+
54+
this.add = clazz.getDeclaredMethod("add", long.class, long.class, int.class);
55+
this.add.setAccessible(true);
56+
this.getCount = clazz.getDeclaredMethod("getCount", long.class, long.class, long.class, int.class);
57+
this.getCount.setAccessible(true);
58+
this.cleanup = clazz.getDeclaredMethod("cleanup", long.class, long.class);
59+
this.cleanup.setAccessible(true);
60+
this.hasRecentActivity = clazz.getDeclaredMethod("hasRecentActivity", long.class, long.class);
61+
this.hasRecentActivity.setAccessible(true);
62+
this.getLastAccessTime = clazz.getDeclaredMethod("getLastAccessTime");
63+
this.getLastAccessTime.setAccessible(true);
64+
}
65+
66+
void add(long currentTimeMs, long bucketSizeMs, int numBuckets) throws Exception {
67+
add.invoke(counter, currentTimeMs, bucketSizeMs, numBuckets);
68+
}
69+
70+
long getCount(long currentTimeMs, long timeWindowMs, long bucketSizeMs, int numBuckets) throws Exception {
71+
Object v = getCount.invoke(counter, currentTimeMs, timeWindowMs, bucketSizeMs, numBuckets);
72+
return (long) v;
73+
}
74+
75+
void cleanup(long currentTimeMs, long timeWindowMs) throws Exception {
76+
cleanup.invoke(counter, currentTimeMs, timeWindowMs);
77+
}
78+
79+
boolean hasRecentActivity(long currentTimeMs, long timeWindowMs) throws Exception {
80+
Object v = hasRecentActivity.invoke(counter, currentTimeMs, timeWindowMs);
81+
return (boolean) v;
82+
}
83+
84+
long getLastAccessTime() throws Exception {
85+
Object v = getLastAccessTime.invoke(counter);
86+
return (long) v;
87+
}
88+
}
89+
90+
@Test
91+
public void testAddAndGetCountWithinWindow() throws Exception {
92+
int numBuckets = 5;
93+
long bucketSizeMs = 1000L;
94+
long timeWindowMs = 5000L;
95+
long base = 10_000L; // avoid 0 (bucket timestamp 0 is treated as "unset" in getCount)
96+
97+
CounterHarness c = new CounterHarness(numBuckets);
98+
c.add(base, bucketSizeMs, numBuckets);
99+
c.add(base + 1000L, bucketSizeMs, numBuckets);
100+
c.add(base + 2000L, bucketSizeMs, numBuckets);
101+
102+
Assertions.assertEquals(base + 2000L, c.getLastAccessTime());
103+
Assertions.assertTrue(c.hasRecentActivity(base + 2000L, timeWindowMs));
104+
105+
long cnt = c.getCount(base + 2000L, timeWindowMs, bucketSizeMs, numBuckets);
106+
Assertions.assertEquals(3L, cnt);
107+
108+
// Cached result (same timestamp) should be consistent
109+
long cnt2 = c.getCount(base + 2000L, timeWindowMs, bucketSizeMs, numBuckets);
110+
Assertions.assertEquals(3L, cnt2);
111+
112+
// add() should invalidate cachedTotalCount
113+
c.add(base + 2500L, bucketSizeMs, numBuckets);
114+
long cnt3 = c.getCount(base + 2500L, timeWindowMs, bucketSizeMs, numBuckets);
115+
Assertions.assertEquals(4L, cnt3);
116+
}
117+
118+
@Test
119+
public void testWindowExpiresOldBuckets() throws Exception {
120+
int numBuckets = 5;
121+
long bucketSizeMs = 1000L;
122+
long timeWindowMs = 5000L;
123+
long base = 20_000L;
124+
125+
CounterHarness c = new CounterHarness(numBuckets);
126+
c.add(base, bucketSizeMs, numBuckets);
127+
c.add(base + 1000L, bucketSizeMs, numBuckets);
128+
c.add(base + 2000L, bucketSizeMs, numBuckets);
129+
130+
// At base + 6000, window start is base + 1000 => count should be 2 (1000 and 2000 buckets)
131+
long cnt1 = c.getCount(base + 6000L, timeWindowMs, bucketSizeMs, numBuckets);
132+
Assertions.assertEquals(2L, cnt1);
133+
134+
// At base + 7000, window start is base + 2000 => count should be 1 (2000 bucket)
135+
long cnt2 = c.getCount(base + 7000L, timeWindowMs, bucketSizeMs, numBuckets);
136+
Assertions.assertEquals(1L, cnt2);
137+
138+
// recent activity should still be true (lastAccessTime=base+2000 within 5000ms of base+7000? exactly 5000ms)
139+
Assertions.assertTrue(c.hasRecentActivity(base + 7000L, timeWindowMs));
140+
}
141+
142+
@Test
143+
public void testCleanupRemovesExpiredBuckets() throws Exception {
144+
int numBuckets = 5;
145+
long bucketSizeMs = 1000L;
146+
long timeWindowMs = 5000L;
147+
long base = 30_000L;
148+
149+
CounterHarness c = new CounterHarness(numBuckets);
150+
c.add(base, bucketSizeMs, numBuckets);
151+
c.add(base + 1000L, bucketSizeMs, numBuckets);
152+
c.add(base + 2000L, bucketSizeMs, numBuckets);
153+
154+
// cleanup at base + 7000: windowStart=base+2000, so base/base+1000 buckets should be cleared
155+
c.cleanup(base + 7000L, timeWindowMs);
156+
long cnt = c.getCount(base + 7000L, timeWindowMs, bucketSizeMs, numBuckets);
157+
Assertions.assertEquals(1L, cnt);
158+
}
159+
160+
@Test
161+
public void testBucketWrapAroundResetsExpiredBucket() throws Exception {
162+
int numBuckets = 5;
163+
long bucketSizeMs = 1000L;
164+
long timeWindowMs = 5000L;
165+
long base = 40_000L;
166+
167+
CounterHarness c = new CounterHarness(numBuckets);
168+
// bucket index wraps every numBuckets * bucketSizeMs (5s here)
169+
c.add(base, bucketSizeMs, numBuckets); // idx = 0
170+
c.add(base + 5000L, bucketSizeMs, numBuckets); // idx = 0 again, should reset old bucket
171+
172+
// With current implementation, the old "base" bucket is overwritten, so only 1 remains.
173+
long cnt = c.getCount(base + 5000L, timeWindowMs, bucketSizeMs, numBuckets);
174+
Assertions.assertEquals(1L, cnt);
175+
}
176+
177+
@Test
178+
public void testTopNComparatorOrdersByAccessCountThenLastAccessTimeDesc() throws Exception {
179+
Field f = TabletAccessStats.class.getDeclaredField("TOPN_ACTIVE_TABLET_COMPARATOR");
180+
f.setAccessible(true);
181+
@SuppressWarnings("unchecked")
182+
Comparator<TabletAccessStats.AccessStatsResult> cmp =
183+
(Comparator<TabletAccessStats.AccessStatsResult>) f.get(null);
184+
185+
List<TabletAccessStats.AccessStatsResult> results = new ArrayList<>();
186+
results.add(new TabletAccessStats.AccessStatsResult(1L, 1L, 200L));
187+
results.add(new TabletAccessStats.AccessStatsResult(2L, 10L, 100L));
188+
results.add(new TabletAccessStats.AccessStatsResult(3L, 10L, 300L));
189+
results.add(new TabletAccessStats.AccessStatsResult(4L, 2L, 400L));
190+
191+
results.sort(cmp);
192+
193+
// accessCount desc => 10 first, then 2, then 1
194+
// tie-breaker lastAccessTime desc => id=3 (300) before id=2 (100)
195+
Assertions.assertEquals(3L, results.get(0).id);
196+
Assertions.assertEquals(2L, results.get(1).id);
197+
Assertions.assertEquals(4L, results.get(2).id);
198+
Assertions.assertEquals(1L, results.get(3).id);
199+
}
200+
}
201+

0 commit comments

Comments
 (0)