1 /*
2 * Copyright (c) 2015, 2025, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 package jdk.jfr.jcmd;
25
26 import java.io.File;
27 import java.nio.file.Files;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.function.Predicate;
31
32 import jdk.jfr.Event;
33 import jdk.jfr.Recording;
34 import jdk.jfr.consumer.RecordedEvent;
35 import jdk.jfr.consumer.RecordingFile;
36 import jdk.test.lib.JDKToolFinder;
37 import jdk.test.lib.jfr.EventNames;
38 import jdk.test.lib.process.OutputAnalyzer;
39 import jdk.test.lib.process.ProcessTools;
40
41 /**
42 * @test
43 * @requires vm.flagless
44 * @requires vm.hasJFR
45 * @requires !(vm.opt.final.UseCompactObjectHeaders == true | vm.opt.final.UseShenandoahGC == true)
46 * @summary The test verifies JFR.dump command
47 * @library /test/lib /test/jdk
48 * @run main/othervm -XX:FlightRecorderOptions:maxchunksize=1M jdk.jfr.jcmd.TestJcmdDump
49 */
50 public class TestJcmdDump {
51
52 static class StoppedEvent extends Event {
53 }
54 static class RunningEvent extends Event {
55 }
56
57 private static final String[] names = { null, "r1" };
58 private static final boolean booleanValues[] = { true, false };
59 private static final long timeoutMillis = 50000;
60
61 public static void main(String[] args) throws Exception {
62 // Create a stopped recording in the repository to complicate things
63 Recording r = new Recording();
64 r.start();
65 StoppedEvent de = new StoppedEvent();
66 de.commit();
67 r.stop();
68
69 // The implementation of JFR.dump touch code that can't be executed using the
70 // Java API. It is therefore important to try all combinations. The
71 // implementation is non-trivial and depends on the combination
72 for (String name : names) {
73 for (boolean disk : booleanValues) {
74 try (Recording r1 = new Recording(); Recording r2 = new Recording()) {
75 System.out.println();
76 System.out.println();
77 System.out.println("Starting recordings with disk=" + disk);
78 r1.setToDisk(disk);
79 // To complicate things, only enable OldObjectSample for one recording
80 r1.enable(EventNames.OldObjectSample).withoutStackTrace();
81 r1.setName("r1");
82 r2.setToDisk(disk);
83 r2.setName("r2");
84 r1.start();
85 r2.start();
86
87 // Expect no path to GC roots
88 jfrDump(Boolean.FALSE, name, disk, rootCount -> rootCount == 0);
89 // Expect path to GC roots
90 jfrDump(null, name, disk, rootCount -> rootCount == 0);
91 // Expect at least one path to a GC root
92 jfrDump(Boolean.TRUE, name, disk, rootCount -> rootCount > 0);
93 }
94 }
95 }
96 r.close(); // release recording data from the stopped recording
97 }
98
99 private static void jfrDump(Boolean pathToGCRoots, String name, boolean disk, Predicate<Integer> successPredicate) throws Exception {
100 List<Object> leakList = new ArrayList<>();
101 leakList.add(new Object[1000_0000]);
102 System.gc();
103 while (true) {
104 RunningEvent re = new RunningEvent();
105 re.commit();
106 leakList.add(new Object[1000_0000]);
107 leakList.add(new Object[1000_0000]);
108 leakList.add(new Object[1000_0000]);
109 System.gc(); // This will shorten time for object to be emitted.
110 File recording = new File("TestJCMdDump.jfr");
111 List<String> params = buildParameters(pathToGCRoots, name, recording);
112 System.out.println(params);
113 OutputAnalyzer output = ProcessTools.executeProcess(new ProcessBuilder(params));
114 output.reportDiagnosticSummary();
115 JcmdAsserts.assertRecordingDumpedToFile(output, recording);
116 int rootCount = 0;
117 int oldObjectCount = 0;
118 int stoppedEventCount = 0;
119 int runningEventCount = 0;
120 for (RecordedEvent e : RecordingFile.readAllEvents(recording.toPath())) {
121 if (e.getEventType().getName().equals(EventNames.OldObjectSample)) {
122 if (e.getValue("root") != null) {
123 rootCount++;
124 }
125 oldObjectCount++;
126 }
127 if (e.getEventType().getName().equals(StoppedEvent.class.getName())) {
128 stoppedEventCount++;
129 }
130 if (e.getEventType().getName().equals(RunningEvent.class.getName())) {
131 runningEventCount++;
132 }
133 }
134 System.out.println("Name: " + name);
135 System.out.println("Disk: " + disk);
136 System.out.println("Path to GC roots: " + pathToGCRoots);
137 System.out.println("Old Objects: " + oldObjectCount);
138 System.out.println("Root objects: "+ rootCount);
139 System.out.println("Stopped events: "+ stoppedEventCount);
140 System.out.println("Running events: "+ runningEventCount);
141
142 System.out.println();
143 if (runningEventCount == 0) {
144 throw new Exception("Missing event from running recording");
145 }
146 if (name == null && stoppedEventCount == 0) {
147 throw new Exception("Missing event from stopped recording");
148 }
149 if (name != null && stoppedEventCount > 0) {
150 throw new Exception("Stopped event should not be part of dump");
151 }
152 if (oldObjectCount != 0 && successPredicate.test(rootCount)) {
153 return;
154 }
155 System.out.println();
156 System.out.println();
157 System.out.println();
158 System.out.println("************* Retrying! **************");
159 Files.delete(recording.toPath());
160 }
161 }
162
163 private static List<String> buildParameters(Boolean pathToGCRoots, String name, File recording) {
164 List<String> params = new ArrayList<>();
165 params.add(JDKToolFinder.getJDKTool("jcmd"));
166 params.add("-J-Dsun.tools.attach.attachTimeout=" + timeoutMillis);
167 params.add(String.valueOf(ProcessHandle.current().pid()));
168 params.add("JFR.dump");
169 params.add("filename=" + recording.getAbsolutePath());
170 if (pathToGCRoots != null) { // if path-to-gc-roots is omitted, default is used (disabled).
171 params.add("path-to-gc-roots=" + pathToGCRoots);
172 }
173 if (name != null) { // if name is omitted, all recordings will be dumped
174 params.add("name=" + name);
175 }
176 return params;
177 }
178 }