File: TestTrimNative.java

package info (click to toggle)
openjdk-17 17.0.17%2B10-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 764,928 kB
  • sloc: java: 5,319,061; xml: 1,291,711; cpp: 1,202,358; ansic: 428,746; asm: 404,978; objc: 20,861; sh: 14,754; javascript: 10,743; python: 6,402; makefile: 2,404; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (348 lines) | stat: -rw-r--r-- 15,223 bytes parent folder | download | duplicates (10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/*
 * Copyright (c) 2023 SAP SE. All rights reserved.
 * Copyright (c) 2023, 2024, Red Hat, Inc. All rights reserved.
 * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

/*
 * @test id=trimNative
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative trimNative
 */

/*
 * @test id=trimNativeStrict
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run main/manual TestTrimNative trimNativeStrict
 */

/*
 * @test id=trimNativeHighInterval
 * @summary High interval trimming should not even kick in for short program runtimes
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative trimNativeHighInterval
 */

/*
 * @test id=trimNativeLowInterval
 * @summary Very low (sub-second) interval, nothing should explode
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative trimNativeLowInterval
 */

/*
 * @test id=trimNativeLowIntervalStrict
 * @summary Very low (sub-second) interval, nothing should explode (stricter test, manual mode)
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run main/manual TestTrimNative trimNativeLowIntervalStrict
 */

/*
 * @test id=testOffByDefault
 * @summary Test that trimming is disabled by default
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative testOffByDefault
 */

/*
 * @test id=testOffExplicit
 * @summary Test that trimming can be disabled explicitly
 * @requires vm.flagless
 * @requires (os.family=="linux") & !vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative testOffExplicit
 */

/*
 * @test id=testOffOnNonCompliantPlatforms
 * @summary Test that trimming is correctly reported as unavailable if unavailable
 * @requires vm.flagless
 * @requires (os.family!="linux") | vm.musl
 * @modules java.base/jdk.internal.misc
 * @library /test/lib
 * @build jdk.test.whitebox.WhiteBox
 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 * @run driver TestTrimNative testOffOnNonCompliantPlatforms
 */

import jdk.test.lib.Platform;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;

import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jdk.test.whitebox.WhiteBox;

public class TestTrimNative {

    // Actual RSS increase is a lot larger than 4 MB. Depends on glibc overhead, and NMT malloc headers in debug VMs.
    // We need small-grained allocations to make sure they actually increase RSS (all touched) and to see the
    // glibc-retaining-memory effect.
    static final int szAllocations = 128;
    static final int totalAllocationsSize = 128 * 1024 * 1024; // 128 MB total
    static final int numAllocations = totalAllocationsSize / szAllocations;

    static long[] ptrs = new long[numAllocations];

    enum Unit {
        B(1), K(1024), M(1024*1024), G(1024*1024*1024);
        public final long size;
        Unit(long size) { this.size = size; }
    }

    private static String[] prepareOptions(String[] extraVMOptions, String[] programOptions) {
        List<String> allOptions = new ArrayList<String>();
        if (extraVMOptions != null) {
            allOptions.addAll(Arrays.asList(extraVMOptions));
        }
        allOptions.add("-Xmx128m");
        allOptions.add("-Xms128m"); // Stabilize RSS
        allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS
        allOptions.add("-XX:+UnlockDiagnosticVMOptions"); // For whitebox
        allOptions.add("-XX:+WhiteBoxAPI");
        allOptions.add("-Xbootclasspath/a:.");
        allOptions.add("-XX:-ExplicitGCInvokesConcurrent"); // Invoke explicit GC on System.gc
        allOptions.add("-Xlog:trimnative=debug");
        allOptions.add("--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
        if (programOptions != null) {
            allOptions.addAll(Arrays.asList(programOptions));
        }
        return allOptions.toArray(new String[0]);
    }

    private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] programOptions) throws IOException {
        ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(prepareOptions(extraOptions, programOptions));
        OutputAnalyzer output = new OutputAnalyzer(pb.start());
        output.shouldHaveExitValue(0);
        return output;
    }

    private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled,
                                                 int expectedInterval) {
        if (expectEnabled) {
            output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " ms");
            output.shouldContain("Native heap trimmer start");
        } else {
            output.shouldNotContain("Periodic native trim enabled");
        }
    }

    /**
     * Given JVM output, look for one or more log lines that describes a successful negative trim. The total amount
     * of trims should be matching about what the test program allocated.
     * @param output
     * @param minTrimsExpected min number of periodic trim lines expected in UL log
     * @param maxTrimsExpected min number of periodic trim lines expected in UL log
     * @param strict: if true, expect RSS to go down; if false, just look for trims without looking at RSS.
     */
    private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minTrimsExpected,
                                                          int maxTrimsExpected, boolean strict) {
        output.reportDiagnosticSummary();
        List<String> lines = output.asLines();
        Pattern pat = Pattern.compile(".*\\[trimnative\\] Periodic Trim \\(\\d+\\): (\\d+)([BKMG])->(\\d+)([BKMG]).*");
        int numTrimsFound = 0;
        long rssReductionTotal = 0;
        for (String line : lines) {
            Matcher mat = pat.matcher(line);
            if (mat.matches()) {
                long rss1 = Long.parseLong(mat.group(1)) * Unit.valueOf(mat.group(2)).size;
                long rss2 = Long.parseLong(mat.group(3)) * Unit.valueOf(mat.group(4)).size;
                if (rss1 > rss2) {
                    rssReductionTotal += (rss1 - rss2);
                }
                numTrimsFound ++;
            }
            if (numTrimsFound > maxTrimsExpected) {
                throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxTrimsExpected +
                        "). Does the interval setting not work?");
            }
        }
        if (numTrimsFound < minTrimsExpected) {
            throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minTrimsExpected +
                    ", found " + numTrimsFound + ").");
        }
        System.out.println("Found " + numTrimsFound + " trims. Ok.");
        if (strict && maxTrimsExpected > 0) {
            // This is very fuzzy. Test program malloced X bytes, then freed them again and trimmed. But the log line prints change in RSS.
            // Which, of course, is influenced by a lot of other factors. But we expect to see *some* reasonable reduction in RSS
            // due to trimming.
            float fudge = 0.5f;
            // On ppc, we see a vastly diminished return (~3M reduction instead of ~200), I suspect because of the underlying
            // 64k pages lead to a different geometry. Manual tests with larger reclaim sizes show that autotrim works. For
            // this test, we just reduce the fudge factor.
            if (Platform.isPPC()) { // le and be both
                fudge = 0.01f;
            }
            long expectedMinimalReduction = (long) (totalAllocationsSize * fudge);
            if (rssReductionTotal < expectedMinimalReduction) {
                throw new RuntimeException("We did not see the expected RSS reduction in the UL log. Expected (with fudge)" +
                        " to see at least a combined reduction of " + expectedMinimalReduction + ".");
            } else {
                System.out.println("Found high enough RSS reduction from trims: " + rssReductionTotal);
            }
        }
    }

    static class Tester {
        public static void main(String[] args) throws Exception {
            long sleeptime = Long.parseLong(args[0]);

            System.out.println("Will spike now...");
            WhiteBox wb = WhiteBox.getWhiteBox();
            for (int i = 0; i < numAllocations; i++) {
                ptrs[i] = wb.NMTMalloc(szAllocations);
                wb.preTouchMemory(ptrs[i], szAllocations);
            }
            for (int i = 0; i < numAllocations; i++) {
                wb.NMTFree(ptrs[i]);
            }
            System.out.println("Done spiking.");

            System.out.println("GC...");
            System.gc();

            // give GC time to react
            System.out.println("Sleeping for " + sleeptime + " ms...");
            Thread.sleep(sleeptime);
            System.out.println("Done.");
        }
    }

    public static void main(String[] args) throws Exception {

        if (args.length == 0) {
            throw new RuntimeException("Argument error");
        }

        boolean strictTesting = args[0].endsWith("Strict");

        switch (args[0]) {
            case "trimNative":
            case "trimNativeStrict": {
                long trimInterval = 500; // twice per second
                long ms1 = System.currentTimeMillis();
                OutputAnalyzer output = runTestWithOptions(
                        new String[] { "-XX:TrimNativeHeapInterval=" + trimInterval },
                        new String[] { TestTrimNative.Tester.class.getName(), "5000" }
                );
                long ms2 = System.currentTimeMillis();
                long runtime_ms = ms2 - ms1;

                checkExpectedLogMessages(output, true, 500);

                long maxTrimsExpected = runtime_ms / trimInterval;
                long minTrimsExpected = maxTrimsExpected / 2;
                parseOutputAndLookForNegativeTrim(output, (int) minTrimsExpected, (int) maxTrimsExpected, strictTesting);
            } break;

            case "trimNativeHighInterval": {
                OutputAnalyzer output = runTestWithOptions(
                        new String[] { "-XX:TrimNativeHeapInterval=" + Integer.MAX_VALUE },
                        new String[] { TestTrimNative.Tester.class.getName(), "5000" }
                );
                checkExpectedLogMessages(output, true, Integer.MAX_VALUE);
                // We should not see any trims since the interval would prevent them
                parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
            } break;

            case "trimNativeLowInterval":
            case "trimNativeLowIntervalStrict": {
                long ms1 = System.currentTimeMillis();
                OutputAnalyzer output = runTestWithOptions(
                        new String[] { "-XX:TrimNativeHeapInterval=1" },
                        new String[] { TestTrimNative.Tester.class.getName(), "0" }
                );
                long ms2 = System.currentTimeMillis();
                int maxTrimsExpected = (int)(ms2 - ms1); // 1ms trim interval
                checkExpectedLogMessages(output, true, 1);
                parseOutputAndLookForNegativeTrim(output, 1, (int)maxTrimsExpected, strictTesting);
            } break;

            case "testOffOnNonCompliantPlatforms": {
                OutputAnalyzer output = runTestWithOptions(
                        new String[] { "-XX:TrimNativeHeapInterval=1" },
                        new String[] { "-version" }
                );
                checkExpectedLogMessages(output, false, 0);
                parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
                // The following output is expected to be printed with warning level, so it should not need -Xlog
                output.shouldContain("[warning][trimnative] Native heap trim is not supported on this platform");
            } break;

            case "testOffExplicit": {
                OutputAnalyzer output = runTestWithOptions(
                        new String[] { "-XX:TrimNativeHeapInterval=0" },
                        new String[] { "-version" }
                );
                checkExpectedLogMessages(output, false, 0);
                parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
            } break;

            case "testOffByDefault": {
                OutputAnalyzer output = runTestWithOptions(null, new String[] { "-version" } );
                checkExpectedLogMessages(output, false, 0);
                parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
            } break;

            default:
                throw new RuntimeException("Invalid test " + args[0]);

        }
    }
}