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