| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
package jdk.jfr.internal.dcmd;  | 
 | 
 | 
 | 
import java.io.FileNotFoundException;  | 
 | 
import java.io.IOException;  | 
 | 
import java.nio.file.Files;  | 
 | 
import java.nio.file.InvalidPathException;  | 
 | 
import java.nio.file.Path;  | 
 | 
import java.nio.file.Paths;  | 
 | 
import java.text.ParseException;  | 
 | 
import java.time.Duration;  | 
 | 
import java.util.Arrays;  | 
 | 
import java.util.HashMap;  | 
 | 
import java.util.Map;  | 
 | 
 | 
 | 
import jdk.jfr.FlightRecorder;  | 
 | 
import jdk.jfr.Recording;  | 
 | 
import jdk.jfr.internal.JVM;  | 
 | 
import jdk.jfr.internal.LogLevel;  | 
 | 
import jdk.jfr.internal.LogTag;  | 
 | 
import jdk.jfr.internal.Logger;  | 
 | 
import jdk.jfr.internal.OldObjectSample;  | 
 | 
import jdk.jfr.internal.PrivateAccess;  | 
 | 
import jdk.jfr.internal.SecuritySupport.SafePath;  | 
 | 
import jdk.jfr.internal.Type;  | 
 | 
import jdk.jfr.internal.jfc.JFC;  | 
 | 
 | 
 | 
/**  | 
 | 
 * JFR.start  | 
 | 
 *  | 
 | 
 */  | 
 | 
 | 
 | 
final class DCmdStart extends AbstractDCmd { | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    @SuppressWarnings("resource") | 
 | 
    public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException { | 
 | 
        if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) { | 
 | 
            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name +  | 
 | 
                    ", settings=" + (settings != null ? Arrays.asList(settings) : "(none)") +  | 
 | 
                    ", delay=" + delay +  | 
 | 
                    ", duration=" + duration +  | 
 | 
                    ", disk=" + disk+  | 
 | 
                    ", filename=" + path +  | 
 | 
                    ", maxage=" + maxAge +  | 
 | 
                    ", maxsize=" + maxSize +  | 
 | 
                    ", dumponexit =" + dumpOnExit +  | 
 | 
                    ", path-to-gc-roots=" + pathToGcRoots);  | 
 | 
        }  | 
 | 
        if (name != null) { | 
 | 
            try { | 
 | 
                Integer.parseInt(name);  | 
 | 
                throw new DCmdException("Name of recording can't be numeric"); | 
 | 
            } catch (NumberFormatException nfe) { | 
 | 
                // ok, can't be mixed up with name  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (duration == null && Boolean.FALSE.equals(dumpOnExit) && path != null) { | 
 | 
            throw new DCmdException("Filename can only be set for a time bound recording or if dumponexit=true. Set duration/dumponexit or omit filename."); | 
 | 
        }  | 
 | 
        if (settings.length == 1 && settings[0].length() == 0) { | 
 | 
            throw new DCmdException("No settings specified. Use settings=none to start without any settings"); | 
 | 
        }  | 
 | 
        Map<String, String> s = new HashMap<>();  | 
 | 
        for (String configName : settings) { | 
 | 
            try { | 
 | 
                s.putAll(JFC.createKnown(configName).getSettings());  | 
 | 
            } catch(FileNotFoundException e) { | 
 | 
                throw new DCmdException("Could not find settings file'" + configName + "'", e); | 
 | 
            } catch (IOException | ParseException e) { | 
 | 
                throw new DCmdException("Could not parse settings file '" + settings[0] + "'", e); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        OldObjectSample.updateSettingPathToGcRoots(s, pathToGcRoots);  | 
 | 
 | 
 | 
        if (duration != null) { | 
 | 
            if (duration < 1000L * 1000L * 1000L) { | 
 | 
                  | 
 | 
                throw new DCmdException("Could not start recording, duration must be at least 1 second."); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (delay != null) { | 
 | 
            if (delay < 1000L * 1000L * 1000) { | 
 | 
                  | 
 | 
                throw new DCmdException("Could not start recording, delay must be at least 1 second."); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (!FlightRecorder.isInitialized() && delay == null) { | 
 | 
            initializeWithForcedInstrumentation(s);  | 
 | 
        }  | 
 | 
 | 
 | 
        Recording recording = new Recording();  | 
 | 
        if (name != null) { | 
 | 
            recording.setName(name);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (disk != null) { | 
 | 
            recording.setToDisk(disk.booleanValue());  | 
 | 
        }  | 
 | 
        recording.setSettings(s);  | 
 | 
        SafePath safePath = null;  | 
 | 
 | 
 | 
        if (path != null) { | 
 | 
            try { | 
 | 
                if (dumpOnExit == null) { | 
 | 
                      | 
 | 
                    dumpOnExit = Boolean.TRUE;  | 
 | 
                }  | 
 | 
                Path p = Paths.get(path);  | 
 | 
                if (Files.isDirectory(p) && Boolean.TRUE.equals(dumpOnExit)) { | 
 | 
                    // Decide destination filename at dump time  | 
 | 
                    // Purposely avoid generating filename in Recording#setDestination due to  | 
 | 
                      | 
 | 
                    PrivateAccess.getInstance().getPlatformRecording(recording).setDumpOnExitDirectory(new SafePath(p));  | 
 | 
                } else { | 
 | 
                    safePath = resolvePath(recording, path);  | 
 | 
                    recording.setDestination(safePath.toPath());  | 
 | 
                }  | 
 | 
            } catch (IOException | InvalidPathException e) { | 
 | 
                recording.close();  | 
 | 
                throw new DCmdException("Could not start recording, not able to write to file %s. %s ", path, e.getMessage()); | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (maxAge != null) { | 
 | 
            recording.setMaxAge(Duration.ofNanos(maxAge));  | 
 | 
        }  | 
 | 
 | 
 | 
        if (maxSize != null) { | 
 | 
            recording.setMaxSize(maxSize);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (duration != null) { | 
 | 
            recording.setDuration(Duration.ofNanos(duration));  | 
 | 
        }  | 
 | 
 | 
 | 
        if (dumpOnExit != null) { | 
 | 
            recording.setDumpOnExit(dumpOnExit);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (delay != null) { | 
 | 
            Duration dDelay = Duration.ofNanos(delay);  | 
 | 
            recording.scheduleStart(dDelay);  | 
 | 
            print("Recording " + recording.getId() + " scheduled to start in "); | 
 | 
            printTimespan(dDelay, " ");  | 
 | 
            print("."); | 
 | 
        } else { | 
 | 
            recording.start();  | 
 | 
            print("Started recording " + recording.getId() + "."); | 
 | 
        }  | 
 | 
 | 
 | 
        if (recording.isToDisk() && duration == null && maxAge == null && maxSize == null) { | 
 | 
            print(" No limit specified, using maxsize=250MB as default."); | 
 | 
            recording.setMaxSize(250*1024L*1024L);  | 
 | 
        }  | 
 | 
 | 
 | 
        if (safePath != null && duration != null) { | 
 | 
            println(" The result will be written to:"); | 
 | 
            println();  | 
 | 
            printPath(safePath);  | 
 | 
        } else { | 
 | 
            println();  | 
 | 
            println();  | 
 | 
            String cmd = duration == null ? "dump" : "stop";  | 
 | 
            String fileOption = path == null ? "filename=FILEPATH " : "";  | 
 | 
            String recordingspecifier = "name=" + recording.getId();  | 
 | 
              | 
 | 
            if (name != null) { | 
 | 
                recordingspecifier = "name=" + quoteIfNeeded(name);  | 
 | 
            }  | 
 | 
            print("Use jcmd " + getPid() + " JFR." + cmd + " " + recordingspecifier + " " + fileOption + "to copy recording data to file."); | 
 | 
            println();  | 
 | 
        }  | 
 | 
        return getResult();  | 
 | 
    }  | 
 | 
 | 
 | 
 | 
 | 
      | 
 | 
    private void initializeWithForcedInstrumentation(Map<String, String> settings) { | 
 | 
        if (!hasJDKEvents(settings)) { | 
 | 
            return;  | 
 | 
        }  | 
 | 
        JVM jvm = JVM.getJVM();  | 
 | 
        try { | 
 | 
           jvm.setForceInstrumentation(true);  | 
 | 
            FlightRecorder.getFlightRecorder();  | 
 | 
        } finally { | 
 | 
           jvm.setForceInstrumentation(false);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    private boolean hasJDKEvents(Map<String, String> settings) { | 
 | 
        String[] eventNames = new String[7];  | 
 | 
        eventNames[0] = "FileRead";  | 
 | 
        eventNames[1] = "FileWrite";  | 
 | 
        eventNames[2] = "SocketRead";  | 
 | 
        eventNames[3] = "SocketWrite";  | 
 | 
        eventNames[4] = "JavaErrorThrow";  | 
 | 
        eventNames[5] = "JavaExceptionThrow";  | 
 | 
        eventNames[6] = "FileForce";  | 
 | 
        for (String eventName : eventNames) { | 
 | 
            if ("true".equals(settings.get(Type.EVENT_NAME_PREFIX + eventName + "#enabled"))) { | 
 | 
                return true;  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return false;  | 
 | 
    }  | 
 | 
}  |