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 .Locale ;
35
+ import java .util .Map ;
36
+ import java .util .Set ;
32
37
33
38
import org .graalvm .nativeimage .ImageSingletons ;
34
39
import org .graalvm .nativeimage .hosted .Feature ;
39
44
import com .oracle .svm .configure .UnresolvedConfigurationCondition ;
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 ;
55
61
/**
56
62
* Implements reachability metadata tracing during native image execution. Enabling
57
63
* {@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 .
64
+ * reachability metadata, and then the run-time option {@link Options#RecordMetadata} enables
65
+ * tracing .
60
66
*/
61
67
public final class MetadataTracer {
62
68
63
69
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>." )//
70
+ @ Option (help = "Generate an image that supports reachability metadata access tracing. " +
71
+ "When tracing is supported, use the -XX:RecordMetadata option to enable tracing at run time." )//
65
72
public static final HostedOptionKey <Boolean > MetadataTracingSupport = new HostedOptionKey <>(false );
66
73
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 <>("" );
74
+ @ Option (help = "file:doc-files/RecordMetadataHelp.txt " )//
75
+ public static final RuntimeOptionKey <String > RecordMetadata = new RuntimeOptionKey <>(null );
69
76
}
70
77
78
+ private RecordOptions options ;
71
79
private volatile ConfigurationSet config ;
72
80
73
- private Path recordMetadataPath ;
74
-
75
81
@ Fold
76
82
public static MetadataTracer singleton () {
77
83
return ImageSingletons .lookup (MetadataTracer .class );
78
84
}
79
85
80
86
/**
81
- * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata=path }).
87
+ * Returns whether tracing is enabled at run time (using {@code -XX:RecordMetadata}).
82
88
*/
83
89
public boolean enabled () {
84
90
VMError .guarantee (Options .MetadataTracingSupport .getValue ());
85
- return recordMetadataPath != null ;
91
+ return options != null ;
86
92
}
87
93
88
94
/**
@@ -159,20 +165,21 @@ public void traceSerializationType(String className) {
159
165
}
160
166
}
161
167
162
- private static void initialize () {
168
+ private static void initialize (String recordMetadataValue ) {
163
169
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 );
170
+
171
+ RecordOptions parsedOptions = RecordOptions .parse (recordMetadataValue );
170
172
try {
171
- Files .createDirectories (recordMetadataPath );
173
+ Files .createDirectories (parsedOptions . path () );
172
174
} catch (IOException ex ) {
173
- throw new IllegalArgumentException ("Exception occurred creating the output directory for tracing (" + recordMetadataPath + ")" , ex );
175
+ throw new IllegalArgumentException ("Exception occurred creating the output directory for tracing (" + parsedOptions . path () + ")" , ex );
174
176
}
175
- singleton .recordMetadataPath = recordMetadataPath ;
177
+ if (parsedOptions .mode () != RecordMode .DEFAULT ) {
178
+ throw new IllegalArgumentException ("Mode " + parsedOptions .mode () + " is not yet supported." );
179
+ }
180
+
181
+ MetadataTracer singleton = MetadataTracer .singleton ();
182
+ singleton .options = parsedOptions ;
176
183
singleton .config = new ConfigurationSet ();
177
184
}
178
185
@@ -183,10 +190,10 @@ private static void shutdown() {
183
190
singleton .config = null ; // clear config so that shutdown events are not traced.
184
191
if (config != null ) {
185
192
try {
186
- config .writeConfiguration (configFile -> singleton .recordMetadataPath .resolve (configFile .getFileName ()));
193
+ config .writeConfiguration (configFile -> singleton .options . path () .resolve (configFile .getFileName ()));
187
194
} catch (IOException ex ) {
188
195
Log log = Log .log ();
189
- log .string ("Failed to write out reachability metadata to directory " ).string (singleton .recordMetadataPath .toString ());
196
+ log .string ("Failed to write out reachability metadata to directory " ).string (singleton .options . path () .toString ());
190
197
log .string (":" ).string (ex .getMessage ());
191
198
log .newline ();
192
199
}
@@ -200,7 +207,7 @@ static RuntimeSupport.Hook initializeMetadataTracingHook() {
200
207
}
201
208
VMError .guarantee (Options .MetadataTracingSupport .getValue ());
202
209
if (Options .RecordMetadata .hasBeenSet ()) {
203
- initialize ();
210
+ initialize (Options . RecordMetadata . getValue () );
204
211
}
205
212
};
206
213
}
@@ -230,12 +237,86 @@ static RuntimeSupport.Hook checkImproperOptionUsageHook() {
230
237
throw new IllegalArgumentException (
231
238
"The option " + Options .RecordMetadata .getName () + " can only be used if metadata tracing is enabled at build time (using " +
232
239
hostedOptionCommandArgument + ")." );
233
-
234
240
}
235
241
};
236
242
}
237
243
}
238
244
245
+ enum RecordMode {
246
+ DEFAULT ,
247
+ CONDITIONAL
248
+ }
249
+
250
+ record RecordOptions (Path path , RecordMode mode ) {
251
+
252
+ static RecordOptions parse (String recordMetadataValue ) {
253
+ if (recordMetadataValue .isEmpty ()) {
254
+ throw parseError ("Option " + MetadataTracer .Options .RecordMetadata .getName () + " cannot be empty" );
255
+ }
256
+
257
+ Map <String , String > parsedArguments = new HashMap <>();
258
+ Set <String > allArguments = new LinkedHashSet <>(List .of ("path" , "mode" ));
259
+ for (String argument : recordMetadataValue .split ("," )) {
260
+ String [] parts = SubstrateUtil .split (argument , "=" , 2 );
261
+ if (parts .length != 2 ) {
262
+ throw badArgumentError (argument , "Argument should be a key-value pair separated by '='" );
263
+ } else if (!allArguments .contains (parts [0 ])) {
264
+ throw badArgumentError (argument , "Argument key should be one of " + allArguments );
265
+ } else if (parsedArguments .containsKey (parts [0 ])) {
266
+ throw badArgumentError (argument , "Argument '" + parts [0 ] + "' was already specified with value '" + parsedArguments .get (parts [0 ]) + "'" );
267
+ } else if (parts [1 ].isEmpty ()) {
268
+ throw badArgumentError (argument , "Argument '" + parts [0 ] + "' cannot be empty" );
269
+ }
270
+ parsedArguments .put (parts [0 ], parts [1 ]);
271
+ }
272
+
273
+ String path = requiredArgument (parsedArguments , "path" );
274
+ String mode = optionalArgument (parsedArguments , "mode" , "default" );
275
+ return new RecordOptions (Paths .get (path ), parseMode (mode ));
276
+ }
277
+
278
+ private static IllegalArgumentException parseError (String message ) {
279
+ return new IllegalArgumentException (message + ". Sample usage: -XX:" + MetadataTracer .Options .RecordMetadata .getName () + "=path=<trace-output-directory>[,mode=<mode>]" );
280
+ }
281
+
282
+ private static IllegalArgumentException badArgumentError (String option , String message ) {
283
+ throw parseError ("Bad argument provided for " + MetadataTracer .Options .RecordMetadata .getName () + ": '" + option + "'. " + message );
284
+ }
285
+
286
+ private static String requiredArgument (Map <String , String > arguments , String key ) {
287
+ if (arguments .containsKey (key )) {
288
+ return arguments .get (key );
289
+ }
290
+ throw parseError (MetadataTracer .Options .RecordMetadata .getName () + " missing required argument '" + key + "'" );
291
+ }
292
+
293
+ private static String optionalArgument (Map <String , String > options , String key , String defaultValue ) {
294
+ if (options .containsKey (key )) {
295
+ return options .get (key );
296
+ }
297
+ return defaultValue ;
298
+ }
299
+
300
+ private static RecordMode parseMode (String modeValue ) {
301
+ try {
302
+ return RecordMode .valueOf (modeValue .toUpperCase (Locale .ROOT ));
303
+ } catch (IllegalArgumentException ex ) {
304
+ StringBuilder message = new StringBuilder ("Mode should be one of [" );
305
+ boolean first = true ;
306
+ for (RecordMode mode : RecordMode .values ()) {
307
+ if (first ) {
308
+ first = false ;
309
+ } else {
310
+ message .append (", " );
311
+ }
312
+ message .append (mode .toString ().toLowerCase (Locale .ROOT ));
313
+ }
314
+ message .append ("]" );
315
+ throw badArgumentError ("mode=" + modeValue , message .toString ());
316
+ }
317
+ }
318
+ }
319
+
239
320
@ AutomaticallyRegisteredFeature
240
321
class MetadataTracerFeature implements InternalFeature {
241
322
@ Override
0 commit comments