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  * @summary The test verifies JFR.dump command
 44  * @requires vm.flagless
 45  * @requires vm.hasJFR
 46  * @library /test/lib /test/jdk
 47  * @run main/othervm -XX:FlightRecorderOptions:maxchunksize=1M jdk.jfr.jcmd.TestJcmdDump
 48  */
 49 public class TestJcmdDump {
 50 
 51     static class StoppedEvent extends Event {
 52     }
 53     static class RunningEvent extends Event {
 54     }
 55 
 56     private static final String[] names = { null, "r1" };
 57     private static final boolean booleanValues[] = { true, false };
 58     private static final long timeoutMillis = 50000;
 59 
 60     public static void main(String[] args) throws Exception {
 61         // Create a stopped recording in the repository to complicate things
 62         Recording r = new Recording();
 63         r.start();
 64         StoppedEvent de = new StoppedEvent();
 65         de.commit();
 66         r.stop();
 67 
 68         // The implementation of JFR.dump touch code that can't be executed using the
 69         // Java API. It is therefore important to try all combinations. The
 70         // implementation is non-trivial and depends on the combination
 71         for (String name : names) {
 72             for (boolean disk : booleanValues) {
 73                 try (Recording r1 = new Recording(); Recording r2 = new Recording()) {
 74                     System.out.println();
 75                     System.out.println();
 76                     System.out.println("Starting recordings with disk=" + disk);
 77                     r1.setToDisk(disk);
 78                     // To complicate things, only enable OldObjectSample for one recording
 79                     r1.enable(EventNames.OldObjectSample).withoutStackTrace();
 80                     r1.setName("r1");
 81                     r2.setToDisk(disk);
 82                     r2.setName("r2");
 83                     r1.start();
 84                     r2.start();
 85 
 86                     // Expect no path to GC roots
 87                     jfrDump(Boolean.FALSE, name, disk, rootCount -> rootCount == 0);
 88                     // Expect path to GC roots
 89                     jfrDump(null, name, disk, rootCount -> rootCount == 0);
 90                     // Expect at least one path to a GC root
 91                     jfrDump(Boolean.TRUE, name, disk, rootCount -> rootCount > 0);
 92                 }
 93             }
 94         }
 95         r.close(); // release recording data from the stopped recording
 96     }
 97 
 98     private static void jfrDump(Boolean pathToGCRoots, String name, boolean disk, Predicate<Integer> successPredicate) throws Exception {
 99         List<Object> leakList = new ArrayList<>();
100         leakList.add(new Object[1000_0000]);
101         System.gc();
102         while (true) {
103             RunningEvent re = new RunningEvent();
104             re.commit();
105             leakList.add(new Object[1000_0000]);
106             leakList.add(new Object[1000_0000]);
107             leakList.add(new Object[1000_0000]);
108             System.gc(); // This will shorten time for object to be emitted.
109             File recording = new File("TestJCMdDump.jfr");
110             List<String> params = buildParameters(pathToGCRoots, name, recording);
111             System.out.println(params);
112             OutputAnalyzer output = ProcessTools.executeProcess(new ProcessBuilder(params));
113             output.reportDiagnosticSummary();
114             JcmdAsserts.assertRecordingDumpedToFile(output, recording);
115             int rootCount = 0;
116             int oldObjectCount = 0;
117             int stoppedEventCount = 0;
118             int runningEventCount = 0;
119             for (RecordedEvent e : RecordingFile.readAllEvents(recording.toPath())) {
120                 if (e.getEventType().getName().equals(EventNames.OldObjectSample)) {
121                     if (e.getValue("root") != null) {
122                         rootCount++;
123                     }
124                     oldObjectCount++;
125                 }
126                 if (e.getEventType().getName().equals(StoppedEvent.class.getName())) {
127                     stoppedEventCount++;
128                 }
129                 if (e.getEventType().getName().equals(RunningEvent.class.getName())) {
130                     runningEventCount++;
131                 }
132             }
133             System.out.println("Name: " + name);
134             System.out.println("Disk: " + disk);
135             System.out.println("Path to GC roots: " + pathToGCRoots);
136             System.out.println("Old Objects: " + oldObjectCount);
137             System.out.println("Root objects: "+ rootCount);
138             System.out.println("Stopped events: "+ stoppedEventCount);
139             System.out.println("Running events: "+ runningEventCount);
140 
141             System.out.println();
142             if (runningEventCount == 0) {
143                 throw new Exception("Missing event from running recording");
144             }
145             if (name == null && stoppedEventCount == 0) {
146                 throw new Exception("Missing event from stopped recording");
147             }
148             if (name != null && stoppedEventCount > 0) {
149                 throw new Exception("Stopped event should not be part of dump");
150             }
151             if (oldObjectCount != 0 && successPredicate.test(rootCount)) {
152                 return;
153             }
154             System.out.println();
155             System.out.println();
156             System.out.println();
157             System.out.println("************* Retrying! **************");
158             Files.delete(recording.toPath());
159         }
160     }
161 
162     private static List<String> buildParameters(Boolean pathToGCRoots, String name, File recording) {
163         List<String> params = new ArrayList<>();
164         params.add(JDKToolFinder.getJDKTool("jcmd"));
165         params.add("-J-Dsun.tools.attach.attachTimeout=" + timeoutMillis);
166         params.add(String.valueOf(ProcessHandle.current().pid()));
167         params.add("JFR.dump");
168         params.add("filename=" + recording.getAbsolutePath());
169         if (pathToGCRoots != null) { // if path-to-gc-roots is omitted, default is used (disabled).
170             params.add("path-to-gc-roots=" + pathToGCRoots);
171         }
172         if (name != null) { // if name is omitted, all recordings will be dumped
173             params.add("name=" + name);
174         }
175         return params;
176     }
177 }