Skip to content

Commit a9d3235

Browse files
authored
Gha memory leak test (#150)
* memoryleak-tests profile for better isolation * GHA memory leak test * debug * brush up analyzer * pom improvement to avoid running other tests than MemoryLeakTest * output minor update
1 parent d7b22e0 commit a9d3235

File tree

8 files changed

+415
-35
lines changed

8 files changed

+415
-35
lines changed

.github/workflows/pull-request.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,39 @@ jobs:
4545
definition-file: https://raw.githubusercontent.com/${GROUP:kiegroup}/drools-ansible-rulebook-integration/${BRANCH:main}/.ci/pull-request-config.yaml
4646
annotations-prefix: ${{ runner.os }}-${{ matrix.java-version }}/${{ matrix.maven-version }}
4747
github-token: "${{ secrets.GITHUB_TOKEN }}"
48+
49+
memory-leak-test:
50+
name: Memory Leak Test
51+
runs-on: ubuntu-latest
52+
timeout-minutes: 30
53+
steps:
54+
- name: Clean Disk Space
55+
uses: kiegroup/kie-ci/.ci/actions/ubuntu-disk-space@main
56+
57+
- name: Java and Maven Setup
58+
uses: kiegroup/kie-ci/.ci/actions/maven@main
59+
with:
60+
java-version: '17'
61+
maven-version: '3.9.3'
62+
63+
- name: Build Chain running MemoryLeakTest
64+
uses: kiegroup/kie-ci/.ci/actions/build-chain@main
65+
env:
66+
BUILD_MVN_OPTS_CURRENT: '-Pmemoryleak-tests'
67+
with:
68+
definition-file: https://raw.githubusercontent.com/${GROUP:kiegroup}/drools-ansible-rulebook-integration/${BRANCH:main}/.ci/pull-request-config.yaml
69+
github-token: "${{ secrets.GITHUB_TOKEN }}"
70+
71+
- name: Run load tests and analyze memory leaks
72+
run: |
73+
cd kiegroup_drools-ansible-rulebook-integration/drools-ansible-rulebook-integration-main
74+
chmod +x load_test_all.sh
75+
./load_test_all.sh
76+
77+
- name: Upload load test results
78+
uses: actions/upload-artifact@v4
79+
if: always()
80+
with:
81+
name: memory-leak-test-results
82+
path: kiegroup_drools-ansible-rulebook-integration/drools-ansible-rulebook-integration-main/result_all.txt
83+
retention-days: 30

Development_Notes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Test tips
2+
3+
Other than usual unit tests, we have some irregular tests:
4+
5+
- **MemoryLeakTest**: This test checks for memory leaks in the code. As they are slow and its results are not deterministic, they are not run by default. To test them, run `mvn test -Pmemoryleak-tests`.
6+
- **load_test_all.sh**: This script runs a load test with different numbers of events, to check memory usage tendencies. It is not run by maven. You can run it manually with `./load_test_all.sh`. `load_test.sh` is a flexible version of that. See the comments in the script for more information.
7+
8+
The above 2 tests are relatively important to detect memory leak issues. Added to github action `pull-request.yml`.
9+
10+
- **PerfTest**: This test contains various and relatively high load tests, which are run by default.
11+
- **SlownessTest**: This test verifies the behavior under the real slowness (but not very long). It is run by default.

drools-ansible-rulebook-integration-api/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,4 @@
6666
<scope>test</scope>
6767
</dependency>
6868
</dependencies>
69-
7069
</project>

drools-ansible-rulebook-integration-api/src/test/java/org/drools/ansible/rulebook/integration/api/MemoryLeakTest.java

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,27 @@
44
import java.util.concurrent.TimeUnit;
55

66
import org.drools.ansible.rulebook.integration.api.rulesengine.SessionStats;
7-
import org.junit.jupiter.api.Disabled;
87
import org.junit.jupiter.api.Test;
98
import org.junit.jupiter.api.Timeout;
109
import org.kie.api.runtime.rule.Match;
1110

1211
import static org.assertj.core.api.Assertions.assertThat;
1312

13+
/**
14+
* Memory leak tests - run with Maven profile:
15+
* mvn test -Pmemoryleak-tests
16+
*
17+
* These tests are excluded from the default test run to avoid instability
18+
* and excessive logging. The memoryleak-tests profile sets the appropriate
19+
* log level and runs only these tests.
20+
*/
1421
public class MemoryLeakTest {
1522

23+
static {
24+
// Set the log level to WARN to avoid excessive logging during tests, when running on IDE
25+
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "WARN");
26+
}
27+
1628
public static final String JSON_TTL =
1729
"""
1830
{
@@ -42,23 +54,21 @@ public class MemoryLeakTest {
4254
}
4355
""";
4456

45-
@Disabled("disabled by default as this could be unstable." +
46-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
4757
@Test
4858
@Timeout(120)
4959
void testMemoryLeakWithUnmatchEvents() {
5060
// If you set a short time for default_events_ttl, you can observe expiring jobs
5161

52-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
5362
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(JSON_TTL);
5463
System.gc();
5564
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
65+
System.out.println("BaseMemory = " + baseMemory);
5666
try {
5767
for (int i = 0; i < 100000; i++) {
5868
List<Match> matches = rulesExecutor.processEvents("{\"i\":5}").join();// not match
5969
assertThat(matches).isEmpty();
6070

61-
if (i % 2000 == 0) {
71+
if (i % 5000 == 0) {
6272
System.gc();
6373
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
6474
try {
@@ -107,21 +117,19 @@ void testMemoryLeakWithUnmatchEvents() {
107117
}
108118
""";
109119

110-
@Disabled("disabled by default as this could be unstable." +
111-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
112120
@Test
113121
@Timeout(120)
114122
public void testMemoryLeakWithMatchingEvents() {
115-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
116123
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(JSON_PLAIN);
117124
System.gc();
118125
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
126+
System.out.println("BaseMemory = " + baseMemory);
119127
try {
120128
for (int i = 0; i < 100000; i++) {
121129
List<Match> matches = rulesExecutor.processEvents("{\"i\":1}").join();
122130
assertThat(matches).hasSize(1);
123131

124-
if (i % 2000 == 0) {
132+
if (i % 5000 == 0) {
125133
System.gc();
126134
System.out.println(" usedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
127135
try {
@@ -179,15 +187,13 @@ public void testMemoryLeakWithMatchingEvents() {
179187
}
180188
""";
181189

182-
@Disabled("disabled by default as this could be unstable." +
183-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
184190
@Test
185191
@Timeout(120)
186192
public void testMemoryLeakWithAccumulateWithin() {
187-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
188193
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_ACCUMULATE_WITHIN);
189194
System.gc();
190195
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
196+
System.out.println("BaseMemory = " + baseMemory);
191197
try {
192198
// Test with multiple hosts to create different control events
193199
String[] hosts = {"host1", "host2", "host3", "host4", "host5"};
@@ -213,7 +219,7 @@ public void testMemoryLeakWithAccumulateWithin() {
213219

214220
rulesExecutor.advanceTime(1, java.util.concurrent.TimeUnit.SECONDS);
215221

216-
if (i % 2000 == 0) {
222+
if (i % 5000 == 0) {
217223
System.gc();
218224
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
219225
try {
@@ -279,15 +285,13 @@ public void testMemoryLeakWithAccumulateWithin() {
279285
}
280286
""";
281287

282-
@Disabled("disabled by default as this could be unstable." +
283-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
284288
@Test
285289
@Timeout(120)
286290
public void testMemoryLeakWithAccumulateWithinBelowThreshold() {
287-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
288291
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_ACCUMULATE_WITHIN_NO_THRESHOLD);
289292
System.gc();
290293
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
294+
System.out.println("BaseMemory = " + baseMemory);
291295
try {
292296
// Test with multiple hosts, but never reach threshold
293297
String[] hosts = {"host1", "host2", "host3", "host4", "host5"};
@@ -304,7 +308,7 @@ public void testMemoryLeakWithAccumulateWithinBelowThreshold() {
304308
// 1 second per event. For the same host, we will never reach the threshold of 10
305309
rulesExecutor.advanceTime(1, java.util.concurrent.TimeUnit.SECONDS);
306310

307-
if (i % 2000 == 0) {
311+
if (i % 5000 == 0) {
308312
System.gc();
309313
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
310314
try {
@@ -369,15 +373,13 @@ public void testMemoryLeakWithAccumulateWithinBelowThreshold() {
369373
}
370374
""";
371375

372-
@Disabled("disabled by default as this could be unstable." +
373-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
374376
@Test
375377
@Timeout(120)
376378
public void testMemoryLeakWithOnceWithin() {
377-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
378379
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_ONCE_WITHIN);
379380
System.gc();
380381
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
382+
System.out.println("BaseMemory = " + baseMemory);
381383
try {
382384
// Test with multiple hosts to create different control events
383385
String[] hosts = {"host1", "host2", "host3", "host4", "host5"};
@@ -403,7 +405,7 @@ public void testMemoryLeakWithOnceWithin() {
403405

404406
rulesExecutor.advanceTime(1, java.util.concurrent.TimeUnit.SECONDS);
405407

406-
if (i % 2000 == 0) {
408+
if (i % 5000 == 0) {
407409
System.gc();
408410
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
409411
try {
@@ -469,15 +471,13 @@ public void testMemoryLeakWithOnceWithin() {
469471
}
470472
""";
471473

472-
@Disabled("disabled by default as this could be unstable." +
473-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
474474
@Test
475475
@Timeout(120)
476476
public void testMemoryLeakWithOnceAfter() {
477-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "WARN");
478477
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_ONCE_AFTER);
479478
System.gc();
480479
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
480+
System.out.println("BaseMemory = " + baseMemory);
481481
try {
482482
// Test with multiple hosts to create different control events
483483
String[] hosts = {"host1", "host2", "host3", "host4", "host5"};
@@ -502,7 +502,7 @@ public void testMemoryLeakWithOnceAfter() {
502502
assertThat(matches).hasSize(1);
503503
assertThat(matches.get(0).getObjects()).hasSize(5);
504504

505-
if (i % 2000 == 0) {
505+
if (i % 5000 == 0) {
506506
System.gc();
507507
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
508508
try {
@@ -572,15 +572,13 @@ public void testMemoryLeakWithOnceAfter() {
572572
}
573573
""";
574574

575-
@Disabled("disabled by default as this could be unstable." +
576-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
577575
@Test
578576
@Timeout(120)
579577
public void testMemoryLeakWithTimedOut() {
580-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "WARN");
581578
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_TIMED_OUT);
582579
System.gc();
583580
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
581+
System.out.println("BaseMemory = " + baseMemory);
584582
try {
585583
for (int i = 0; i < 100000; i++) {
586584

@@ -613,7 +611,7 @@ public void testMemoryLeakWithTimedOut() {
613611

614612
// Advance time by 3 seconds (less than 5 second timeout)
615613

616-
if (i % 2000 == 0) {
614+
if (i % 5000 == 0) {
617615
System.gc();
618616
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
619617
try {
@@ -694,15 +692,13 @@ public void testMemoryLeakWithTimedOut() {
694692
}
695693
""";
696694

697-
@Disabled("disabled by default as this could be unstable." +
698-
" Also this test may flood the logs with DEBUG messages (SimpleLogger cannot change the log level dynamically)")
699695
@Test
700696
@Timeout(120)
701697
public void testMemoryLeakWithTimeWindow() {
702-
System.setProperty("org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration", "INFO");
703698
RulesExecutor rulesExecutor = RulesExecutorFactory.createFromJson(RuleNotation.CoreNotation.INSTANCE.withOptions(RuleConfigurationOption.USE_PSEUDO_CLOCK), JSON_TIME_WINDOW);
704699
System.gc();
705700
long baseMemory = rulesExecutor.getSessionStats().getUsedMemory();
701+
System.out.println("BaseMemory = " + baseMemory);
706702
try {
707703
for (int i = 0; i < 100000; i++) {
708704

@@ -729,7 +725,7 @@ public void testMemoryLeakWithTimeWindow() {
729725
matches = rulesExecutor.processEvents(event).join();
730726
assertThat(matches).hasSize(1);
731727

732-
if (i % 2000 == 0) {
728+
if (i % 5000 == 0) {
733729
System.gc();
734730
System.out.println(" UsedMemory = " + rulesExecutor.getSessionStats().getUsedMemory());
735731
try {

drools-ansible-rulebook-integration-api/src/test/resources/simplelogger.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
org.slf4j.simpleLogger.defaultLogLevel=info
33

44
# Logging detail level
5-
org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration=debug
5+
org.slf4j.simpleLogger.log.org.drools.ansible.rulebook.integration=info
66

77
# Set to true if you want the current date and time to be included in output messages.
88
# Default is false, and will output the number of milliseconds elapsed since startup.

drools-ansible-rulebook-integration-main/load_test_all.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,14 @@ for file in "${files[@]}"; do
3737
done
3838

3939
echo "All runs complete. See result_all.txt for any STDERR output (testfile, usedMemory, timeTaken)."
40+
41+
# Run MemoryLeakAnalyzer to check for memory leaks
42+
echo ""
43+
echo "Analyzing results for memory leaks..."
44+
java -cp "target/classes" org.drools.ansible.rulebook.integration.main.MemoryLeakAnalyzer result_all.txt
45+
46+
# Capture the exit code
47+
ANALYZER_EXIT_CODE=$?
48+
49+
# Exit with the same code as the analyzer
50+
exit $ANALYZER_EXIT_CODE

0 commit comments

Comments
 (0)