28
28
import java .nio .file .Files ;
29
29
import java .nio .file .Path ;
30
30
import java .nio .file .Paths ;
31
+ import java .util .HashMap ;
32
+ import java .util .LinkedHashSet ;
31
33
import java .util .List ;
34
+ import java .util .Map ;
35
+ import java .util .Set ;
32
36
33
37
import org .graalvm .nativeimage .ImageSingletons ;
34
38
import org .graalvm .nativeimage .hosted .Feature ;
37
41
import com .oracle .svm .configure .NamedConfigurationTypeDescriptor ;
38
42
import com .oracle .svm .configure .ProxyConfigurationTypeDescriptor ;
39
43
import com .oracle .svm .configure .UnresolvedConfigurationCondition ;
44
+ import com .oracle .svm .configure .config .ConfigurationFileCollection ;
40
45
import com .oracle .svm .configure .config .ConfigurationSet ;
41
46
import com .oracle .svm .configure .config .ConfigurationType ;
47
+ import com .oracle .svm .core .SubstrateUtil ;
42
48
import com .oracle .svm .core .feature .AutomaticallyRegisteredFeature ;
43
49
import com .oracle .svm .core .feature .InternalFeature ;
44
50
import com .oracle .svm .core .jdk .RuntimeSupport ;
51
57
52
58
import jdk .graal .compiler .api .replacements .Fold ;
53
59
import jdk .graal .compiler .options .Option ;
60
+ import jdk .graal .compiler .options .OptionStability ;
54
61
55
62
/**
56
63
* Implements reachability metadata tracing during native image execution. Enabling
57
64
* {@link Options#MetadataTracingSupport} at build time will generate code to trace all accesses of
58
- * reachability metadata. When {@link Options#RecordMetadata} is specified at run time, the image
59
- * will trace and emit metadata to the specified path .
65
+ * reachability metadata, and then the run-time option {@link Options#RecordMetadata} enables
66
+ * tracing .
60
67
*/
61
68
public final class MetadataTracer {
62
69
63
70
public static class Options {
64
- @ Option (help = "Enables the run-time code to trace reachability metadata accesses in the produced native image by using -XX:RecordMetadata=<path>." )//
71
+ @ Option (help = "Generate an image that supports reachability metadata access tracing. " +
72
+ "When tracing is supported, use the -XX:RecordMetadata option to enable tracing at run time." )//
65
73
public static final HostedOptionKey <Boolean > MetadataTracingSupport = new HostedOptionKey <>(false );
66
74
67
- @ Option (help = "The path of the directory to write traced metadata to. Metadata tracing is enabled only when this option is provided." )//
68
- public static final RuntimeOptionKey <String > RecordMetadata = new RuntimeOptionKey <>("" );
75
+ static final String RECORD_METADATA_HELP = """
76
+ Enables metadata tracing at run time. This option is only supported if -H:+MetadataTracingSupport is set when building the image.
77
+ The value of this option is a comma-separated list of arguments specified as key-value pairs. The following arguments are supported:
78
+
79
+ - path=<trace-output-directory> (required): Specifies the directory to write traced metadata to.
80
+ - merge=<boolean> (optional): Specifies whether to merge or overwrite metadata with existing files at the output path (default: true).
81
+
82
+ Example usage:
83
+ -H:RecordMetadata=path=trace_output_directory
84
+ -H:RecordMetadata=path=trace_output_directory,merge=false
85
+ """ ;
86
+
87
+ @ Option (help = RECORD_METADATA_HELP , stability = OptionStability .EXPERIMENTAL )//
88
+ public static final RuntimeOptionKey <String > RecordMetadata = new RuntimeOptionKey <>(null );
69
89
}
70
90
91
+ private RecordOptions options ;
71
92
private volatile ConfigurationSet config ;
72
93
73
- private Path recordMetadataPath ;
74
-
75
94
@ Fold
76
95
public static MetadataTracer singleton () {
77
96
return ImageSingletons .lookup (MetadataTracer .class );
78
97
}
79
98
80
99
/**
81
- * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata=path }).
100
+ * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata}).
82
101
*/
83
102
public boolean enabled () {
84
103
VMError .guarantee (Options .MetadataTracingSupport .getValue ());
85
- return recordMetadataPath != null ;
104
+ return options != null ;
86
105
}
87
106
88
107
/**
@@ -159,21 +178,35 @@ public void traceSerializationType(String className) {
159
178
}
160
179
}
161
180
162
- private static void initialize () {
181
+ private static void initialize (String recordMetadataValue ) {
163
182
assert Options .MetadataTracingSupport .getValue ();
164
- MetadataTracer singleton = MetadataTracer .singleton ();
165
- String recordMetadataValue = Options .RecordMetadata .getValue ();
166
- if (recordMetadataValue .isEmpty ()) {
167
- throw new IllegalArgumentException ("Empty path provided for " + Options .RecordMetadata .getName () + "." );
168
- }
169
- Path recordMetadataPath = Paths .get (recordMetadataValue );
183
+
184
+ RecordOptions parsedOptions = RecordOptions .parse (recordMetadataValue );
170
185
try {
171
- Files .createDirectories (recordMetadataPath );
186
+ Files .createDirectories (parsedOptions . path () );
172
187
} catch (IOException ex ) {
173
- throw new IllegalArgumentException ("Exception occurred creating the output directory for tracing (" + recordMetadataPath + ")" , ex );
188
+ throw new IllegalArgumentException ("Exception occurred creating the output directory for tracing (" + parsedOptions . path () + ")" , ex );
174
189
}
175
- singleton .recordMetadataPath = recordMetadataPath ;
176
- singleton .config = new ConfigurationSet ();
190
+
191
+ MetadataTracer singleton = MetadataTracer .singleton ();
192
+ singleton .options = parsedOptions ;
193
+ singleton .config = initializeConfigurationSet (parsedOptions );
194
+ }
195
+
196
+ private static ConfigurationSet initializeConfigurationSet (RecordOptions options ) {
197
+ if (options .merge () && Files .exists (options .path ())) {
198
+ ConfigurationFileCollection mergeConfigs = new ConfigurationFileCollection ();
199
+ mergeConfigs .addDirectory (options .path ());
200
+ try {
201
+ return mergeConfigs .loadConfigurationSet (ioexception -> ioexception , null , null );
202
+ } catch (Exception ex ) {
203
+ // suppress and fall back on empty configuration set.
204
+ Log .log ().string ("An exception occurred when loading merge metadata from path " + options .path () + ". " )
205
+ .string ("Any existing metadata may be overwritten." ).newline ()
206
+ .string ("Exception: " ).exception (ex ).newline ();
207
+ }
208
+ }
209
+ return new ConfigurationSet ();
177
210
}
178
211
179
212
private static void shutdown () {
@@ -183,10 +216,10 @@ private static void shutdown() {
183
216
singleton .config = null ; // clear config so that shutdown events are not traced.
184
217
if (config != null ) {
185
218
try {
186
- config .writeConfiguration (configFile -> singleton .recordMetadataPath .resolve (configFile .getFileName ()));
219
+ config .writeConfiguration (configFile -> singleton .options . path () .resolve (configFile .getFileName ()));
187
220
} catch (IOException ex ) {
188
221
Log log = Log .log ();
189
- log .string ("Failed to write out reachability metadata to directory " ).string (singleton .recordMetadataPath .toString ());
222
+ log .string ("Failed to write out reachability metadata to directory " ).string (singleton .options . path () .toString ());
190
223
log .string (":" ).string (ex .getMessage ());
191
224
log .newline ();
192
225
}
@@ -200,7 +233,7 @@ static RuntimeSupport.Hook initializeMetadataTracingHook() {
200
233
}
201
234
VMError .guarantee (Options .MetadataTracingSupport .getValue ());
202
235
if (Options .RecordMetadata .hasBeenSet ()) {
203
- initialize ();
236
+ initialize (Options . RecordMetadata . getValue () );
204
237
}
205
238
};
206
239
}
@@ -230,12 +263,91 @@ static RuntimeSupport.Hook checkImproperOptionUsageHook() {
230
263
throw new IllegalArgumentException (
231
264
"The option " + Options .RecordMetadata .getName () + " can only be used if metadata tracing is enabled at build time (using " +
232
265
hostedOptionCommandArgument + ")." );
233
-
234
266
}
235
267
};
236
268
}
237
269
}
238
270
271
+ record RecordOptions (Path path , boolean merge ) {
272
+
273
+ private static final int ARGUMENT_PARTS = 2 ;
274
+
275
+ static RecordOptions parse (String recordMetadataValue ) {
276
+ if (recordMetadataValue .isEmpty ()) {
277
+ throw printHelp ("Option " + MetadataTracer .Options .RecordMetadata .getName () + " cannot be empty." );
278
+ } else if (recordMetadataValue .equals ("help" )) {
279
+ throw printHelp ("Option " + MetadataTracer .Options .RecordMetadata .getName () + " value is 'help'. Printing a description and aborting." );
280
+ }
281
+
282
+ Map <String , String > parsedArguments = new HashMap <>();
283
+ Set <String > allArguments = new LinkedHashSet <>(List .of ("path" , "merge" ));
284
+ for (String argument : recordMetadataValue .split ("," )) {
285
+ String [] parts = SubstrateUtil .split (argument , "=" , ARGUMENT_PARTS );
286
+ if (parts .length != ARGUMENT_PARTS ) {
287
+ throw badArgumentError (argument , "Argument should be a key-value pair separated by '='" );
288
+ } else if (!allArguments .contains (parts [0 ])) {
289
+ throw badArgumentError (argument , "Argument key should be one of " + allArguments );
290
+ } else if (parsedArguments .containsKey (parts [0 ])) {
291
+ throw badArgumentError (argument , "Argument '" + parts [0 ] + "' was already specified with value '" + parsedArguments .get (parts [0 ]) + "'" );
292
+ } else if (parts [1 ].isEmpty ()) {
293
+ throw badArgumentError (argument , "Value cannot be empty" );
294
+ }
295
+ parsedArguments .put (parts [0 ], parts [1 ]);
296
+ }
297
+
298
+ String path = requiredArgument (parsedArguments , "path" , IDENTITY_PARSER );
299
+ boolean merge = optionalArgument (parsedArguments , "merge" , true , BOOLEAN_PARSER );
300
+ return new RecordOptions (Paths .get (path ), merge );
301
+ }
302
+
303
+ private static IllegalArgumentException printHelp (String errorMessage ) {
304
+ throw new IllegalArgumentException ("""
305
+ %s
306
+
307
+ %s description:
308
+
309
+ %s
310
+ """ .formatted (errorMessage , MetadataTracer .Options .RecordMetadata .getName (), MetadataTracer .Options .RECORD_METADATA_HELP ));
311
+ }
312
+
313
+ private static IllegalArgumentException parseError (String message ) {
314
+ return new IllegalArgumentException (message + ". For more information (including usage examples), pass 'help' as an argument to " + MetadataTracer .Options .RecordMetadata .getName () + "." );
315
+ }
316
+
317
+ private static IllegalArgumentException badArgumentError (String argument , String message ) {
318
+ throw parseError ("Bad argument provided for " + MetadataTracer .Options .RecordMetadata .getName () + ": '" + argument + "'. " + message );
319
+ }
320
+
321
+ private static IllegalArgumentException badArgumentValueError (String argumentKey , String argumentValue , String message ) {
322
+ throw badArgumentError (argumentKey + "=" + argumentValue , message );
323
+ }
324
+
325
+ private interface ArgumentParser <T > {
326
+ T parse (String argumentKey , String argumentValue );
327
+ }
328
+
329
+ private static final ArgumentParser <String > IDENTITY_PARSER = ((argumentKey , argumentValue ) -> argumentValue );
330
+ private static final ArgumentParser <Boolean > BOOLEAN_PARSER = ((argumentKey , argumentValue ) -> switch (argumentValue ) {
331
+ case "true" -> true ;
332
+ case "false" -> false ;
333
+ default -> throw badArgumentValueError (argumentKey , argumentValue , "Value must be a literal 'true' or 'false'" );
334
+ });
335
+
336
+ private static <T > T requiredArgument (Map <String , String > arguments , String key , ArgumentParser <T > parser ) {
337
+ if (arguments .containsKey (key )) {
338
+ return parser .parse (key , arguments .get (key ));
339
+ }
340
+ throw parseError (MetadataTracer .Options .RecordMetadata .getName () + " missing required argument '" + key + "'" );
341
+ }
342
+
343
+ private static <T > T optionalArgument (Map <String , String > options , String key , T defaultValue , ArgumentParser <T > parser ) {
344
+ if (options .containsKey (key )) {
345
+ return parser .parse (key , options .get (key ));
346
+ }
347
+ return defaultValue ;
348
+ }
349
+ }
350
+
239
351
@ AutomaticallyRegisteredFeature
240
352
class MetadataTracerFeature implements InternalFeature {
241
353
@ Override
0 commit comments