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 }