Skip to content

Commit

Permalink
8261441: JFR: Filename expansion
Browse files Browse the repository at this point in the history
Reviewed-by: jbachorik, egahlin
  • Loading branch information
D-D-H authored and y1yang0 committed Aug 6, 2021
1 parent e38e365 commit adb0ae5
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 21 deletions.
3 changes: 3 additions & 0 deletions src/java.base/share/man/java.1
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,9 @@ written when the recording is stopped, for example:
\f[CB]/home/user/recordings/recording.jfr\f[R]
.IP \[bu] 2
\f[CB]c:\\recordings\\recording.jfr\f[R]
.PP
If \f[CB]%p\f[R] and/or \f[CB]%t\f[R] is specified in the filename, it expands to the JVM\[aq]s
PID and the current timestamp, respectively.
.RE
.TP
.B \f[CB]name=\f[R]\f[I]identifier\f[R]
Expand Down
6 changes: 6 additions & 0 deletions src/jdk.jcmd/share/man/jcmd.1
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,8 @@ If no filename is given, a filename is generated from the PID and the
current date.
The filename may also be a directory in which case, the filename is
generated from the PID and the current date in the specified directory.
If \f[CB]%p\f[R] and/or \f[CB]%t\f[R] is specified in the filename, it
expands to the JVM\[aq]s PID and the current timestamp, respectively.
(STRING, no default value)
.IP \[bu] 2
\f[CB]maxage\f[R]: (Optional) Length of time for dumping the flight
Expand Down Expand Up @@ -509,6 +511,8 @@ current date and is placed in the directory where the process was
started.
The filename may also be a directory in which case, the filename is
generated from the PID and the current date in the specified directory.
If \f[CB]%p\f[R] and/or \f[CB]%t\f[R] is specified in the filename, it
expands to the JVM\[aq]s PID and the current timestamp, respectively.
(STRING, no default value)
.IP \[bu] 2
\f[CB]maxage\f[R]: (Optional) Maximum time to keep the recorded data on
Expand Down Expand Up @@ -600,6 +604,8 @@ If no parameters are entered, then no recording is stopped.
.IP \[bu] 2
\f[CB]filename\f[R]: (Optional) Name of the file to which the recording is
written when the recording is stopped.
If \f[CB]%p\f[R] and/or \f[CB]%t\f[R] is specified in the filename, it
expands to the JVM\[aq]s PID and the current timestamp, respectively.
If no path is provided, the data from the recording is discarded.
(STRING, no default value)
.IP \[bu] 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -191,7 +192,7 @@ protected final void print(String s) {
}

protected final void print(String s, Object... args) {
currentLine.append(String.format(s, args));
currentLine.append(args.length > 0 ? String.format(s, args) : s);
}

protected final void println(String s, Object... args) {
Expand Down Expand Up @@ -269,4 +270,41 @@ protected final String exampleDirectory() {
return "/directory/recordings";
}
}

static String expandFilename(String filename) {
if (filename == null || filename.indexOf('%') == -1) {
return filename;
}

String pid = null;
String time = null;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < filename.length(); i++) {
char c = filename.charAt(i);
if (c == '%' && i < filename.length() - 1) {
char nc = filename.charAt(i + 1);
if (nc == '%') { // %% ==> %
sb.append('%');
i++;
} else if (nc == 'p') {
if (pid == null) {
pid = JVM.getJVM().getPid();
}
sb.append(pid);
i++;
} else if (nc == 't') {
if (time == null) {
time = Utils.formatDateTime(LocalDateTime.now());
}
sb.append(time);
i++;
} else {
sb.append('%');
}
} else {
sb.append(c);
}
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ final class DCmdDump extends AbstractDCmd {
public void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
String name = parser.getOption("name");
String filename = parser.getOption("filename");
String filename = expandFilename(parser.getOption("filename"));
Long maxAge = parser.getOption("maxage");
Long maxSize = parser.getOption("maxsize");
String begin = parser.getOption("begin");
Expand Down Expand Up @@ -232,6 +232,10 @@ public String[] printHelp() {
case, the filename is generated from the PID and the current date in
the specified directory. (STRING, no default value)
Note: If a filename is given, '%%p' in the filename will be
replaced by the PID, and '%%t' will be replaced by the time in
'yyyy_MM_dd_HH_mm_ss' format.
maxage (Optional) Length of time for dumping the flight recording data to a
file. (INTEGER followed by 's' for seconds 'm' for minutes or 'h' for
hours, no default value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void execute(ArgumentParser parser) throws DCmdException {
Long delay = parser.getOption("delay");
Long duration = parser.getOption("duration");
Boolean disk = parser.getOption("disk");
String path = parser.getOption("filename");
String path = expandFilename(parser.getOption("filename"));
Long maxAge = parser.getOption("maxage");
Long maxSize = parser.getOption("maxsize");
Long flush = parser.getOption("flush-interval");
Expand Down Expand Up @@ -339,6 +339,10 @@ Virtual Machine (JVM) shuts down. If set to 'true' and no value
generated from the PID and the current date in the specified
directory. (STRING, no default value)
Note: If a filename is given, '%%p' in the filename will be
replaced by the PID, and '%%t' will be replaced by the time in
'yyyy_MM_dd_HH_mm_ss' format.
maxage (Optional) Maximum time to keep the recorded data on disk. This
parameter is valid only when the disk parameter is set to true.
Note 0s means forever. (INTEGER followed by 's' for seconds 'm'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ final class DCmdStop extends AbstractDCmd {
protected void execute(ArgumentParser parser) throws DCmdException {
parser.checkUnknownArguments();
String name = parser.getOption("name");
String filename = parser.getOption("filename");
String filename = expandFilename(parser.getOption("filename"));
try {
Recording recording = findRecording(name);
WriteableUserPath path = PrivateAccess.getInstance().getPlatformRecording(recording).getDestination();
Expand Down Expand Up @@ -82,6 +82,9 @@ public String[] printHelp() {
recording is stopped. If no path is provided, the data from the recording
is discarded. (STRING, no default value)
Note: If a path is given, '%%p' in the path will be replaced by the PID,
and '%%t' will be replaced by the time in 'yyyy_MM_dd_HH_mm_ss' format.
name Name of the recording (STRING, no default value)
Options must be specified using the <key> or <key>=<value> syntax.
Expand Down
13 changes: 13 additions & 0 deletions test/jdk/jdk/jfr/jcmd/JcmdHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import java.util.stream.Collectors;

import jdk.test.lib.Asserts;
Expand Down Expand Up @@ -118,4 +119,16 @@ public static OutputAnalyzer jcmd(String... args) {
public static OutputAnalyzer jcmdCheck(String recordingName, boolean verbose) {
return jcmd("JFR.check", "name=" + recordingName, "verbose=" + verbose);
}

public static String readFilename(OutputAnalyzer output) throws Exception {
Iterator<String> it = output.asLines().iterator();
while (it.hasNext()) {
String line = it.next();
if (line.contains("written to")) {
line = it.next(); // blank line
return it.next();
}
}
throw new Exception("Could not find filename of dumped recording.");
}
}
56 changes: 56 additions & 0 deletions test/jdk/jdk/jfr/jcmd/TestFilenameExpansion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2021, Alibaba Group Holding Limited. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jdk.jfr.jcmd;

import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jdk.test.lib.Asserts;
import jdk.test.lib.jfr.FileHelper;
import jdk.test.lib.process.OutputAnalyzer;

/**
* @test
* @summary The test verifies JFR.start/dump/stop commands
* @key jfr
* @requires vm.hasJFR
* @library /test/lib /test/jdk
* @run main/othervm jdk.jfr.jcmd.TestFilenameExpansion
*/
public class TestFilenameExpansion {

public static void main(String[] args) throws Exception {
String pid = Long.toString(ProcessHandle.current().pid());
String name = "output_%p_%t_%%.jfr";
String pattern = "output_" + pid + "_" + "\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}" + "_%\\.jfr";

JcmdHelper.jcmd("JFR.start name=test");
String filename = JcmdHelper.readFilename(JcmdHelper.jcmd("JFR.dump name=test filename=" + name));
File file = new File(filename);
Asserts.assertTrue(file.exists(), file.getAbsolutePath() + " does not exist");
Asserts.assertTrue(file.isFile(), file.getAbsolutePath() + " is not a file");
Asserts.assertTrue(Pattern.compile(pattern).matcher(filename).find());
}
}
21 changes: 4 additions & 17 deletions test/jdk/jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;

import jdk.jfr.Configuration;
import jdk.jfr.Recording;
Expand Down Expand Up @@ -60,26 +59,26 @@ public static void main(String[] args) throws Exception {

private static void testDumpFilename() throws Exception {
OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump");
verifyFile(readFilename(output), null);
verifyFile(JcmdHelper.readFilename(output), null);
}

private static void testDumpFilename(Recording r) throws Exception {
OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId());
verifyFile(readFilename(output), r.getId());
verifyFile(JcmdHelper.readFilename(output), r.getId());
}

private static void testDumpDiectory() throws Exception {
Path directory = Paths.get(".").toAbsolutePath().normalize();
OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + directory);
String filename = readFilename(output);
String filename = JcmdHelper.readFilename(output);
verifyFile(filename, null);
verifyDirectory(filename, directory);
}

private static void testDumpDiectory(Recording r) throws Exception {
Path directory = Paths.get(".").toAbsolutePath().normalize();
OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId(), "filename=" + directory);
String filename = readFilename(output);
String filename = JcmdHelper.readFilename(output);
verifyFile(filename, r.getId());
verifyDirectory(filename, directory);
}
Expand All @@ -98,16 +97,4 @@ private static void verifyFile(String filename, Long id) throws Exception {
}
FileHelper.verifyRecording(new File(filename));
}

private static String readFilename(OutputAnalyzer output) throws Exception {
Iterator<String> it = output.asLines().iterator();
while (it.hasNext()) {
String line = it.next();
if (line.contains("written to")) {
line = it.next(); // blank line
return it.next();
}
}
throw new Exception("Could not find filename of dumped recording.");
}
}

1 comment on commit adb0ae5

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.