1 /* 2 * Copyright (c) 2018, 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.event.oldobject; 27 28 import java.lang.reflect.Array; 29 import java.util.ArrayList; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33 import java.util.concurrent.Callable; 34 35 import jdk.jfr.Recording; 36 import jdk.jfr.consumer.RecordedEvent; 37 import jdk.jfr.consumer.RecordedObject; 38 import jdk.jfr.internal.test.WhiteBox; 39 import jdk.testlibrary.jfr.EventNames; 40 import jdk.testlibrary.jfr.Events; 41 import jdk.testlibrary.jfr.OldObjects; 42 import jdk.testlibrary.jfr.oldobject.TestClassLoader; 43 44 /* 45 * @test 46 * @key jfr 47 * @requires vm.gc == "null" 48 * @library /lib/testlibrary 49 * @modules jdk.jfr/jdk.jfr.internal.test 50 * @run main/othervm -XX:TLABSize=2k -XX:FlightRecorderOptions=old-object-queue-size=10240 jdk.jfr.event.oldobject.TestObjectDescription 51 */ 52 public class TestObjectDescription { 53 54 private static final int OBJECT_DESCRIPTION_MAX_SIZE = 100; 55 private static final String CLASS_NAME = TestClassLoader.class.getName() + "$TestClass"; 56 public static List<?> leaks; 57 58 public final static class MyThreadGroup extends ThreadGroup { 59 public final static String NAME = "My Thread Group"; 60 61 public MyThreadGroup(String name) { 62 super(name); 63 } 64 65 // Allocate array to trigger sampling code path for interpreter / c1 66 byte[] bytes = new byte[10]; 67 } 68 69 public final static class MyThread extends Thread { 70 public final static String NAME = "My Thread"; 71 72 public MyThread() { 73 super(NAME); 74 } 75 76 // Allocate array to trigger sampling code path for interpreter / c1 77 byte[] bytes = new byte[10]; 78 } 79 80 public static void main(String[] args) throws Exception { 81 WhiteBox.setWriteAllObjectSamples(true); 82 83 testThreadGroupName(); 84 testThreadName(); 85 testClassName(); 86 testSize(); 87 testEllipsis(); 88 } 89 90 private static void testThreadName() throws Exception { 91 asseertObjectDescription(() -> { 92 List<MyThread> threads = new ArrayList<>(OldObjects.MIN_SIZE); 93 for (int i = 0; i < OldObjects.MIN_SIZE; i++) { 94 threads.add(new MyThread()); 95 } 96 return threads; 97 }, "Thread Name: " + MyThread.NAME); 98 } 99 100 private static void testThreadGroupName() throws Exception { 101 asseertObjectDescription(() -> { 102 List<MyThreadGroup> groups = new ArrayList<>(OldObjects.MIN_SIZE); 103 for (int i = 0; i < OldObjects.MIN_SIZE; i++) { 104 groups.add(new MyThreadGroup("My Thread Group")); 105 } 106 return groups; 107 }, "Thread Group: " + "My Thread Group"); 108 } 109 110 private static void testClassName() throws Exception { 111 asseertObjectDescription(() -> { 112 TestClassLoader testClassLoader = new TestClassLoader(); 113 List<Object> classObjects = new ArrayList<>(OldObjects.MIN_SIZE); 114 for (Class<?> clazz : testClassLoader.loadClasses(OldObjects.MIN_SIZE / 20)) { 115 // Allocate array to trigger sampling code path for interpreter / c1 116 for (int i = 0; i < 20; i++) { 117 Object classArray = Array.newInstance(clazz, 20); 118 Array.set(classArray, i, clazz.newInstance()); 119 classObjects.add(classArray); 120 } 121 } 122 return classObjects; 123 }, "Class Name: " + CLASS_NAME); 124 } 125 126 private static void testSize() throws Exception { 127 asseertObjectDescription(() -> { 128 List<Object> arrayLists = new ArrayList<>(OldObjects.MIN_SIZE); 129 for (int i = 0; i < OldObjects.MIN_SIZE; i++) { 130 List<Object> arrayList = new ArrayList<>(); 131 arrayList.add(new Object()); 132 arrayList.add(new Object()); 133 arrayList.add(new Object()); 134 arrayLists.add(arrayList); 135 } 136 return arrayLists; 137 }, "Size: 3"); 138 } 139 140 private static void testEllipsis() throws Exception { 141 asseertObjectDescription(() -> { 142 StringBuilder sb = new StringBuilder(); 143 for (int i = 0; i < 2 * OBJECT_DESCRIPTION_MAX_SIZE; i++) { 144 sb.append("x"); 145 } 146 String threadName = sb.toString(); 147 List<Thread> threads = new ArrayList<>(); 148 for (int i = 0; i < OldObjects.MIN_SIZE; i++) { 149 threads.add(new Thread(threadName)); 150 } 151 return threads; 152 }, "xxx..."); 153 } 154 155 private static void asseertObjectDescription(Callable<List<?>> callable, String text) throws Exception { 156 int iteration = 1; 157 while (true) { 158 try (Recording recording = new Recording()) { 159 System.out.println("Iteration: " + iteration); 160 recording.enable(EventNames.OldObjectSample).withoutStackTrace().with("cutoff", "infinity"); 161 recording.start(); 162 leaks = null; 163 System.gc(); 164 leaks = callable.call(); 165 166 recording.stop(); 167 168 List<RecordedEvent> events = Events.fromRecording(recording); 169 Set<String> objectDescriptions = extractObjectDecriptions(events); 170 for (String s : objectDescriptions) { 171 if (s.contains(text)) { 172 printDescriptions(objectDescriptions); 173 return; 174 } 175 } 176 System.out.println("Could not find object description containing text \"" + text + "\""); 177 printDescriptions(objectDescriptions); 178 System.out.println(); 179 iteration++; 180 } 181 } 182 } 183 184 private static void printDescriptions(Set<String> objectDescriptions) { 185 System.out.println("Found descriptions:"); 186 for (String t : objectDescriptions) { 187 System.out.println(t); 188 } 189 } 190 191 private static Set<String> extractObjectDecriptions(List<RecordedEvent> events) { 192 Set<String> objectDescriptions = new HashSet<>(); 193 for (RecordedEvent e : events) { 194 objectDescriptions.addAll(extractObjectDescriptions(e.getValue("object"))); 195 } 196 return objectDescriptions; 197 } 198 199 private static Set<String> extractObjectDescriptions(RecordedObject o) { 200 Set<Long> visited = new HashSet<>(); 201 Set<String> descriptions = new HashSet<>(); 202 while (o != null) { 203 Long memoryAddress = o.getValue("address"); 204 if (visited.contains(memoryAddress)) { 205 return descriptions; 206 } 207 visited.add(memoryAddress); 208 String od = o.getValue("description"); 209 if (od != null) { 210 descriptions.add(od); 211 } 212 RecordedObject referrer = o.getValue("referrer"); 213 o = referrer != null ? referrer.getValue("object") : null; 214 } 215 return descriptions; 216 } 217 }