1 /* 2 * Copyright (c) 2012, 2021, 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 package jdk.jfr.internal.dcmd; 26 27 import java.io.IOException; 28 import java.nio.file.Files; 29 import java.nio.file.InvalidPathException; 30 import java.nio.file.Path; 31 import java.nio.file.Paths; 32 import java.time.Duration; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.Comparator; 36 import java.util.List; 37 38 import jdk.jfr.FlightRecorder; 39 import jdk.jfr.Recording; 40 import jdk.jfr.internal.JVM; 41 import jdk.jfr.internal.LogLevel; 42 import jdk.jfr.internal.LogTag; 43 import jdk.jfr.internal.Logger; 44 import jdk.jfr.internal.SecuritySupport; 45 import jdk.jfr.internal.SecuritySupport.SafePath; 46 import jdk.jfr.internal.Utils; 47 48 /** 49 * Base class for JFR diagnostic commands 50 * 51 */ 52 abstract class AbstractDCmd { 53 54 private final StringBuilder currentLine = new StringBuilder(80); 55 private final List<String> lines = new ArrayList<>(); 56 private String source; 57 58 // Called by native printHelp()59 public abstract String[] printHelp(); 60 61 // Called by native. The number of arguments for each command is 62 // reported to the DCmdFramework as a hardcoded number in native. 63 // This is to avoid an upcall as part of DcmdFramework enumerating existing commands. 64 // Remember to keep the two sides in synch. getArgumentInfos()65 public abstract Argument[] getArgumentInfos(); 66 67 // Called by native execute(ArgumentParser parser)68 protected abstract void execute(ArgumentParser parser) throws DCmdException; 69 70 71 // Called by native execute(String source, String arg, char delimiter)72 public final String[] execute(String source, String arg, char delimiter) throws DCmdException { 73 this.source = source; 74 try { 75 boolean log = Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG); 76 if (log) { 77 System.out.println(arg); 78 Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing " + this.getClass().getSimpleName() + ": " + arg); 79 } 80 ArgumentParser parser = new ArgumentParser(getArgumentInfos(), arg, delimiter); 81 parser.parse(); 82 if (log) { 83 Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "DCMD options: " + parser.getOptions()); 84 if (parser.hasExtendedOptions()) { 85 Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "JFC options: " + parser.getExtendedOptions()); 86 } 87 } 88 execute(parser); 89 return getResult(); 90 } 91 catch (IllegalArgumentException iae) { 92 DCmdException e = new DCmdException(iae.getMessage()); 93 e.addSuppressed(iae); 94 throw e; 95 } 96 } 97 98 getFlightRecorder()99 protected final FlightRecorder getFlightRecorder() { 100 return FlightRecorder.getFlightRecorder(); 101 } 102 getResult()103 protected final String[] getResult() { 104 return lines.toArray(new String[lines.size()]); 105 } 106 logWarning(String message)107 protected void logWarning(String message) { 108 if (source.equals("internal")) { // -XX:StartFlightRecording 109 Logger.log(LogTag.JFR_START, LogLevel.WARN, message); 110 } else { // DiagnosticMXBean or JCMD 111 println("Warning! " + message); 112 } 113 } 114 getPid()115 public String getPid() { 116 // Invoking ProcessHandle.current().pid() would require loading more 117 // classes during startup so instead JVM.getJVM().getPid() is used. 118 // The pid will not be exposed to running Java application, only when starting 119 // JFR from command line (-XX:StartFlightRecording) or jcmd (JFR.start and JFR.check) 120 return JVM.getJVM().getPid(); 121 } 122 resolvePath(Recording recording, String filename)123 protected final SafePath resolvePath(Recording recording, String filename) throws InvalidPathException { 124 if (filename == null) { 125 return makeGenerated(recording, Paths.get(".")); 126 } 127 Path path = Paths.get(filename); 128 if (Files.isDirectory(path)) { 129 return makeGenerated(recording, path); 130 } 131 return new SafePath(path.toAbsolutePath().normalize()); 132 } 133 makeGenerated(Recording recording, Path directory)134 private SafePath makeGenerated(Recording recording, Path directory) { 135 return new SafePath(directory.toAbsolutePath().resolve(Utils.makeFilename(recording)).normalize()); 136 } 137 findRecording(String name)138 protected final Recording findRecording(String name) throws DCmdException { 139 try { 140 return findRecordingById(Integer.parseInt(name)); 141 } catch (NumberFormatException nfe) { 142 // User specified a name, not an id. 143 return findRecordingByName(name); 144 } 145 } 146 reportOperationComplete(String actionPrefix, String name, SafePath file)147 protected final void reportOperationComplete(String actionPrefix, String name, SafePath file) { 148 print(actionPrefix); 149 print(" recording"); 150 if (name != null) { 151 print(" \"" + name + "\""); 152 } 153 if (file != null) { 154 print(","); 155 try { 156 print(" "); 157 long bytes = SecuritySupport.getFileSize(file); 158 printBytes(bytes); 159 } catch (IOException e) { 160 // Ignore, not essential 161 } 162 println(" written to:"); 163 println(); 164 printPath(file); 165 } else { 166 println("."); 167 } 168 } 169 getRecordings()170 protected final List<Recording> getRecordings() { 171 List<Recording> list = new ArrayList<>(getFlightRecorder().getRecordings()); 172 Collections.sort(list, Comparator.comparing(Recording::getId)); 173 return list; 174 } 175 quoteIfNeeded(String text)176 static String quoteIfNeeded(String text) { 177 if (text.contains(" ")) { 178 return "\\\"" + text + "\\\""; 179 } else { 180 return text; 181 } 182 } 183 println()184 protected final void println() { 185 lines.add(currentLine.toString()); 186 currentLine.setLength(0); 187 } 188 print(String s)189 protected final void print(String s) { 190 currentLine.append(s); 191 } 192 print(String s, Object... args)193 protected final void print(String s, Object... args) { 194 currentLine.append(String.format(s, args)); 195 } 196 println(String s, Object... args)197 protected final void println(String s, Object... args) { 198 print(s, args); 199 println(); 200 } 201 printBytes(long bytes)202 protected final void printBytes(long bytes) { 203 print(Utils.formatBytes(bytes)); 204 } 205 printTimespan(Duration timespan, String separator)206 protected final void printTimespan(Duration timespan, String separator) { 207 print(Utils.formatTimespan(timespan, separator)); 208 } 209 printPath(SafePath path)210 protected final void printPath(SafePath path) { 211 if (path == null) { 212 print("N/A"); 213 return; 214 } 215 try { 216 printPath(SecuritySupport.getAbsolutePath(path).toPath()); 217 } catch (IOException ioe) { 218 printPath(path.toPath()); 219 } 220 } 221 printPath(Path path)222 protected final void printPath(Path path) { 223 try { 224 println(path.toAbsolutePath().toString()); 225 } catch (SecurityException e) { 226 // fall back on filename 227 println(path.toString()); 228 } 229 } 230 findRecordingById(int id)231 private Recording findRecordingById(int id) throws DCmdException { 232 for (Recording r : getFlightRecorder().getRecordings()) { 233 if (r.getId() == id) { 234 return r; 235 } 236 } 237 throw new DCmdException("Could not find %d.\n\nUse JFR.check without options to see list of all available recordings.", id); 238 } 239 findRecordingByName(String name)240 private Recording findRecordingByName(String name) throws DCmdException { 241 for (Recording recording : getFlightRecorder().getRecordings()) { 242 if (name.equals(recording.getName())) { 243 return recording; 244 } 245 } 246 throw new DCmdException("Could not find %s.\n\nUse JFR.check without options to see list of all available recordings.", name); 247 } 248 exampleRepository()249 protected final String exampleRepository() { 250 if ("\r\n".equals(System.lineSeparator())) { 251 return "C:\\Repositories"; 252 } else { 253 return "/Repositories"; 254 } 255 } 256 exampleFilename()257 protected final String exampleFilename() { 258 if ("\r\n".equals(System.lineSeparator())) { 259 return "C:\\Users\\user\\recording.jfr"; 260 } else { 261 return "/recordings/recording.jfr"; 262 } 263 } 264 exampleDirectory()265 protected final String exampleDirectory() { 266 if ("\r\n".equals(System.lineSeparator())) { 267 return "C:\\Directory\\recordings"; 268 } else { 269 return "/directory/recordings"; 270 } 271 } 272 } 273