1 /* 2 * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.internal.tool; 27 28 import java.io.IOException; 29 import java.io.PrintStream; 30 import java.io.PrintWriter; 31 import java.nio.charset.Charset; 32 import java.nio.file.Path; 33 import java.util.ArrayList; 34 import java.util.Deque; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.function.Function; 39 import java.util.function.Predicate; 40 41 import jdk.jfr.EventType; 42 43 final class Print extends Command { 44 @Override getName()45 public String getName() { 46 return "print"; 47 } 48 49 @Override getOptionSyntax()50 public List<String> getOptionSyntax() { 51 List<String> list = new ArrayList<>(); 52 list.add("[--xml|--json]"); 53 list.add("[--categories <filter>]"); 54 list.add("[--events <filter>]"); 55 list.add("[--stack-depth <depth>]"); 56 list.add("<file>"); 57 return list; 58 } 59 60 @Override getTitle()61 protected String getTitle() { 62 return "Print contents of a recording file"; 63 } 64 65 @Override getDescription()66 public String getDescription() { 67 return getTitle() + ". See 'jfr help print' for details."; 68 } 69 70 @Override displayOptionUsage(PrintStream stream)71 public void displayOptionUsage(PrintStream stream) { 72 stream.println(" --xml Print recording in XML format"); 73 stream.println(); 74 stream.println(" --json Print recording in JSON format"); 75 stream.println(); 76 stream.println(" --categories <filter> Select events matching a category name."); 77 stream.println(" The filter is a comma-separated list of names,"); 78 stream.println(" simple and/or qualified, and/or quoted glob patterns"); 79 stream.println(); 80 stream.println(" --events <filter> Select events matching an event name."); 81 stream.println(" The filter is a comma-separated list of names,"); 82 stream.println(" simple and/or qualified, and/or quoted glob patterns"); 83 stream.println(); 84 stream.println(" --stack-depth <depth> Number of frames in stack traces, by default 5"); 85 stream.println(); 86 stream.println(" <file> Location of the recording file (.jfr)"); 87 stream.println(); 88 stream.println(); 89 stream.println("Example usage:"); 90 stream.println(); 91 stream.println(" jfr print --events OldObjectSample recording.jfr"); 92 stream.println(); 93 stream.println(" jfr print --events CPULoad,GarbageCollection recording.jfr"); 94 stream.println(); 95 stream.println(" jfr print --categories \"GC,JVM,Java*\" recording.jfr"); 96 stream.println(); 97 stream.println(" jfr print --events \"jdk.*\" --stack-depth 64 recording.jfr"); 98 stream.println(); 99 stream.println(" jfr print --json --events CPULoad recording.jfr"); 100 } 101 102 @Override execute(Deque<String> options)103 public void execute(Deque<String> options) throws UserSyntaxException, UserDataException { 104 Path file = getJFRInputFile(options); 105 PrintWriter pw = new PrintWriter(System.out, false, Charset.forName("UTF-8")); 106 Predicate<EventType> eventFilter = null; 107 int stackDepth = 5; 108 EventPrintWriter eventWriter = null; 109 int optionCount = options.size(); 110 boolean foundEventFilter = false; 111 boolean foundCategoryFilter = false; 112 while (optionCount > 0) { 113 if (acceptFilterOption(options, "--events")) { 114 if (foundEventFilter) { 115 throw new UserSyntaxException("use --events event1,event2,event3 to include multiple events"); 116 } 117 foundEventFilter = true; 118 String filter = options.remove(); 119 warnForWildcardExpansion("--events", filter); 120 eventFilter = addEventFilter(filter, eventFilter); 121 } 122 if (acceptFilterOption(options, "--categories")) { 123 if (foundCategoryFilter) { 124 throw new UserSyntaxException("use --categories category1,category2 to include multiple categories"); 125 } 126 foundCategoryFilter = true; 127 String filter = options.remove(); 128 warnForWildcardExpansion("--categories", filter); 129 eventFilter = addCategoryFilter(filter, eventFilter); 130 } 131 if (acceptOption(options, "--stack-depth")) { 132 String value = options.pop(); 133 try { 134 stackDepth = Integer.parseInt(value); 135 if (stackDepth < 0) { 136 throw new UserSyntaxException("stack depth must be zero or a positive integer."); 137 } 138 } catch (NumberFormatException nfe) { 139 throw new UserSyntaxException("not a valid value for --stack-depth"); 140 } 141 } 142 if (acceptFormatterOption(options, eventWriter, "--json")) { 143 eventWriter = new JSONWriter(pw); 144 } 145 if (acceptFormatterOption(options, eventWriter, "--xml")) { 146 eventWriter = new XMLWriter(pw); 147 } 148 if (optionCount == options.size()) { 149 // No progress made 150 checkCommonError(options, "--event", "--events"); 151 checkCommonError(options, "--category", "--categories"); 152 throw new UserSyntaxException("unknown option " + options.peek()); 153 } 154 optionCount = options.size(); 155 } 156 if (eventWriter == null) { 157 eventWriter = new PrettyWriter(pw); // default to pretty printer 158 } 159 eventWriter.setStackDepth(stackDepth); 160 if (eventFilter != null) { 161 eventFilter = addCache(eventFilter, eventType -> eventType.getId()); 162 eventWriter.setEventFilter(eventFilter); 163 } 164 try { 165 eventWriter.print(file); 166 } catch (IOException ioe) { 167 couldNotReadError(file, ioe); 168 } 169 pw.flush(); 170 } 171 checkCommonError(Deque<String> options, String typo, String correct)172 private void checkCommonError(Deque<String> options, String typo, String correct) throws UserSyntaxException { 173 if (typo.equals(options.peek())) { 174 throw new UserSyntaxException("unknown option " + typo + ", did you mean " + correct + "?"); 175 } 176 } 177 acceptFormatterOption(Deque<String> options, EventPrintWriter eventWriter, String expected)178 private static boolean acceptFormatterOption(Deque<String> options, EventPrintWriter eventWriter, String expected) throws UserSyntaxException { 179 if (expected.equals(options.peek())) { 180 if (eventWriter != null) { 181 throw new UserSyntaxException("only one format can be specified at a time"); 182 } 183 options.remove(); 184 return true; 185 } 186 return false; 187 } 188 addCache(final Predicate<T> filter, Function<T, X> cacheFunction)189 private static <T, X> Predicate<T> addCache(final Predicate<T> filter, Function<T, X> cacheFunction) { 190 Map<X, Boolean> cache = new HashMap<>(); 191 return t -> cache.computeIfAbsent(cacheFunction.apply(t), x -> filter.test(t)); 192 } 193 recurseIfPossible(Predicate<T> filter)194 private static <T> Predicate<T> recurseIfPossible(Predicate<T> filter) { 195 return x -> filter != null && filter.test(x); 196 } 197 addCategoryFilter(String filterText, Predicate<EventType> eventFilter)198 private static Predicate<EventType> addCategoryFilter(String filterText, Predicate<EventType> eventFilter) throws UserSyntaxException { 199 List<String> filters = explodeFilter(filterText); 200 Predicate<EventType> newFilter = recurseIfPossible(eventType -> { 201 for (String category : eventType.getCategoryNames()) { 202 for (String filter : filters) { 203 if (match(category, filter)) { 204 return true; 205 } 206 if (category.contains(" ") && acronomify(category).equals(filter)) { 207 return true; 208 } 209 } 210 } 211 return false; 212 }); 213 return eventFilter == null ? newFilter : eventFilter.or(newFilter); 214 } 215 acronomify(String multipleWords)216 private static String acronomify(String multipleWords) { 217 boolean newWord = true; 218 String acronym = ""; 219 for (char c : multipleWords.toCharArray()) { 220 if (newWord) { 221 if (Character.isAlphabetic(c) && Character.isUpperCase(c)) { 222 acronym += c; 223 } 224 } 225 newWord = Character.isWhitespace(c); 226 } 227 return acronym; 228 } 229 addEventFilter(String filterText, final Predicate<EventType> eventFilter)230 private static Predicate<EventType> addEventFilter(String filterText, final Predicate<EventType> eventFilter) throws UserSyntaxException { 231 List<String> filters = explodeFilter(filterText); 232 Predicate<EventType> newFilter = recurseIfPossible(eventType -> { 233 for (String filter : filters) { 234 String fullEventName = eventType.getName(); 235 if (match(fullEventName, filter)) { 236 return true; 237 } 238 String eventName = fullEventName.substring(fullEventName.lastIndexOf(".") + 1); 239 if (match(eventName, filter)) { 240 return true; 241 } 242 } 243 return false; 244 }); 245 return eventFilter == null ? newFilter : eventFilter.or(newFilter); 246 } 247 match(String text, String filter)248 private static boolean match(String text, String filter) { 249 if (filter.length() == 0) { 250 // empty filter string matches if string is empty 251 return text.length() == 0; 252 } 253 if (filter.charAt(0) == '*') { // recursive check 254 filter = filter.substring(1); 255 for (int n = 0; n <= text.length(); n++) { 256 if (match(text.substring(n), filter)) 257 return true; 258 } 259 } else if (text.length() == 0) { 260 // empty string and non-empty filter does not match 261 return false; 262 } else if (filter.charAt(0) == '?') { 263 // eat any char and move on 264 return match(text.substring(1), filter.substring(1)); 265 } else if (filter.charAt(0) == text.charAt(0)) { 266 // eat chars and move on 267 return match(text.substring(1), filter.substring(1)); 268 } 269 return false; 270 } 271 explodeFilter(String filter)272 private static List<String> explodeFilter(String filter) throws UserSyntaxException { 273 List<String> list = new ArrayList<>(); 274 for (String s : filter.split(",")) { 275 s = s.trim(); 276 if (!s.isEmpty()) { 277 list.add(s); 278 } 279 } 280 return list; 281 } 282 } 283