Skip to content

Commit 3ff1e70

Browse files
committed
tests: internal: add unit tests for log_sampling processor
Add unit tests for sampling algorithms: - Fixed window sampling and window resets - Sliding window with bucket expiration - Exponential decay rate calculations - Edge cases and boundary conditions Signed-off-by: Jorge Niedbalski <[email protected]>
1 parent c7166ee commit 3ff1e70

File tree

2 files changed

+248
-0
lines changed

2 files changed

+248
-0
lines changed

tests/internal/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ if(FLB_LUAJIT)
153153
)
154154
endif()
155155

156+
if(FLB_PROCESSOR_LOG_SAMPLING)
157+
set(UNIT_TESTS_FILES
158+
${UNIT_TESTS_FILES}
159+
log_sampling.c
160+
)
161+
endif()
162+
156163
set(UNIT_TESTS_DATA
157164
data/tls/certificate.pem
158165
data/tls/private_key.pem

tests/internal/log_sampling.c

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2+
3+
/* Fluent Bit
4+
* ==========
5+
* Copyright (C) 2015-2025 The Fluent Bit Authors
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
#include <fluent-bit/flb_info.h>
21+
#include <fluent-bit/flb_mem.h>
22+
#include <fluent-bit/flb_time.h>
23+
24+
#include "flb_tests_internal.h"
25+
26+
/* Import the sampling functions from the processor plugin */
27+
#include "../../plugins/processor_log_sampling/log_sampling.h"
28+
29+
static void test_fixed_window_basic()
30+
{
31+
struct sampling_state state = {0};
32+
int result;
33+
int i;
34+
int sampled_count = 0;
35+
time_t current_time = 1000; /* Start time */
36+
int window_size = 60;
37+
int max_logs = 5;
38+
39+
/* Initialize state */
40+
state.window_start = current_time;
41+
state.current_window_count = 0;
42+
43+
/* Test: First 5 logs should be sampled */
44+
for (i = 0; i < 10; i++) {
45+
result = flb_sampling_fixed_window(&state, current_time, window_size, max_logs);
46+
if (result == FLB_TRUE) {
47+
sampled_count++;
48+
}
49+
}
50+
51+
TEST_CHECK(sampled_count == 5);
52+
TEST_MSG("Fixed window: sampled %d out of 10 (expected 5)", sampled_count);
53+
54+
/* Test: New window should reset the count */
55+
current_time += window_size + 1; /* Move to next window */
56+
sampled_count = 0;
57+
58+
for (i = 0; i < 3; i++) {
59+
result = flb_sampling_fixed_window(&state, current_time, window_size, max_logs);
60+
if (result == FLB_TRUE) {
61+
sampled_count++;
62+
}
63+
}
64+
65+
TEST_CHECK(sampled_count == 3);
66+
TEST_MSG("Fixed window (new): sampled %d out of 3 (expected 3)", sampled_count);
67+
}
68+
69+
static void test_sliding_window_basic()
70+
{
71+
struct sampling_state state = {0};
72+
int result;
73+
int i;
74+
int sampled_count = 0;
75+
time_t current_time = 1000;
76+
int window_size = 10;
77+
int max_logs = 5;
78+
79+
/* Initialize sliding window buckets */
80+
state.bucket_count = window_size;
81+
state.buckets = flb_calloc(state.bucket_count, sizeof(*state.buckets));
82+
TEST_CHECK(state.buckets != NULL);
83+
84+
/* Test: First 5 logs should be sampled */
85+
for (i = 0; i < 7; i++) {
86+
result = flb_sampling_sliding_window(&state, current_time, window_size, max_logs);
87+
if (result == FLB_TRUE) {
88+
sampled_count++;
89+
}
90+
/* Advance time slightly to distribute across buckets */
91+
if (i % 2 == 0) {
92+
current_time++;
93+
}
94+
}
95+
96+
TEST_CHECK(sampled_count == 5);
97+
TEST_MSG("Sliding window: sampled %d out of 7 (expected 5)", sampled_count);
98+
99+
/* Test: Old entries should expire */
100+
current_time += window_size + 2; /* Move past window */
101+
sampled_count = 0;
102+
103+
for (i = 0; i < 3; i++) {
104+
result = flb_sampling_sliding_window(&state, current_time, window_size, max_logs);
105+
if (result == FLB_TRUE) {
106+
sampled_count++;
107+
}
108+
}
109+
110+
TEST_CHECK(sampled_count == 3);
111+
TEST_MSG("Sliding window (after expiry): sampled %d out of 3 (expected 3)", sampled_count);
112+
113+
flb_free(state.buckets);
114+
}
115+
116+
static void test_exponential_decay_basic()
117+
{
118+
int result;
119+
int i;
120+
int sampled_count;
121+
time_t window_start = 1000;
122+
time_t current_time = 1000;
123+
double base_rate = 1.0; /* 100% initially */
124+
double decay_factor = 0.5; /* 50% reduction per interval */
125+
int decay_interval = 10;
126+
127+
/* Seed random for reproducible tests */
128+
srand(12345);
129+
130+
/* Test: First interval should sample most logs (rate=1.0) */
131+
sampled_count = 0;
132+
for (i = 0; i < 100; i++) {
133+
result = flb_sampling_exponential(window_start, current_time,
134+
base_rate, decay_factor, decay_interval);
135+
if (result == FLB_TRUE) {
136+
sampled_count++;
137+
}
138+
}
139+
140+
/* With rate=1.0, should sample all or nearly all */
141+
TEST_CHECK(sampled_count >= 95);
142+
TEST_MSG("Exponential (interval 0): sampled %d out of 100 (expected >= 95)", sampled_count);
143+
144+
/* Test: Second interval should sample about 50% */
145+
current_time = window_start + decay_interval;
146+
sampled_count = 0;
147+
148+
for (i = 0; i < 100; i++) {
149+
result = flb_sampling_exponential(window_start, current_time,
150+
base_rate, decay_factor, decay_interval);
151+
if (result == FLB_TRUE) {
152+
sampled_count++;
153+
}
154+
}
155+
156+
/* With rate=0.5, should sample roughly 40-60% */
157+
TEST_CHECK(sampled_count >= 40 && sampled_count <= 60);
158+
TEST_MSG("Exponential (interval 1): sampled %d out of 100 (expected 40-60)", sampled_count);
159+
160+
/* Test: Third interval should sample about 25% */
161+
current_time = window_start + (2 * decay_interval);
162+
sampled_count = 0;
163+
164+
for (i = 0; i < 100; i++) {
165+
result = flb_sampling_exponential(window_start, current_time,
166+
base_rate, decay_factor, decay_interval);
167+
if (result == FLB_TRUE) {
168+
sampled_count++;
169+
}
170+
}
171+
172+
/* With rate=0.25, should sample roughly 20-30% */
173+
TEST_CHECK(sampled_count >= 15 && sampled_count <= 35);
174+
TEST_MSG("Exponential (interval 2): sampled %d out of 100 (expected 15-35)", sampled_count);
175+
}
176+
177+
static void test_fixed_window_edge_cases()
178+
{
179+
struct sampling_state state = {0};
180+
int result;
181+
time_t current_time = 1000;
182+
int window_size = 60;
183+
int max_logs = 0; /* Edge case: no logs allowed */
184+
185+
/* Test: max_logs = 0 should reject all */
186+
state.window_start = current_time;
187+
state.current_window_count = 0;
188+
189+
result = flb_sampling_fixed_window(&state, current_time, window_size, max_logs);
190+
TEST_CHECK(result == FLB_FALSE);
191+
192+
/* Test: Window boundary */
193+
max_logs = 1;
194+
state.current_window_count = 0;
195+
196+
result = flb_sampling_fixed_window(&state, current_time, window_size, max_logs);
197+
TEST_CHECK(result == FLB_TRUE);
198+
199+
/* Move to exact window boundary */
200+
current_time = state.window_start + window_size;
201+
result = flb_sampling_fixed_window(&state, current_time, window_size, max_logs);
202+
TEST_CHECK(result == FLB_TRUE); /* New window should allow */
203+
TEST_CHECK(state.current_window_count == 1);
204+
}
205+
206+
static void test_sliding_window_edge_cases()
207+
{
208+
struct sampling_state state = {0};
209+
int result;
210+
time_t current_time = 1000;
211+
int window_size = 1; /* Minimal window */
212+
int max_logs = 1;
213+
214+
/* Initialize with minimal bucket */
215+
state.bucket_count = 1;
216+
state.buckets = flb_calloc(1, sizeof(*state.buckets));
217+
TEST_CHECK(state.buckets != NULL);
218+
219+
/* Test: Single bucket behavior */
220+
result = flb_sampling_sliding_window(&state, current_time, window_size, max_logs);
221+
TEST_CHECK(result == FLB_TRUE);
222+
223+
result = flb_sampling_sliding_window(&state, current_time, window_size, max_logs);
224+
TEST_CHECK(result == FLB_FALSE); /* Already at limit */
225+
226+
/* Test: Bucket expiry */
227+
current_time += window_size + 1;
228+
result = flb_sampling_sliding_window(&state, current_time, window_size, max_logs);
229+
TEST_CHECK(result == FLB_TRUE); /* Old bucket should be expired */
230+
231+
flb_free(state.buckets);
232+
}
233+
234+
TEST_LIST = {
235+
{ "fixed_window_basic", test_fixed_window_basic },
236+
{ "sliding_window_basic", test_sliding_window_basic },
237+
{ "exponential_decay_basic", test_exponential_decay_basic },
238+
{ "fixed_window_edge_cases", test_fixed_window_edge_cases },
239+
{ "sliding_window_edge_cases", test_sliding_window_edge_cases },
240+
{ 0 }
241+
};

0 commit comments

Comments
 (0)