1
+ /*
2
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
+ *
5
+ * This code is free software; you can redistribute it and/or modify it
6
+ * under the terms of the GNU General Public License version 2 only, as
7
+ * published by the Free Software Foundation.
8
+ *
9
+ * This code is distributed in the hope that it will be useful, but WITHOUT
10
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12
+ * version 2 for more details (a copy is included in the LICENSE file that
13
+ * accompanied this code).
14
+ *
15
+ * You should have received a copy of the GNU General Public License version
16
+ * 2 along with this work; if not, write to the Free Software Foundation,
17
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
+ *
19
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20
+ * or visit www.oracle.com if you need additional information or have any
21
+ * questions.
22
+ */
23
+
24
+ /*
25
+ * @test id=no-options
26
+ * @summary Run test with no arguments apart from the ones required by
27
+ * the test.
28
+ * @requires os.family == "linux"
29
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log TestTracePageSizes
30
+ */
31
+
32
+ /*
33
+ * @test id=explicit-large-page-size
34
+ * @summary Run test explicitly with both 2m and 1g pages on x64. Excluding ZGC since
35
+ * it fail initialization if no large pages are available on the system.
36
+ * @requires os.family == "linux"
37
+ * @requires os.arch=="amd64" | os.arch=="x86_64"
38
+ * @requires vm.gc != "Z"
39
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseLargePages -XX:LargePageSizeInBytes=2m TestTracePageSizes
40
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseLargePages -XX:LargePageSizeInBytes=1g TestTracePageSizes
41
+ */
42
+
43
+ /*
44
+ * @test id=compiler-options
45
+ * @summary Run test without segmented code cache. Excluding ZGC since it
46
+ * fail initialization if no large pages are available on the system.
47
+ * @requires os.family == "linux"
48
+ * @requires vm.gc != "Z"
49
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:-SegmentedCodeCache TestTracePageSizes
50
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:-SegmentedCodeCache -XX:+UseLargePages TestTracePageSizes
51
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:-SegmentedCodeCache -XX:+UseTransparentHugePages TestTracePageSizes
52
+ */
53
+
54
+ /*
55
+ * @test id=with-G1
56
+ * @summary Run tests with G1
57
+ * @requires os.family == "linux"
58
+ * @requires vm.gc.G1
59
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseG1GC TestTracePageSizes
60
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseG1GC -XX:+UseLargePages TestTracePageSizes
61
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseG1GC -XX:+UseTransparentHugePages TestTracePageSizes
62
+ */
63
+
64
+ /*
65
+ * @test id=with-Parallel
66
+ * @summary Run tests with Parallel
67
+ * @requires os.family == "linux"
68
+ * @requires vm.gc.Parallel
69
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseParallelGC TestTracePageSizes
70
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseParallelGC -XX:+UseLargePages TestTracePageSizes
71
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseParallelGC -XX:+UseTransparentHugePages TestTracePageSizes
72
+ */
73
+
74
+ /*
75
+ * @test id=with-Serial
76
+ * @summary Run tests with Serial
77
+ * @requires os.family == "linux"
78
+ * @requires vm.gc.Serial
79
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseSerialGC TestTracePageSizes
80
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseSerialGC -XX:+UseLargePages TestTracePageSizes
81
+ * @run main/othervm -XX:+AlwaysPreTouch -Xlog:pagesize:ps-%p.log -XX:+UseSerialGC -XX:+UseTransparentHugePages TestTracePageSizes
82
+ */
83
+
84
+ import java .io .File ;
85
+ import java .util .LinkedList ;
86
+ import java .util .Scanner ;
87
+ import java .util .regex .Matcher ;
88
+ import java .util .regex .Pattern ;
89
+
90
+ // Check that page sizes logged match what is recorded in /proc/self/smaps.
91
+ // For transparent huge pages the matching is best effort since we can't
92
+ // know for sure what the underlying page size is.
93
+ public class TestTracePageSizes {
94
+ // Store address ranges with known page size.
95
+ private static LinkedList <RangeWithPageSize > ranges = new LinkedList <>();
96
+ private static boolean debug ;
97
+
98
+ // Parse /proc/self/smaps using a regexp capturing the address
99
+ // ranges, what page size they have and if they might use
100
+ // transparent huge pages. The pattern is not greedy and will
101
+ // match as little as possible so each "segment" in the file
102
+ // will generate a match.
103
+ private static void parseSmaps () throws Exception {
104
+ String smapsPatternString = "(\\ w+)-(\\ w+).*?" +
105
+ "KernelPageSize:\\ s*(\\ d*) kB.*?" +
106
+ "VmFlags: ([\\ w ]*)" ;
107
+ Pattern smapsPattern = Pattern .compile (smapsPatternString , Pattern .DOTALL );
108
+ Scanner smapsScanner = new Scanner (new File ("/proc/self/smaps" ));
109
+ // Find all memory segments in the smaps-file.
110
+ smapsScanner .findAll (smapsPattern ).forEach (mr -> {
111
+ String start = mr .group (1 );
112
+ String end = mr .group (2 );
113
+ String ps = mr .group (3 );
114
+ String vmFlags = mr .group (4 );
115
+
116
+ // Create a range given the match and add it to the list.
117
+ RangeWithPageSize range = new RangeWithPageSize (start , end , ps , vmFlags );
118
+ ranges .add (range );
119
+ debug ("Added range: " + range );
120
+ });
121
+ smapsScanner .close ();
122
+ }
123
+
124
+ // Search for a range including the given address.
125
+ private static RangeWithPageSize getRange (String addr ) {
126
+ long laddr = Long .decode (addr );
127
+ for (RangeWithPageSize range : ranges ) {
128
+ if (range .includes (laddr )) {
129
+ return range ;
130
+ }
131
+ }
132
+ return null ;
133
+ }
134
+
135
+ // Helper to get the page size in KB given a page size parsed
136
+ // from log_info(pagesize) output.
137
+ private static long pageSizeInKB (String pageSize ) {
138
+ String value = pageSize .substring (0 , pageSize .length ()-1 );
139
+ String unit = pageSize .substring (pageSize .length ()-1 );
140
+ long ret = Long .parseLong (value );
141
+ if (unit .equals ("K" )) {
142
+ return ret ;
143
+ } else if (unit .equals ("M" )) {
144
+ return ret * 1024 ;
145
+ } else if (unit .equals ("G" )) {
146
+ return ret * 1024 * 1024 ;
147
+ }
148
+ return 0 ;
149
+ }
150
+
151
+ // The test needs to be run with:
152
+ // * -Xlog:pagesize:ps-%p.log - To generate the log file parsed
153
+ // by the test itself.
154
+ // * -XX:+AlwaysPreTouch - To make sure mapped memory is touched
155
+ // so the relevant information is recorded in the smaps-file.
156
+ public static void main (String args []) throws Exception {
157
+ // Check if debug printing is enabled.
158
+ if (args .length > 0 && args [0 ].equals ("-debug" )) {
159
+ debug = true ;
160
+ } else {
161
+ debug = false ;
162
+ }
163
+
164
+ // Parse /proc/self/smaps to compare with values logged in the VM.
165
+ parseSmaps ();
166
+
167
+ // Setup patters for the JVM page size logging.
168
+ String traceLinePatternString = ".*base=(0x[0-9A-Fa-f]*).*page_size=([^ ]+).*" ;
169
+ Pattern traceLinePattern = Pattern .compile (traceLinePatternString );
170
+
171
+ // The test needs to be run with page size logging printed to ps-$pid.log.
172
+ Scanner fileScanner = new Scanner (new File ("./ps-" + ProcessHandle .current ().pid () + ".log" ));
173
+ while (fileScanner .hasNextLine ()) {
174
+ String line = fileScanner .nextLine ();
175
+ if (line .matches (traceLinePatternString )) {
176
+ Matcher trace = traceLinePattern .matcher (line );
177
+ trace .find ();
178
+
179
+ String address = trace .group (1 );
180
+ String pageSize = trace .group (2 );
181
+
182
+ RangeWithPageSize range = getRange (address );
183
+ if (range == null ) {
184
+ debug ("Could not find range for: " + line );
185
+ throw new AssertionError ("No memory range found for address: " + address );
186
+ }
187
+
188
+ long pageSizeFromSmaps = range .getPageSize ();
189
+ long pageSizeFromTrace = pageSizeInKB (pageSize );
190
+
191
+ debug ("From logfile: " + line );
192
+ debug ("From smaps: " + range );
193
+
194
+ if (pageSizeFromSmaps != pageSizeFromTrace ) {
195
+ if (pageSizeFromTrace > pageSizeFromSmaps && range .isTransparentHuge ()) {
196
+ // Page sizes mismatch because we can't know what underlying page size will
197
+ // be used when THP is enabled. So this is not a failure.
198
+ debug ("Success: " + pageSizeFromTrace + " > " + pageSizeFromSmaps + " and THP enabled" );
199
+ } else {
200
+ debug ("Failure: " + pageSizeFromSmaps + " != " + pageSizeFromTrace );
201
+ throw new AssertionError ("Page sizes mismatch: " + pageSizeFromSmaps + " != " + pageSizeFromTrace );
202
+ }
203
+ } else {
204
+ debug ("Success: " + pageSizeFromSmaps + " == " + pageSizeFromTrace );
205
+ }
206
+ }
207
+ debug ("---" );
208
+ }
209
+ fileScanner .close ();
210
+ }
211
+
212
+ private static void debug (String str ) {
213
+ if (debug ) {
214
+ System .out .println (str );
215
+ }
216
+ }
217
+ }
218
+
219
+ // Class used to store information about memory ranges parsed
220
+ // from /proc/self/smaps. The file contain a lot of information
221
+ // about the different mappings done by an application, but the
222
+ // lines we care about are:
223
+ // 700000000-73ea00000 rw-p 00000000 00:00 0
224
+ // ...
225
+ // KernelPageSize: 4 kB
226
+ // ...
227
+ // VmFlags: rd wr mr mw me ac sd
228
+ //
229
+ // We use the VmFlags to know what kind of huge pages are used.
230
+ // For transparent huge pages the KernelPageSize field will not
231
+ // report the large page size.
232
+ class RangeWithPageSize {
233
+ private long start ;
234
+ private long end ;
235
+ private long pageSize ;
236
+ private boolean vmFlagHG ;
237
+ private boolean vmFlagHT ;
238
+
239
+ public RangeWithPageSize (String start , String end , String pageSize , String vmFlags ) {
240
+ this .start = Long .parseUnsignedLong (start , 16 );
241
+ this .end = Long .parseUnsignedLong (end , 16 );
242
+ this .pageSize = Long .parseLong (pageSize );
243
+
244
+ vmFlagHG = false ;
245
+ vmFlagHT = false ;
246
+ // Check if the vmFlags line include:
247
+ // * ht - Meaning the range is mapped using explicit huge pages.
248
+ // * hg - Meaning the range is madvised huge.
249
+ for (String flag : vmFlags .split (" " )) {
250
+ if (flag .equals ("ht" )) {
251
+ vmFlagHT = true ;
252
+ } else if (flag .equals ("hg" )) {
253
+ vmFlagHG = true ;
254
+ }
255
+ }
256
+ }
257
+
258
+ public long getPageSize () {
259
+ return pageSize ;
260
+ }
261
+
262
+ public boolean isTransparentHuge () {
263
+ return vmFlagHG ;
264
+ }
265
+
266
+ public boolean isExplicitHuge () {
267
+ return vmFlagHT ;
268
+ }
269
+
270
+ public boolean includes (long addr ) {
271
+ return start <= addr && addr < end ;
272
+ }
273
+
274
+ public String toString () {
275
+ return "[" + Long .toHexString (start ) + ", " + Long .toHexString (end ) + ") " +
276
+ "pageSize=" + pageSize + "KB isTHP=" + vmFlagHG + " isHUGETLB=" + vmFlagHT ;
277
+ }
278
+ }
0 commit comments