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 } 128 PlatformRecorder recorder = PrivateAccess.getInstance().getPlatformRecorder(); 129 synchronized (recorder) { 130 dump(recorder, recording, name, filename, maxSize, pathToGcRoots, beginTime, endTime); 131 } 132 return getResult(); 133 } 134 135 public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException { 136 try (PlatformRecording r = newSnapShot(recorder, recording, pathToGcRoots)) { 137 r.filter(beginTime, endTime, maxSize); 138 if (r.getChunks().isEmpty()) { 139 throw new DCmdException("Dump failed. No data found in the specified interval."); 140 } 141 SafePath dumpFile = resolvePath(recording, filename); 142 143 // Needed for JVM 144 Utils.touch(dumpFile.toPath()); 145 r.dumpStopped(new WriteableUserPath(dumpFile.toPath())); 146 reportOperationComplete("Dumped", name, dumpFile); 147 } catch (IOException | InvalidPathException e) { 148 throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage()); 149 } 150 } 151 152 private Instant parseTime(String time, String parameter) throws DCmdException { 153 if (time == null) { 154 return null; 155 } 156 try { 157 return Instant.parse(time); 158 } catch (DateTimeParseException dtp) { 159 // fall through 160 } 161 try { 162 LocalDateTime ldt = LocalDateTime.parse(time); 163 return ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant(); 164 } catch (DateTimeParseException dtp) { 165 // fall through 166 } 167 try { 168 LocalTime lt = LocalTime.parse(time); 169 LocalDate ld = LocalDate.now(); 170 Instant instant = ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); 171 Instant now = Instant.now(); 172 if (instant.isAfter(now) && !instant.isBefore(now.plusSeconds(3600))) { 173 // User must have meant previous day 174 ld = ld.minusDays(1); 175 } 176 return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); 177 } catch (DateTimeParseException dtp) { 178 // fall through 179 } 180 181 if (time.startsWith("-")) { 182 try { 183 long durationNanos = Utils.parseTimespan(time.substring(1)); 184 Duration duration = Duration.ofNanos(durationNanos); 185 return Instant.now().minus(duration); 186 } catch (NumberFormatException nfe) { 187 // fall through 188 } 189 } 190 throw new DCmdException("Dump failed, not a valid %s time.", parameter); 191 } 192 193 private PlatformRecording newSnapShot(PlatformRecorder recorder, Recording recording, Boolean pathToGcRoots) throws DCmdException, IOException { 194 if (recording == null) { 195 // Operate on all recordings 196 PlatformRecording snapshot = recorder.newTemporaryRecording(); 197 recorder.fillWithRecordedData(snapshot, pathToGcRoots); 198 return snapshot; 199 } 200 201 PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); 202 return pr.newSnapshotClone(PlatformRecording.REASON_DUMPED_BY_USER, pathToGcRoots); 203 } 204 205 }