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.io.IOException;
 28 import java.util.ArrayList;
 29 import java.util.Collections;
 30 import java.util.HashMap;
 31 import java.util.List;
 32 import java.util.Map;
 33 
 34 import jdk.jfr.Enabled;
 35 import jdk.jfr.Recording;
 36 import jdk.jfr.consumer.RecordedEvent;
 37 import jdk.jfr.consumer.RecordedObject;
 38 import jdk.jfr.consumer.RecordingFile;
 39 import jdk.jfr.internal.test.WhiteBox;
 40 import jdk.test.lib.jfr.EventNames;
 41 
 42 /**
 43  * @test
 44  * @summary Start a recording with or without path-to-gc-roots
 45  * @requires vm.hasJFR
 46  * @modules jdk.jfr/jdk.jfr.internal.test
 47  * @library /test/lib /test/jdk
 48  * @requires vm.flagless
 49  *
 50  * @run main/othervm -XX:TLABSize=2k jdk.jfr.jcmd.TestJcmdDumpPathToGCRoots
 51  */
 52 public class TestJcmdDumpPathToGCRoots {
 53 
 54     private static final int OBJECT_COUNT = 100_000;
 55     public static List<Object[]> leak = new ArrayList<>(OBJECT_COUNT);
 56 
 57     public static void main(String[] args) throws Exception {
 58         WhiteBox.setWriteAllObjectSamples(true);
 59 
 60         String settingName = EventNames.OldObjectSample + "#" + "cutoff";
 61 
 62         // dump parameter trumps previous setting
 63         testDump("path-to-gc-roots=true", Collections.singletonMap(settingName, "infinity"), true);
 64         testDump("path-to-gc-roots=true", Collections.singletonMap(settingName, "0 ns"), true);
 65         testDump("path-to-gc-roots=true", Collections.emptyMap(), true);
 66 
 67         testDump("path-to-gc-roots=false", Collections.singletonMap(settingName, "infinity"), false);
 68         testDump("path-to-gc-roots=false", Collections.singletonMap(settingName, "0 ns"), false);
 69         testDump("path-to-gc-roots=false", Collections.emptyMap(), false);
 70 
 71         testDump("", Collections.singletonMap(settingName, "infinity"), true);
 72         testDump("", Collections.singletonMap(settingName, "0 ns"), false);
 73         testDump("", Collections.emptyMap(), false);
 74     }
 75 
 76     private static void testDump(String pathToGcRoots, Map<String, String> settings, boolean expectedChains) throws Exception {
 77         while (true) {
 78             try (Recording r = new Recording()) {
 79                 Map<String, String> p = new HashMap<>(settings);
 80                 p.put(EventNames.OldObjectSample + "#" + Enabled.NAME, "true");
 81                 r.setName("dodo");
 82                 r.setSettings(p);
 83                 r.setToDisk(true);
 84                 r.start();
 85                 clearLeak();
 86                 System.out.println("Recording id: " + r.getId());
 87                 System.out.println("Settings: " + settings.toString());
 88                 System.out.println("Command: JFR.dump " + pathToGcRoots);
 89                 System.out.println("Chains expected: " + expectedChains);
 90                 buildLeak();
 91                 System.gc();
 92                 System.gc();
 93                 File recording = new File("TestJcmdDumpPathToGCRoots" + r.getId() + ".jfr");
 94                 recording.delete();
 95                 JcmdHelper.jcmd("JFR.dump", "name=dodo", pathToGcRoots, "filename=" + recording.getAbsolutePath());
 96                 r.setSettings(Collections.emptyMap());
 97                 List<RecordedEvent> events = RecordingFile.readAllEvents(recording.toPath());
 98                 if (events.isEmpty()) {
 99                     System.out.println("No events found in recording. Retrying.");
100                     continue;
101                 }
102                 boolean chains = hasChains(events);
103                 if (expectedChains && !chains) {
104                     System.out.println(events);
105                     System.out.println("Expected chains but found none. Retrying.");
106                     continue;
107                 }
108                 if (!expectedChains && chains) {
109                     System.out.println(events);
110                     System.out.println("Didn't expect chains but found some. Retrying.");
111                     continue;
112                 }
113                 return; // Success
114             }
115         }
116     }
117 
118     private static void clearLeak() {
119       leak.clear();
120       System.gc();
121     }
122 
123     private static boolean hasChains(List<RecordedEvent> events) throws IOException {
124         for (RecordedEvent e : events) {
125             RecordedObject ro = e.getValue("object");
126             if (ro.getValue("referrer") != null) {
127                 return true;
128             }
129         }
130         return false;
131     }
132 
133     private static void buildLeak() {
134         for (int i = 0; i < OBJECT_COUNT;i ++) {
135             leak.add(new Object[0]);
136         }
137     }
138 }