1 /* 2 * Copyright (c) 2012, 2019, 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.InvalidPathException; 29 import java.time.Duration; 30 import java.time.Instant; 31 import java.time.LocalDate; 32 import java.time.LocalDateTime; 33 import java.time.LocalTime; 34 import java.time.ZoneId; 35 import java.time.ZonedDateTime; 36 import java.time.format.DateTimeParseException; 37 38 import jdk.jfr.FlightRecorder; 39 import jdk.jfr.Recording; 40 import jdk.jfr.internal.LogLevel; 41 import jdk.jfr.internal.LogTag; 42 import jdk.jfr.internal.Logger; 43 import jdk.jfr.internal.PlatformRecorder; 44 import jdk.jfr.internal.PlatformRecording; 45 import jdk.jfr.internal.PrivateAccess; 46 import jdk.jfr.internal.SecuritySupport.SafePath; 47 import jdk.jfr.internal.Utils; 48 import jdk.jfr.internal.WriteableUserPath; 49 50 /** 51 * JFR.dump 52 * 53 */ 54 // Instantiated by native 55 final class DCmdDump extends AbstractDCmd { 56 /** 57 * Execute JFR.dump. 58 * 59 * @param name name or id of the recording to dump, or <code>null</code> to dump everything 60 * 61 * @param filename file path where recording should be written, not null 62 * @param maxAge how far back in time to dump, may be null 63 * @param maxSize how far back in size to dump data from, may be null 64 * @param begin point in time to dump data from, may be null 65 * @param end point in time to dump data to, may be null 66 * @param pathToGcRoots if Java heap should be swept for reference chains 67 * 68 * @return result output 69 * 70 * @throws DCmdException if the dump could not be completed 71 */ 72 public String execute(String name, String filename, Long maxAge, Long maxSize, String begin, String end, Boolean pathToGcRoots) throws DCmdException { 73 if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) { 74 Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, 75 "Executing DCmdDump: name=" + name + 76 ", filename=" + filename + 77 ", maxage=" + maxAge + 78 ", maxsize=" + maxSize + 79 ", begin=" + begin + 80 ", end" + end + 81 ", path-to-gc-roots=" + pathToGcRoots); 82 } 83 84 if (FlightRecorder.getFlightRecorder().getRecordings().isEmpty()) { 85 throw new DCmdException("No recordings to dump from. Use JFR.start to start a recording."); 86 } 87 88 if (maxAge != null) { 89 if (end != null || begin != null) { 90 throw new DCmdException("Dump failed, maxage can't be combined with begin or end."); 91 } 92 93 if (maxAge < 0) { 94 throw new DCmdException("Dump failed, maxage can't be negative."); 95 } 96 if (maxAge == 0) { 97 maxAge = Long.MAX_VALUE / 2; // a high value that won't overflow 98 } 99 } 100 101 if (maxSize!= null) { 102 if (maxSize < 0) { 103 throw new DCmdException("Dump failed, maxsize can't be negative."); 104 } 105 if (maxSize == 0) { 106 maxSize = Long.MAX_VALUE / 2; // a high value that won't overflow 107 } 108 } 109 110 Instant beginTime = parseTime(begin, "begin"); 111 Instant endTime = parseTime(end, "end"); 112 113 if (beginTime != null && endTime != null) { 114 if (endTime.isBefore(beginTime)) { 115 throw new DCmdException("Dump failed, begin must preceed end."); 116 } 117 } 118 119 Duration duration = null; 120 if (maxAge != null) { 121 duration = Duration.ofNanos(maxAge); 122 beginTime = Instant.now().minus(duration); 123 } 124 Recording recording = null; 125 if (name != null) { 126 recording = findRecording(name); 127 var dest = recording.getDestination(); 128 if ((filename == null) && (dest != null)) { 129 filename = dest.toString(); 130 } 131 } 132 PlatformRecorder recorder = PrivateAccess.getInstance().getPlatformRecorder(); 133 synchronized (recorder) { 134 dump(recorder, recording, name, filename, maxSize, pathToGcRoots, beginTime, endTime); 135 } 136 return getResult(); 137 } 138 139 public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException { 140 try (PlatformRecording r = newSnapShot(recorder, recording, pathToGcRoots)) { 141 r.filter(beginTime, endTime, maxSize); 142 if (r.getChunks().isEmpty()) { 143 throw new DCmdException("Dump failed. No data found in the specified interval."); 144 } 145 SafePath dumpFile = resolvePath(recording, filename); 146 147 // Needed for JVM 148 Utils.touch(dumpFile.toPath()); 149 r.dumpStopped(new WriteableUserPath(dumpFile.toPath())); 150 reportOperationComplete("Dumped", name, dumpFile); 151 } catch (IOException | InvalidPathException e) { 152 throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage()); 153 } 154 } 155 156 private Instant parseTime(String time, String parameter) throws DCmdException { 157 if (time == null) { 158 return null; 159 } 160 try { 161 return Instant.parse(time); 162 } catch (DateTimeParseException dtp) { 163 // fall through 164 } 165 try { 166 LocalDateTime ldt = LocalDateTime.parse(time); 167 return ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant(); 168 } catch (DateTimeParseException dtp) { 169 // fall through 170 } 171 try { 172 LocalTime lt = LocalTime.parse(time); 173 LocalDate ld = LocalDate.now(); 174 Instant instant = ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); 175 Instant now = Instant.now(); 176 if (instant.isAfter(now) && !instant.isBefore(now.plusSeconds(3600))) { 177 // User must have meant previous day 178 ld = ld.minusDays(1); 179 } 180 return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); 181 } catch (DateTimeParseException dtp) { 182 // fall through 183 } 184 185 if (time.startsWith("-")) { 186 try { 187 long durationNanos = Utils.parseTimespan(time.substring(1)); 188 Duration duration = Duration.ofNanos(durationNanos); 189 return Instant.now().minus(duration); 190 } catch (NumberFormatException nfe) { 191 // fall through 192 } 193 } 194 throw new DCmdException("Dump failed, not a valid %s time.", parameter); 195 } 196 197 private PlatformRecording newSnapShot(PlatformRecorder recorder, Recording recording, Boolean pathToGcRoots) throws DCmdException, IOException { 198 if (recording == null) { 199 // Operate on all recordings 200 PlatformRecording snapshot = recorder.newTemporaryRecording(); 201 recorder.fillWithRecordedData(snapshot, pathToGcRoots); 202 return snapshot; 203 } 204 205 PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); 206 return pr.newSnapshotClone("Dumped by user", pathToGcRoots); 207 } 208 209 }