1
1
/*
2
- * Copyright (c) 2020, 2023 , Oracle and/or its affiliates. All rights reserved.
2
+ * Copyright (c) 2020, 2024 , Oracle and/or its affiliates. All rights reserved.
3
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
4
*
5
5
* The Universal Permissive License (UPL), Version 1.0
41
41
package com .oracle .graal .python .test .advanced ;
42
42
43
43
import java .io .IOException ;
44
+ import java .io .OutputStream ;
44
45
import java .lang .management .ManagementFactory ;
45
46
import java .lang .management .ThreadInfo ;
46
47
import java .lang .management .ThreadMXBean ;
@@ -98,10 +99,14 @@ public static void main(String[] args) {
98
99
99
100
private boolean sharedEngine = false ;
100
101
private boolean keepDump = false ;
102
+ private int repeatAndCheckSize = -1 ;
103
+ private boolean nullStdout = false ;
101
104
private String languageId ;
102
105
private String code ;
103
106
private List <String > forbiddenClasses = new ArrayList <>();
104
107
108
+ private static final int REPEAT_AND_CHECK_BASLINE_ITERATION = 32 ;
109
+
105
110
private final class SystemExit extends RuntimeException {
106
111
private static final long serialVersionUID = 1L ;
107
112
@@ -123,7 +128,7 @@ private void dumpAndAnalyze() {
123
128
124
129
MBeanServer server = doFullGC ();
125
130
String threadDump = getThreadDump ();
126
- Path dumpFile = dumpHeap (server );
131
+ Path dumpFile = dumpHeap (server , keepDump );
127
132
boolean fail = checkForLeaks (dumpFile );
128
133
if (fail ) {
129
134
System .err .print (threadDump );
@@ -181,50 +186,6 @@ private boolean checkForLeaks(Path dumpFile) {
181
186
return fail ;
182
187
}
183
188
184
- private Path dumpHeap (MBeanServer server ) {
185
- Path dumpFile = null ;
186
- try {
187
- Path p = Files .createTempDirectory ("leakTest" );
188
- if (!keepDump ) {
189
- p .toFile ().deleteOnExit ();
190
- }
191
- dumpFile = p .resolve ("heapdump.hprof" );
192
- if (!keepDump ) {
193
- dumpFile .toFile ().deleteOnExit ();
194
- } else {
195
- System .out .println ("Dump file: " + dumpFile .toString ());
196
- }
197
- HotSpotDiagnosticMXBean mxBean = ManagementFactory .newPlatformMXBeanProxy (server ,
198
- "com.sun.management:type=HotSpotDiagnostic" , HotSpotDiagnosticMXBean .class );
199
- mxBean .dumpHeap (dumpFile .toString (), true );
200
- } catch (IOException e ) {
201
- throw new RuntimeException (e );
202
- }
203
- return dumpFile ;
204
- }
205
-
206
- private MBeanServer doFullGC () {
207
- // do this a few times to dump a small heap if we can
208
- MBeanServer server = null ;
209
- for (int i = 0 ; i < 10 ; i ++) {
210
- System .gc ();
211
- Runtime .getRuntime ().freeMemory ();
212
- server = ManagementFactory .getPlatformMBeanServer ();
213
- try {
214
- ObjectName objectName = new ObjectName ("com.sun.management:type=DiagnosticCommand" );
215
- server .invoke (objectName , "gcRun" , new Object []{null }, new String []{String [].class .getName ()});
216
- } catch (MalformedObjectNameException | InstanceNotFoundException | ReflectionException | MBeanException e ) {
217
- throw new RuntimeException (e );
218
- }
219
- try {
220
- Thread .sleep (2000 );
221
- } catch (InterruptedException e1 ) {
222
- // do nothing
223
- }
224
- }
225
- return server ;
226
- }
227
-
228
189
private int getCntAndErrors (JavaClass cls , List <String > errors ) {
229
190
int cnt = cls .getInstancesCount ();
230
191
if (cnt > 0 ) {
@@ -253,6 +214,58 @@ public final Throwable fillInStackTrace() {
253
214
}
254
215
}
255
216
217
+ private static MBeanServer doFullGC () {
218
+ // do this a few times to dump a small heap if we can
219
+ MBeanServer server = null ;
220
+ for (int i = 0 ; i < 10 ; i ++) {
221
+ System .gc ();
222
+ Runtime .getRuntime ().freeMemory ();
223
+ server = ManagementFactory .getPlatformMBeanServer ();
224
+ try {
225
+ ObjectName objectName = new ObjectName ("com.sun.management:type=DiagnosticCommand" );
226
+ server .invoke (objectName , "gcRun" , new Object []{null }, new String []{String [].class .getName ()});
227
+ } catch (MalformedObjectNameException | InstanceNotFoundException | ReflectionException | MBeanException e ) {
228
+ throw new RuntimeException (e );
229
+ }
230
+ try {
231
+ Thread .sleep (2000 );
232
+ } catch (InterruptedException e1 ) {
233
+ // do nothing
234
+ }
235
+ }
236
+ return server ;
237
+ }
238
+
239
+ private static Path dumpHeap (MBeanServer server , boolean keepDump ) {
240
+ Path dumpFile = null ;
241
+ try {
242
+ Path p = Files .createTempDirectory ("leakTest" );
243
+ if (!keepDump ) {
244
+ p .toFile ().deleteOnExit ();
245
+ }
246
+ dumpFile = p .resolve ("heapdump.hprof" );
247
+ if (!keepDump ) {
248
+ dumpFile .toFile ().deleteOnExit ();
249
+ } else {
250
+ System .out .println ("Dump file: " + dumpFile .toString ());
251
+ }
252
+ HotSpotDiagnosticMXBean mxBean = ManagementFactory .newPlatformMXBeanProxy (server ,
253
+ "com.sun.management:type=HotSpotDiagnostic" , HotSpotDiagnosticMXBean .class );
254
+ mxBean .dumpHeap (dumpFile .toString (), true );
255
+ } catch (IOException e ) {
256
+ throw new RuntimeException (e );
257
+ }
258
+ return dumpFile ;
259
+ }
260
+
261
+ private static long getJavaHeapSize (boolean createHeapDump ) {
262
+ MBeanServer server = doFullGC ();
263
+ if (createHeapDump ) {
264
+ dumpHeap (server , true );
265
+ }
266
+ return Runtime .getRuntime ().totalMemory () - Runtime .getRuntime ().freeMemory ();
267
+ }
268
+
256
269
@ Override
257
270
protected List <String > preprocessArguments (List <String > arguments , Map <String , String > polyglotOptions ) {
258
271
ArrayList <String > unrecognized = new ArrayList <>();
@@ -269,6 +282,14 @@ protected List<String> preprocessArguments(List<String> arguments, Map<String, S
269
282
code = arguments .get (++i );
270
283
} else if (arg .equals ("--forbidden-class" )) {
271
284
forbiddenClasses .add (arguments .get (++i ));
285
+ } else if (arg .equals ("--repeat-and-check-size" )) {
286
+ repeatAndCheckSize = Integer .parseInt (arguments .get (++i ));
287
+ if (repeatAndCheckSize <= REPEAT_AND_CHECK_BASLINE_ITERATION ) {
288
+ System .err .printf ("--repeat-and-check-size must be at least %d\n " , REPEAT_AND_CHECK_BASLINE_ITERATION );
289
+ System .exit (1 );
290
+ }
291
+ } else if (arg .equals ("--null-stdout" )) {
292
+ nullStdout = true ;
272
293
} else {
273
294
unrecognized .add (arg );
274
295
}
@@ -284,26 +305,59 @@ protected void launch(Builder contextBuilder) {
284
305
contextBuilder .engine (engine );
285
306
}
286
307
contextBuilder .allowExperimentalOptions (true ).allowAllAccess (true );
308
+ if (nullStdout ) {
309
+ contextBuilder .out (OutputStream .nullOutputStream ());
310
+ }
287
311
288
312
try (Context c = contextBuilder .build ()) {
289
313
try {
290
314
c .eval (getLanguageId (), code );
291
315
} catch (PolyglotException e ) {
292
- if (e .isExit ()) {
293
- if (e .getExitStatus () == 0 ) {
294
- throw new SystemExit ();
295
- } else {
296
- exit (e .getExitStatus ());
316
+ handleException (e );
317
+ }
318
+ }
319
+
320
+ if (repeatAndCheckSize > 0 ) {
321
+ long initialSize = -1 ;
322
+ for (int i = 0 ; i < repeatAndCheckSize ; i ++) {
323
+ if (i == REPEAT_AND_CHECK_BASLINE_ITERATION ) {
324
+ // Give the system some time to stabilize, fill caches, etc.
325
+ initialSize = getJavaHeapSize (keepDump );
326
+ System .out .printf ("Baseline heap size: %,d\n " , initialSize );
327
+ }
328
+ try (Context c = contextBuilder .build ()) {
329
+ try {
330
+ c .eval (getLanguageId (), code );
331
+ } catch (PolyglotException e ) {
332
+ handleException (e );
297
333
}
298
- } else {
299
- e .printStackTrace ();
300
- exit (255 );
301
334
}
302
335
}
336
+ // the check at the end will make a dump anyway, so no createHeapDump flag here
337
+ long currentSize = getJavaHeapSize (false );
338
+ System .out .printf ("Heap size after all repetitions: %,d\n " , currentSize );
339
+ if (currentSize > initialSize * 1.1 ) {
340
+ System .err .printf ("Heap size grew too much after repeated context creations and invocations. From %,d bytes to %,d bytes.\n " , initialSize , currentSize );
341
+ System .exit (255 );
342
+ }
303
343
}
344
+
304
345
throw new SystemExit ();
305
346
}
306
347
348
+ private void handleException (PolyglotException e ) {
349
+ if (e .isExit ()) {
350
+ if (e .getExitStatus () == 0 ) {
351
+ throw new SystemExit ();
352
+ } else {
353
+ exit (e .getExitStatus ());
354
+ }
355
+ } else {
356
+ e .printStackTrace ();
357
+ exit (255 );
358
+ }
359
+ }
360
+
307
361
@ Override
308
362
protected String getLanguageId () {
309
363
return languageId ;
0 commit comments