1 /*
2  * Copyright (c) 2012, 2020, 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.io.PrintWriter;
29 import java.io.StringWriter;
30 import java.nio.file.Files;
31 import java.nio.file.InvalidPathException;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.time.Duration;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.List;
39 
40 import jdk.jfr.FlightRecorder;
41 import jdk.jfr.Recording;
42 import jdk.jfr.internal.JVM;
43 import jdk.jfr.internal.SecuritySupport;
44 import jdk.jfr.internal.SecuritySupport.SafePath;
45 import jdk.jfr.internal.Utils;
46 
47 /**
48  * Base class for JFR diagnostic commands
49  *
50  */
51 abstract class AbstractDCmd {
52 
53     private final StringWriter result;
54     private final PrintWriter log;
55 
AbstractDCmd()56     protected AbstractDCmd() {
57         result = new StringWriter();
58         log = new PrintWriter(result);
59     }
60 
getFlightRecorder()61     protected final FlightRecorder getFlightRecorder() {
62         return FlightRecorder.getFlightRecorder();
63     }
64 
getResult()65     public final String getResult() {
66         return result.toString();
67     }
68 
getPid()69     public String getPid() {
70         // Invoking ProcessHandle.current().pid() would require loading more
71         // classes during startup so instead JVM.getJVM().getPid() is used.
72         // The pid will not be exposed to running Java application, only when starting
73         // JFR from command line (-XX:StartFlightRecording) or jcmd (JFR.start and JFR.check)
74         return JVM.getJVM().getPid();
75     }
76 
resolvePath(Recording recording, String filename)77     protected final SafePath resolvePath(Recording recording, String filename) throws InvalidPathException {
78         if (filename == null) {
79             return makeGenerated(recording, Paths.get("."));
80         }
81         Path path = Paths.get(filename);
82         if (Files.isDirectory(path)) {
83             return makeGenerated(recording, path);
84         }
85         return new SafePath(path.toAbsolutePath().normalize());
86     }
87 
makeGenerated(Recording recording, Path directory)88     private SafePath makeGenerated(Recording recording, Path directory) {
89         return new SafePath(directory.toAbsolutePath().resolve(Utils.makeFilename(recording)).normalize());
90     }
91 
findRecording(String name)92     protected final Recording findRecording(String name) throws DCmdException {
93         try {
94             return findRecordingById(Integer.parseInt(name));
95         } catch (NumberFormatException nfe) {
96             // User specified a name, not an id.
97             return findRecordingByName(name);
98         }
99     }
100 
reportOperationComplete(String actionPrefix, String name, SafePath file)101     protected final void reportOperationComplete(String actionPrefix, String name, SafePath file) {
102         print(actionPrefix);
103         print(" recording");
104         if (name != null) {
105             print(" \"" + name + "\"");
106         }
107         if (file != null) {
108             print(",");
109             try {
110                 print(" ");
111                 long bytes = SecuritySupport.getFileSize(file);
112                 printBytes(bytes);
113             } catch (IOException e) {
114                 // Ignore, not essential
115             }
116             println(" written to:");
117             println();
118             printPath(file);
119         } else {
120             println(".");
121         }
122     }
123 
getRecordings()124     protected final List<Recording> getRecordings() {
125         List<Recording> list = new ArrayList<>(getFlightRecorder().getRecordings());
126         Collections.sort(list, Comparator.comparing(Recording::getId));
127         return list;
128     }
129 
quoteIfNeeded(String text)130     static String quoteIfNeeded(String text) {
131         if (text.contains(" ")) {
132             return "\\\"" + text + "\\\"";
133         } else {
134             return text;
135         }
136     }
137 
println()138     protected final void println() {
139         log.println();
140     }
141 
print(String s)142     protected final void print(String s) {
143         log.print(s);
144     }
145 
print(String s, Object... args)146     protected final void print(String s, Object... args) {
147         log.printf(s, args);
148     }
149 
println(String s, Object... args)150     protected final void println(String s, Object... args) {
151         print(s, args);
152         println();
153     }
154 
printBytes(long bytes)155     protected final void printBytes(long bytes) {
156         print(Utils.formatBytes(bytes));
157     }
158 
printTimespan(Duration timespan, String separator)159     protected final void printTimespan(Duration timespan, String separator) {
160         print(Utils.formatTimespan(timespan, separator));
161     }
162 
printPath(SafePath path)163     protected final void printPath(SafePath path) {
164         if (path == null) {
165             print("N/A");
166             return;
167         }
168         try {
169             printPath(SecuritySupport.getAbsolutePath(path).toPath());
170         } catch (IOException ioe) {
171             printPath(path.toPath());
172         }
173     }
174 
printPath(Path path)175     protected final void printPath(Path path) {
176         try {
177             println(path.toAbsolutePath().toString());
178         } catch (SecurityException e) {
179             // fall back on filename
180             println(path.toString());
181         }
182     }
183 
findRecordingById(int id)184     private Recording findRecordingById(int id) throws DCmdException {
185         for (Recording r : getFlightRecorder().getRecordings()) {
186             if (r.getId() == id) {
187                 return r;
188             }
189         }
190         throw new DCmdException("Could not find %d.\n\nUse JFR.check without options to see list of all available recordings.", id);
191     }
192 
findRecordingByName(String name)193     private Recording findRecordingByName(String name) throws DCmdException {
194         for (Recording recording : getFlightRecorder().getRecordings()) {
195             if (name.equals(recording.getName())) {
196                 return recording;
197             }
198         }
199         throw new DCmdException("Could not find %s.\n\nUse JFR.check without options to see list of all available recordings.", name);
200     }
201 }
202