Skip to content

Commit 86e4c75

Browse files
y1yang0egahlin
authored andcommitted
8256156: JFR: Allow 'jfr' tool to show metadata without a recording
Reviewed-by: egahlin
1 parent 0b68ced commit 86e4c75

File tree

5 files changed

+363
-133
lines changed

5 files changed

+363
-133
lines changed

src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Command.java

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,14 +31,21 @@
3131
import java.io.IOException;
3232
import java.io.PrintStream;
3333
import java.io.RandomAccessFile;
34+
import java.nio.charset.Charset;
3435
import java.nio.file.Files;
3536
import java.nio.file.InvalidPathException;
3637
import java.nio.file.Path;
3738
import java.nio.file.Paths;
3839
import java.util.ArrayList;
3940
import java.util.Collections;
4041
import java.util.Deque;
42+
import java.util.HashMap;
4143
import java.util.List;
44+
import java.util.Map;
45+
import java.util.function.Function;
46+
import java.util.function.Predicate;
47+
48+
import jdk.jfr.EventType;
4249

4350
abstract class Command {
4451
public final static String title = "Tool for working with Flight Recorder files (.jfr)";
@@ -236,7 +243,7 @@ final protected Path getJFRInputFile(Deque<String> options) throws UserSyntaxExc
236243
}
237244
}
238245

239-
private void ensureAccess(Path path) throws UserDataException {
246+
final protected void ensureAccess(Path path) throws UserDataException {
240247
try (RandomAccessFile rad = new RandomAccessFile(path.toFile(), "r")) {
241248
if (rad.length() == 0) {
242249
throw new UserDataException("file is empty '" + path + "'");
@@ -303,4 +310,108 @@ public List<String> getNames() {
303310
names.addAll(getAliases());
304311
return names;
305312
}
306-
}
313+
314+
public static void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException {
315+
if (typo.equals(options.peek())) {
316+
throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?");
317+
}
318+
}
319+
320+
final protected static char quoteCharacter() {
321+
return File.pathSeparatorChar == ';' ? '"' : '\'';
322+
}
323+
324+
private static <T> Predicate<T> recurseIfPossible(Predicate<T> filter) {
325+
return x -> filter != null && filter.test(x);
326+
}
327+
328+
private static String acronomify(String multipleWords) {
329+
boolean newWord = true;
330+
String acronym = "";
331+
for (char c : multipleWords.toCharArray()) {
332+
if (newWord) {
333+
if (Character.isAlphabetic(c) && Character.isUpperCase(c)) {
334+
acronym += c;
335+
}
336+
}
337+
newWord = Character.isWhitespace(c);
338+
}
339+
return acronym;
340+
}
341+
342+
private static boolean match(String text, String filter) {
343+
if (filter.length() == 0) {
344+
// empty filter string matches if string is empty
345+
return text.length() == 0;
346+
}
347+
if (filter.charAt(0) == '*') { // recursive check
348+
filter = filter.substring(1);
349+
for (int n = 0; n <= text.length(); n++) {
350+
if (match(text.substring(n), filter))
351+
return true;
352+
}
353+
} else if (text.length() == 0) {
354+
// empty string and non-empty filter does not match
355+
return false;
356+
} else if (filter.charAt(0) == '?') {
357+
// eat any char and move on
358+
return match(text.substring(1), filter.substring(1));
359+
} else if (filter.charAt(0) == text.charAt(0)) {
360+
// eat chars and move on
361+
return match(text.substring(1), filter.substring(1));
362+
}
363+
return false;
364+
}
365+
366+
private static List<String> explodeFilter(String filter) throws UserSyntaxException {
367+
List<String> list = new ArrayList<>();
368+
for (String s : filter.split(",")) {
369+
s = s.trim();
370+
if (!s.isEmpty()) {
371+
list.add(s);
372+
}
373+
}
374+
return list;
375+
}
376+
377+
final protected static Predicate<EventType> addCategoryFilter(String filterText, Predicate<EventType> eventFilter) throws UserSyntaxException {
378+
List<String> filters = explodeFilter(filterText);
379+
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
380+
for (String category : eventType.getCategoryNames()) {
381+
for (String filter : filters) {
382+
if (match(category, filter)) {
383+
return true;
384+
}
385+
if (category.contains(" ") && acronomify(category).equals(filter)) {
386+
return true;
387+
}
388+
}
389+
}
390+
return false;
391+
});
392+
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
393+
}
394+
395+
final protected static Predicate<EventType> addEventFilter(String filterText, final Predicate<EventType> eventFilter) throws UserSyntaxException {
396+
List<String> filters = explodeFilter(filterText);
397+
Predicate<EventType> newFilter = recurseIfPossible(eventType -> {
398+
for (String filter : filters) {
399+
String fullEventName = eventType.getName();
400+
if (match(fullEventName, filter)) {
401+
return true;
402+
}
403+
String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1);
404+
if (match(eventName, filter)) {
405+
return true;
406+
}
407+
}
408+
return false;
409+
});
410+
return eventFilter == null ? newFilter : eventFilter.or(newFilter);
411+
}
412+
413+
final protected static <T, X> Predicate<T> addCache(final Predicate<T> filter, Function<T, X> cacheFunction) {
414+
Map<X, Boolean> cache = new HashMap<>();
415+
return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t));
416+
}
417+
}

src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Main.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -75,6 +75,8 @@ public static void main(String... args) {
7575
System.out.println();
7676
System.out.println(" jfr metadata recording.jfr");
7777
System.out.println();
78+
System.out.println(" jfr metadata --categories GC,Detailed");
79+
System.out.println();
7880
System.out.println("For more information about available commands, use 'jfr help'");
7981
System.exit(EXIT_OK);
8082
}

src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java

Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,15 +26,25 @@
2626
package jdk.jfr.internal.tool;
2727

2828
import java.io.IOException;
29+
import java.io.PrintStream;
2930
import java.io.PrintWriter;
31+
import java.nio.charset.Charset;
3032
import java.nio.file.Path;
33+
import java.nio.file.Paths;
34+
import java.util.ArrayList;
3135
import java.util.Collections;
3236
import java.util.Comparator;
3337
import java.util.Deque;
3438
import java.util.List;
39+
import java.util.function.Predicate;
3540

41+
import jdk.jfr.EventType;
42+
import jdk.jfr.FlightRecorder;
3643
import jdk.jfr.consumer.RecordingFile;
44+
import jdk.jfr.internal.PlatformEventType;
45+
import jdk.jfr.internal.PrivateAccess;
3746
import jdk.jfr.internal.Type;
47+
import jdk.jfr.internal.TypeLibrary;
3848
import jdk.jfr.internal.consumer.JdkJfrConsumer;
3949

4050
final class Metadata extends Command {
@@ -91,52 +101,165 @@ int groupValue(Type t) {
91101
}
92102
}
93103

94-
95104
@Override
96105
public String getName() {
97106
return "metadata";
98107
}
99108

100109
@Override
101110
public List<String> getOptionSyntax() {
102-
return Collections.singletonList("<file>");
111+
List<String> list = new ArrayList<>();
112+
list.add("[--categories <filter>]");
113+
list.add("[--events <filter>]");
114+
list.add("[<file>]");
115+
return list;
103116
}
104117

105118
@Override
106-
public String getDescription() {
119+
protected String getTitle() {
107120
return "Display event metadata, such as labels, descriptions and field layout";
108121
}
109122

123+
@Override
124+
public String getDescription() {
125+
return getTitle() + ". See 'jfr help metadata' for details.";
126+
}
127+
128+
@Override
129+
public void displayOptionUsage(PrintStream stream) {
130+
char q = quoteCharacter();
131+
stream.println(" --categories <filter> Select events matching a category name.");
132+
stream.println(" The filter is a comma-separated list of names,");
133+
stream.println(" simple and/or qualified, and/or quoted glob patterns");
134+
stream.println();
135+
stream.println(" --events <filter> Select events matching an event name.");
136+
stream.println(" The filter is a comma-separated list of names,");
137+
stream.println(" simple and/or qualified, and/or quoted glob patterns");
138+
stream.println();
139+
stream.println(" <file> Location of the recording file (.jfr)");
140+
stream.println();
141+
stream.println("If the <file> parameter is omitted, metadata from the JDK where");
142+
stream.println("the " + q + "jfr" + q + " tool is located will be used");
143+
stream.println();
144+
stream.println();
145+
stream.println("Example usage:");
146+
stream.println();
147+
stream.println(" jfr metadata");
148+
stream.println();
149+
stream.println(" jfr metadata --events jdk.ThreadStart recording.jfr");
150+
stream.println();
151+
stream.println(" jfr metadata --events CPULoad,GarbageCollection");
152+
stream.println();
153+
stream.println(" jfr metadata --categories " + q + "GC,JVM,Java*" + q);
154+
stream.println();
155+
stream.println(" jfr metadata --events " + q + "Thread*" + q);
156+
stream.println();
157+
}
158+
110159
@Override
111160
public void execute(Deque<String> options) throws UserSyntaxException, UserDataException {
112-
Path file = getJFRInputFile(options);
161+
Path file = getOptionalJFRInputFile(options);
113162

114163
boolean showIds = false;
164+
boolean foundEventFilter = false;
165+
boolean foundCategoryFilter = false;
166+
Predicate<EventType> filter = null;
115167
int optionCount = options.size();
116168
while (optionCount > 0) {
117-
if (acceptOption(options, "--ids")) {
169+
// internal option, doest not export to users
170+
if (acceptSingleOption(options, "--ids")) {
118171
showIds = true;
119172
}
173+
if (acceptFilterOption(options, "--events")) {
174+
if (foundEventFilter) {
175+
throw new UserSyntaxException("use --events event1,event2,event3 to include multiple events");
176+
}
177+
foundEventFilter = true;
178+
String filterStr = options.remove();
179+
warnForWildcardExpansion("--events", filterStr);
180+
filter = addEventFilter(filterStr, filter);
181+
}
182+
if (acceptFilterOption(options, "--categories")) {
183+
if (foundCategoryFilter) {
184+
throw new UserSyntaxException("use --categories category1,category2 to include multiple categories");
185+
}
186+
foundCategoryFilter = true;
187+
String filterStr = options.remove();
188+
warnForWildcardExpansion("--categories", filterStr);
189+
filter = addCategoryFilter(filterStr, filter);
190+
}
120191
if (optionCount == options.size()) {
121192
// No progress made
193+
checkCommonError(options, "--event", "--events");
194+
checkCommonError(options, "--category", "--categories");
122195
throw new UserSyntaxException("unknown option " + options.peek());
123196
}
124197
optionCount = options.size();
125198
}
126199

127-
try (PrintWriter pw = new PrintWriter(System.out)) {
200+
try (PrintWriter pw = new PrintWriter(System.out, false, Charset.forName("UTF-8"))) {
128201
PrettyWriter prettyWriter = new PrettyWriter(pw);
129202
prettyWriter.setShowIds(showIds);
130-
try (RecordingFile rf = new RecordingFile(file)) {
131-
List<Type> types = PRIVATE_ACCESS.readTypes(rf);
132-
Collections.sort(types, new TypeComparator());
133-
for (Type type : types) {
203+
if (filter != null) {
204+
filter = addCache(filter, type -> type.getId());
205+
}
206+
207+
List<Type> types = findTypes(file);
208+
Collections.sort(types, new TypeComparator());
209+
for (Type type : types) {
210+
if (filter != null) {
211+
// If --events or --categories, only operate on events
212+
if (Type.SUPER_TYPE_EVENT.equals(type.getSuperType())) {
213+
EventType et = PrivateAccess.getInstance().newEventType((PlatformEventType) type);
214+
if (filter.test(et)) {
215+
prettyWriter.printType(type);
216+
}
217+
}
218+
} else {
134219
prettyWriter.printType(type);
135220
}
136-
prettyWriter.flush(true);
137-
} catch (IOException ioe) {
138-
couldNotReadError(file, ioe);
139221
}
222+
prettyWriter.flush(true);
223+
pw.flush();
224+
}
225+
}
226+
227+
private List<Type> findTypes(Path file) throws UserDataException {
228+
// Determine whether reading from recording file or reading from the JDK where
229+
// the jfr tool is located will be used
230+
if (file == null) {
231+
// Force initialization
232+
FlightRecorder.getFlightRecorder().getEventTypes();
233+
return TypeLibrary.getInstance().getTypes();
234+
}
235+
try (RecordingFile rf = new RecordingFile(file)) {
236+
return PRIVATE_ACCESS.readTypes(rf);
237+
} catch (IOException ioe) {
238+
couldNotReadError(file, ioe);
239+
}
240+
return null; // Can't reach
241+
}
242+
243+
private Path getOptionalJFRInputFile(Deque<String> options) throws UserDataException {
244+
if (!options.isEmpty()) {
245+
String file = options.getLast();
246+
if (!file.startsWith("--")) {
247+
Path tmp = Paths.get(file).toAbsolutePath();
248+
if (tmp.toString().endsWith(".jfr")) {
249+
ensureAccess(tmp);
250+
options.removeLast();
251+
return tmp;
252+
}
253+
}
254+
}
255+
return null;
256+
}
257+
258+
private static boolean acceptSingleOption(Deque<String> options, String expected) {
259+
if (expected.equals(options.peek())) {
260+
options.remove();
261+
return true;
140262
}
263+
return false;
141264
}
142265
}

0 commit comments

Comments
 (0)