Skip to content

Commit 3c952a6

Browse files
committed
HHH-19240 Improve memory consumption testing
(cherry picked from commit 411bae9)
1 parent 0b7c64b commit 3c952a6

File tree

7 files changed

+488
-88
lines changed

7 files changed

+488
-88
lines changed

hibernate-core/src/test/java/org/hibernate/orm/test/hql/HqlParserMemoryUsageTest.java

Lines changed: 36 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import jakarta.persistence.Table;
1313
import org.hibernate.cfg.QuerySettings;
1414
import org.hibernate.query.hql.HqlTranslator;
15-
import org.hibernate.query.sqm.tree.SqmStatement;
15+
import org.hibernate.testing.memory.MemoryUsageUtil;
1616
import org.hibernate.testing.orm.junit.DomainModel;
1717
import org.hibernate.testing.orm.junit.Jira;
1818
import org.hibernate.testing.orm.junit.ServiceRegistry;
@@ -41,104 +41,52 @@
4141
@Jira("https://hibernate.atlassian.net/browse/HHH-19240")
4242
public class HqlParserMemoryUsageTest {
4343

44-
private static final String HQL = """
45-
SELECT DISTINCT u.id
46-
FROM AppUser u
47-
LEFT JOIN u.addresses a
48-
LEFT JOIN u.orders o
49-
LEFT JOIN o.orderItems oi
50-
LEFT JOIN oi.product p
51-
LEFT JOIN p.discounts d
52-
WHERE u.id = :userId
53-
AND (
54-
CASE
55-
WHEN u.name = 'SPECIAL_USER' THEN TRUE
56-
ELSE (
57-
CASE
58-
WHEN a.city = 'New York' THEN TRUE
59-
ELSE (
60-
p.category.name = 'Electronics'
61-
OR d.code LIKE '%DISC%'
62-
OR u.id IN (
63-
SELECT u2.id
64-
FROM AppUser u2
65-
JOIN u2.orders o2
66-
JOIN o2.orderItems oi2
67-
JOIN oi2.product p2
68-
WHERE p2.price > (
69-
SELECT AVG(p3.price) FROM Product p3
70-
)
71-
)
72-
)
73-
END
74-
)
75-
END
76-
)
77-
""";
44+
private static final String HQL = "SELECT DISTINCT u.id\n" +
45+
"FROM AppUser u\n" +
46+
"LEFT JOIN u.addresses a\n" +
47+
"LEFT JOIN u.orders o\n" +
48+
"LEFT JOIN o.orderItems oi\n" +
49+
"LEFT JOIN oi.product p\n" +
50+
"LEFT JOIN p.discounts d\n" +
51+
"WHERE u.id = :userId\n" +
52+
"AND (\n" +
53+
" CASE\n" +
54+
" WHEN u.name = 'SPECIAL_USER' THEN TRUE\n" +
55+
" ELSE (\n" +
56+
" CASE\n" +
57+
" WHEN a.city = 'New York' THEN TRUE\n" +
58+
" ELSE (\n" +
59+
" p.category.name = 'Electronics'\n" +
60+
" OR d.code LIKE '%DISC%'\n" +
61+
" OR u.id IN (\n" +
62+
" SELECT u2.id\n" +
63+
" FROM AppUser u2\n" +
64+
" JOIN u2.orders o2\n" +
65+
" JOIN o2.orderItems oi2\n" +
66+
" JOIN oi2.product p2\n" +
67+
" WHERE p2.price > (\n" +
68+
" SELECT AVG(p3.price) FROM Product p3\n" +
69+
" )\n" +
70+
" )\n" +
71+
" )\n" +
72+
" END\n" +
73+
" )\n" +
74+
" END\n" +
75+
")\n";
7876

7977

8078
@Test
8179
public void testParserMemoryUsage(SessionFactoryScope scope) {
8280
final HqlTranslator hqlTranslator = scope.getSessionFactory().getQueryEngine().getHqlTranslator();
83-
final Runtime runtime = Runtime.getRuntime();
8481

8582
// Ensure classes and basic stuff is initialized in case this is the first test run
8683
hqlTranslator.translate( "from AppUser", AppUser.class );
87-
runtime.gc();
88-
runtime.gc();
89-
90-
// Track memory usage before execution
91-
long totalMemoryBefore = runtime.totalMemory();
92-
long usedMemoryBefore = totalMemoryBefore - runtime.freeMemory();
93-
94-
System.out.println("Memory Usage Before Create Query:");
95-
System.out.println("----------------------------");
96-
System.out.println("Total Memory: " + (totalMemoryBefore / 1024) + " KB");
97-
System.out.println("Used Memory : " + (usedMemoryBefore / 1024) + " KB");
98-
System.out.println();
99-
100-
// Create query
101-
SqmStatement<Long> statement = hqlTranslator.translate( HQL, Long.class );
102-
103-
// Track memory usage after execution
104-
long totalMemoryAfter = runtime.totalMemory();
105-
long usedMemoryAfter = totalMemoryAfter - runtime.freeMemory();
106-
107-
System.out.println("Memory Usage After Create Query:");
108-
System.out.println("----------------------------");
109-
System.out.println("Total Memory: " + (totalMemoryAfter / 1024) + " KB");
110-
System.out.println("Used Memory : " + (usedMemoryAfter / 1024) + " KB");
111-
System.out.println();
112-
113-
System.out.println("Memory increase After Parsing:");
114-
System.out.println("----------------------------");
115-
System.out.println("Total Memory increase: " + ((totalMemoryAfter - totalMemoryBefore) / 1024) + " KB");
116-
System.out.println("Used Memory increase : " + ((usedMemoryAfter - usedMemoryBefore) / 1024) + " KB");
117-
System.out.println();
118-
119-
runtime.gc();
120-
runtime.gc();
121-
122-
// Track memory usage after execution
123-
long totalMemoryAfterGc = runtime.totalMemory();
124-
long usedMemoryAfterGc = totalMemoryAfterGc - runtime.freeMemory();
125-
126-
System.out.println("Memory Usage After Create Query and GC:");
127-
System.out.println("----------------------------");
128-
System.out.println("Total Memory: " + (totalMemoryAfterGc / 1024) + " KB");
129-
System.out.println("Used Memory : " + (usedMemoryAfterGc / 1024) + " KB");
130-
System.out.println();
131-
132-
System.out.println("Memory overhead of Parsing:");
133-
System.out.println("----------------------------");
134-
System.out.println("Total Memory increase: " + ((totalMemoryAfter - totalMemoryAfterGc) / 1024) + " KB");
135-
System.out.println("Used Memory increase : " + ((usedMemoryAfter - usedMemoryAfterGc) / 1024) + " KB");
136-
System.out.println();
13784

13885
// During testing, before the fix for HHH-19240, the allocation was around 500+ MB,
13986
// and after the fix it dropped to 170 - 250 MB
140-
final long memoryConsumption = usedMemoryAfter - usedMemoryAfterGc;
141-
assertTrue( usedMemoryAfter - usedMemoryAfterGc < 256_000_000, "Parsing of queries consumes too much memory (" + ( memoryConsumption / 1024 ) + " KB), when at most 256 MB are expected" );
87+
final long memoryUsage = MemoryUsageUtil.estimateMemoryUsage( () -> hqlTranslator.translate( HQL, Long.class ) );
88+
System.out.println( "Memory Consumption: " + (memoryUsage / 1024) + " KB" );
89+
assertTrue( memoryUsage < 256_000_000, "Parsing of queries consumes too much memory (" + ( memoryUsage / 1024 ) + " KB), when at most 256 MB are expected" );
14290
}
14391

14492
@Entity(name = "Address")
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.testing.memory;
6+
7+
import java.lang.management.ManagementFactory;
8+
import java.lang.management.MemoryPoolMXBean;
9+
import java.util.List;
10+
import java.util.Objects;
11+
12+
final class GlobalMemoryUsageSnapshotter implements MemoryAllocationSnapshotter {
13+
14+
private static final GlobalMemoryUsageSnapshotter INSTANCE = new GlobalMemoryUsageSnapshotter(
15+
ManagementFactory.getMemoryPoolMXBeans()
16+
);
17+
18+
private final List<MemoryPoolMXBean> heapPoolBeans;
19+
private final Runnable gcAndWait;
20+
21+
private GlobalMemoryUsageSnapshotter(List<MemoryPoolMXBean> heapPoolBeans) {
22+
this.heapPoolBeans = heapPoolBeans;
23+
this.gcAndWait = () -> {
24+
for (int i = 0; i < 3; i++) {
25+
System.gc();
26+
try { Thread.sleep(50); } catch (InterruptedException ignored) {}
27+
}
28+
};
29+
}
30+
31+
public static GlobalMemoryUsageSnapshotter getInstance() {
32+
return INSTANCE;
33+
}
34+
35+
@Override
36+
public MemoryAllocationSnapshot snapshot() {
37+
final long peakUsage = heapPoolBeans.stream().mapToLong(p -> p.getPeakUsage().getUsed()).sum();
38+
gcAndWait.run();
39+
final long retainedUsage = heapPoolBeans.stream().mapToLong(p -> p.getUsage().getUsed()).sum();
40+
heapPoolBeans.forEach(MemoryPoolMXBean::resetPeakUsage);
41+
return new GlobalMemoryAllocationSnapshot( peakUsage, retainedUsage );
42+
}
43+
44+
final static class GlobalMemoryAllocationSnapshot implements MemoryAllocationSnapshot {
45+
private final long peakUsage;
46+
private final long retainedUsage;
47+
48+
GlobalMemoryAllocationSnapshot(long peakUsage, long retainedUsage) {
49+
this.peakUsage = peakUsage;
50+
this.retainedUsage = retainedUsage;
51+
}
52+
53+
public long peakUsage() {
54+
return peakUsage;
55+
}
56+
57+
public long retainedUsage() {
58+
return retainedUsage;
59+
}
60+
61+
@Override
62+
public long difference(MemoryAllocationSnapshot before) {
63+
// When doing the "before" snapshot, the peak usage is reset.
64+
// Since this object is the "after" snapshot, we can simply estimate the memory usage of an operation
65+
// to be the peak usage of that operation minus the usage after GC
66+
return peakUsage - retainedUsage;
67+
}
68+
69+
@Override
70+
public boolean equals(Object obj) {
71+
if ( obj == this ) {
72+
return true;
73+
}
74+
if ( obj == null || obj.getClass() != this.getClass() ) {
75+
return false;
76+
}
77+
var that = (GlobalMemoryAllocationSnapshot) obj;
78+
return this.peakUsage == that.peakUsage &&
79+
this.retainedUsage == that.retainedUsage;
80+
}
81+
82+
@Override
83+
public int hashCode() {
84+
return Objects.hash( peakUsage, retainedUsage );
85+
}
86+
87+
@Override
88+
public String toString() {
89+
return "GlobalMemoryAllocationSnapshot[" +
90+
"peakUsage=" + peakUsage + ", " +
91+
"retainedUsage=" + retainedUsage + ']';
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)