Skip to content

Commit bb85ef6

Browse files
committed
Add test to detect memory leak
1 parent 799521f commit bb85ef6

2 files changed

Lines changed: 69 additions & 22 deletions

File tree

.github/workflows/ca-basic-test.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
--network-alias=pki.example.com \
4747
pki
4848
49+
docker exec pki rpm -qa | grep openjdk
50+
docker exec pki dnf install -y java-25-openjdk-devel
51+
4952
- name: Get Fedora version
5053
run: |
5154
FEDORA_VERSION=$(docker exec pki sed -n 's/^VERSION_ID=//p' /etc/os-release)
@@ -549,6 +552,62 @@ jobs:
549552
- name: Restart PKI server
550553
run: |
551554
docker exec pki pki-server restart --wait
555+
docker exec pki ps ax
556+
557+
PID=$(docker exec pki ps -C java -o pid --no-headers | awk '{print $1;}')
558+
echo "PID=$PID"
559+
echo "$PID" > java.pid
560+
561+
- name: Check heap before load test
562+
run: |
563+
PID=$(cat java.pid)
564+
565+
docker exec pki jhsdb jmap --histo --pid $PID \
566+
| tail -n +9 \
567+
| grep -E 'org.dogtagpki|org.mozilla' \
568+
| awk '{print $4, $2}' \
569+
| sort \
570+
| tee heap.before
571+
572+
- name: Run load test
573+
run: |
574+
for i in $(seq 1 100); do
575+
docker exec pki pki info
576+
done
577+
578+
- name: Run garbage collection
579+
run: |
580+
PID=$(cat java.pid)
581+
582+
docker exec pki jcmd $PID GC.run
583+
584+
- name: Check heap after load test
585+
run: |
586+
PID=$(cat java.pid)
587+
588+
# get object class names and number of instances
589+
docker exec pki jhsdb jmap --histo --pid $PID \
590+
| tail -n +9 \
591+
| grep -E 'org.dogtagpki|org.mozilla' \
592+
| awk '{print $4, $2}' \
593+
| sort \
594+
| tee heap.after
595+
596+
- name: Check heap growth
597+
run: |
598+
tests/bin/compare-heap-snapshots.py heap.before heap.after
599+
600+
- name: Check heap info
601+
run: |
602+
PID=$(cat java.pid)
603+
604+
docker exec pki jhsdb jmap --heap --pid $PID
605+
606+
- name: Check class loader statistics
607+
run: |
608+
PID=$(cat java.pid)
609+
610+
docker exec pki jmap -clstats $PID
552611
553612
- name: Check pki info with default API
554613
run: |

tests/bin/compare-heap-snapshots.py

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def calculate_growth(snapshot1: Dict[str, int], snapshot2: Dict[str, int]) -> Li
4444
all_classes = set(snapshot1.keys()) | set(snapshot2.keys())
4545

4646
for class_name in all_classes:
47-
count1 = snapshot1.get(class_name, 0)
48-
count2 = snapshot2.get(class_name, 0)
47+
original_count = snapshot1.get(class_name, 0)
48+
growth = snapshot2.get(class_name, 0) - original_count
4949

5050
# Calculate percentage growth
5151
if count1 == 0:
@@ -55,11 +55,11 @@ def calculate_growth(snapshot1: Dict[str, int], snapshot2: Dict[str, int]) -> Li
5555
else:
5656
continue
5757
else:
58-
growth_pct = ((count2 - count1) / count1) * 100
58+
growth_pct = (growth / original_count) * 100
5959

6060
# Only include classes that have grown
61-
if count2 > count1:
62-
growth_data.append((class_name, count1, count2, growth_pct))
61+
if growth > 0:
62+
growth_data.append((class_name, original_count, growth, growth_pct))
6363

6464
return growth_data
6565

@@ -70,31 +70,21 @@ def main():
7070
)
7171
parser.add_argument('snapshot1', help='First heap snapshot file')
7272
parser.add_argument('snapshot2', help='Second heap snapshot file')
73-
parser.add_argument('-n', '--top', type=int, default=10,
74-
help='Number of top growing classes to display (default: 10)')
7573

7674
args = parser.parse_args()
7775

78-
print(f"Comparing heap snapshots:")
79-
print(f" Snapshot 1: {args.snapshot1}")
80-
print(f" Snapshot 2: {args.snapshot2}")
81-
print()
82-
8376
# Parse both snapshots
8477
snapshot1 = parse_snapshot(args.snapshot1)
8578
snapshot2 = parse_snapshot(args.snapshot2)
8679

8780
if not snapshot1:
8881
print("Error: First snapshot is empty or invalid", file=sys.stderr)
8982
sys.exit(1)
83+
9084
if not snapshot2:
9185
print("Error: Second snapshot is empty or invalid", file=sys.stderr)
9286
sys.exit(1)
9387

94-
print(f"Classes in snapshot 1: {len(snapshot1)}")
95-
print(f"Classes in snapshot 2: {len(snapshot2)}")
96-
print()
97-
9888
# Calculate growth
9989
growth_data = calculate_growth(snapshot1, snapshot2)
10090

@@ -105,13 +95,11 @@ def main():
10595
# Sort by growth percentage (descending)
10696
growth_data.sort(key=lambda x: (x[3] != float('inf'), x[3]), reverse=True)
10797

108-
# Display top N classes
109-
print(f"Top {args.top} classes with highest growth:")
110-
print()
111-
print(f"{'Class Name':<60} {'Count 1':>12} {'Count 2':>12} {'Growth':>12}")
98+
# Display growth
99+
print(f"{'Class Name':<60} {'Original':>12} {'Growth':>12} {'Percentage':>12}")
112100
print("=" * 100)
113101

114-
for i, (class_name, count1, count2, growth_pct) in enumerate(growth_data[:args.top]):
102+
for i, (class_name, original_count, growth, growth_pct) in enumerate(growth_data):
115103
if growth_pct == float('inf'):
116104
growth_str = "NEW"
117105
else:
@@ -120,7 +108,7 @@ def main():
120108
# Truncate long class names
121109
display_name = class_name[:60] if len(class_name) <= 60 else class_name[:57] + "..."
122110

123-
print(f"{display_name:<60} {count1:>12,} {count2:>12,} {growth_str:>12}")
111+
print(f"{display_name:<60} {original_count:>12,} {growth:>12,} {growth_str:>12}")
124112

125113

126114
if __name__ == '__main__':

0 commit comments

Comments
 (0)