1 /*
2  * Copyright (c) 2015, 2018, 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 package jdk.jfr.event.oldobject;
26 
27 import java.io.IOException;
28 import java.util.List;
29 import java.util.function.Predicate;
30 
31 import jdk.jfr.Recording;
32 import jdk.jfr.consumer.RecordedClass;
33 import jdk.jfr.consumer.RecordedEvent;
34 import jdk.jfr.consumer.RecordedFrame;
35 import jdk.jfr.consumer.RecordedMethod;
36 import jdk.jfr.consumer.RecordedObject;
37 import jdk.jfr.consumer.RecordedStackTrace;
38 import jdk.test.lib.jfr.Events;
39 
40 /**
41  * Utility class to perform Old Object provocation/detection and
42  * stack trace/object verification for the Old Object Sample JFR event
43  */
44 final public class OldObjects {
45 
46     public static final int MIN_SIZE = 99901; // prime number
47     public final static int LEAK_CONTEXT = 100; // length of chain assiociated with the object sample
48     public final static int ROOT_CONTEXT = 100; // length of chain assoicated with the root
49     public final static int MAX_CHAIN_LENGTH = LEAK_CONTEXT + ROOT_CONTEXT; // the VM should not construct chains longer than this
50 
getFrames(String expectedFrame)51     private static String[] getFrames(String expectedFrame) {
52         if (expectedFrame != null) {
53             return new String[] { expectedFrame };
54         } else {
55             return null;
56         }
57     }
58 
59     /**
60     *
61     * @param r
62     *            A recording
63     * @param expectedFrame
64     *            A frame that must be found on the stack. Null if no check is required.
65     * @param fieldType
66     *            The object type (of the field). Null if no check is required.
67     * @param fieldName
68     *            The field name. Null if no check is required.
69     * @param referrerType
70     *            The class name. Null if no check is required.
71     * @param minDuration
72     *            The minimum duration of the event, -1 if not applicable.
73     * @return The count of matching events
74     * @throws IOException
75     */
countMatchingEvents(Recording r, String expectedFrame, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration)76    public static long countMatchingEvents(Recording r, String expectedFrame, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration) throws IOException {
77        return countMatchingEvents(r, getFrames(expectedFrame), fieldType, fieldName, referrerType, minDuration);
78    }
79 
80     /**
81      * Gets the OldObjectSample events from the provided recording through a dump
82      * and counts how many events matches the provided parameters.
83      *
84      * @param r
85      *            A recording
86      * @param expectedStack
87      *            Some frames that must be found on the stack. Null if no check is required.
88      * @param fieldType
89      *            The object type (of the field). Null if no check is required.
90      * @param fieldName
91      *            The field name. Null if no check is required.
92      * @param referrerType
93      *            The class name. Null if no check is required.
94      * @param minDuration
95      *            The minimum duration of the event, -1 if not applicable.
96      * @return The count of matching events
97      * @throws IOException
98      */
countMatchingEvents(Recording r, String[] expectedStack, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration)99     public static long countMatchingEvents(Recording r, String[] expectedStack, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration) throws IOException {
100         return countMatchingEvents(Events.fromRecording(r), fieldType, fieldName, referrerType, minDuration, expectedStack);
101     }
102 
103     /**
104     *
105     * @param events
106     *            A list of RecordedEvent.
107     * @param expectedFrame
108     *             A frame that must be found on the stack. Null if no check is required.
109     * @param fieldType
110     *            The object type (of the field). Null if no check is required.
111     * @param fieldName
112     *            The field name. Null if no check is required.
113     * @param referrerType
114     *            The class name. Null if no check is required.
115     * @param minDuration
116     *            The minimum duration of the event, -1 if not applicable.
117     * @return The count of matching events
118     * @throws IOException
119     */
countMatchingEvents(List<RecordedEvent> events, String expectedFrame, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration)120    public static long countMatchingEvents(List<RecordedEvent> events, String expectedFrame, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration) throws IOException {
121        return countMatchingEvents(events, fieldType, fieldName, referrerType, minDuration, getFrames(expectedFrame));
122    }
123 
124     /**
125      *
126      * @param events
127      *            The list of events to find matching events in
128      * @param expectedStack
129      *            Some frames that must be found on the stack. Null if no check is required.
130      * @param fieldType
131      *            The object type (of the field). Null if no check is required.
132      * @param fieldName
133      *            The field name. Null if no check is required.
134      * @param referrerType
135      *            The class name. Null if no check is required.
136      * @param minDuration
137      *            The minimum duration of the event, -1 if not applicable.
138      * @return The count of matching events
139      * @throws IOException
140      */
countMatchingEvents(List<RecordedEvent> events, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration, String... expectedStack)141     public static long countMatchingEvents(List<RecordedEvent> events, Class<?> fieldType, String fieldName, Class<?> referrerType, long minDuration, String... expectedStack) throws IOException {
142         String currentThread = Thread.currentThread().getName();
143         return events.stream()
144                 .filter(hasJavaThread(currentThread))
145                 .filter(fieldIsType(fieldType))
146                 .filter(hasFieldName(fieldName))
147                 .filter(isReferrerType(referrerType))
148                 .filter(durationAtLeast(minDuration))
149                 .filter(hasStackTrace(expectedStack))
150                 .count();
151     }
152 
hasJavaThread(String expectedThread)153     private static Predicate<RecordedEvent> hasJavaThread(String expectedThread) {
154         if (expectedThread != null) {
155             return e -> e.getThread() != null && expectedThread.equals(e.getThread().getJavaName());
156         } else {
157             return e -> true;
158         }
159     }
160 
hasStackTrace(String[] expectedStack)161     private static Predicate<RecordedEvent> hasStackTrace(String[] expectedStack) {
162         if (expectedStack != null) {
163             return e -> matchingStackTrace(e.getStackTrace(), expectedStack);
164         } else {
165             return e -> true;
166         }
167     }
168 
fieldIsType(Class<?> fieldType)169     private static Predicate<RecordedEvent> fieldIsType(Class<?> fieldType) {
170         if (fieldType != null) {
171             return e -> e.hasField("object.type") && ((RecordedClass) e.getValue("object.type")).getName().equals(fieldType.getName());
172         } else {
173             return e -> true;
174         }
175     }
176 
hasFieldName(String fieldName)177     private static Predicate<RecordedEvent> hasFieldName(String fieldName) {
178         if (fieldName != null) {
179             return e -> {
180                 RecordedObject referrer = e.getValue("object.referrer");
181                 return referrer != null ? referrer.hasField("field.name") && referrer.getValue("field.name").equals(fieldName) : false;
182             };
183         } else {
184             return e -> true;
185         }
186     }
187 
isReferrerType(Class<?> referrerType)188     private static Predicate<RecordedEvent> isReferrerType(Class<?> referrerType) {
189         if (referrerType != null) {
190             return e -> {
191                 RecordedObject referrer = e.getValue("object.referrer");
192                 return referrer != null ? referrer.hasField("object.type") &&
193                                             ((RecordedClass) referrer.getValue("object.type")).getName().equals(referrerType.getName()) : false;
194             };
195         } else {
196             return e -> true;
197         }
198     }
199 
200     private static Predicate<RecordedEvent> durationAtLeast(long minDurationMs) {
201         if (minDurationMs > 0) {
202             return e -> e.getDuration().toMillis() >= minDurationMs;
203         } else {
204             return e -> true;
205         }
206     }
207 
208     public static boolean matchingReferrerClass(RecordedEvent event, String className) {
209         RecordedObject referrer = event.getValue("object.referrer");
210         if (referrer != null) {
211             if (!referrer.hasField("object.type")) {
212                 return false;
213             }
214 
215             String reportedClass = ((RecordedClass) referrer.getValue("object.type")).getName();
216             if (reportedClass.equals(className)) {
217                 return true;
218             }
219         }
220         return false;
221     }
222 
223     public static String getReferrerFieldName(RecordedEvent event) {
224         RecordedObject referrer = event.getValue("object.referrer");
225         return referrer != null && referrer.hasField("field.name") ? referrer.getValue("field.name") : null;
226     }
227 
228     public static boolean matchingStackTrace(RecordedStackTrace stack, String[] expectedStack) {
229         if (stack == null) {
230             return false;
231         }
232 
233         List<RecordedFrame> frames = stack.getFrames();
234         int pos = findFramePos(frames, expectedStack[0]);
235 
236         if (pos == -1) {
237             return false;
238         }
239 
240         for (String expectedFrame : expectedStack) {
241             RecordedFrame f = frames.get(pos++);
242             String frame = frameToString(f);
243 
244             if (!frame.contains(expectedFrame)) {
245                 return false;
246             }
247         }
248         return true;
249     }
250 
251     private static int findFramePos(List<RecordedFrame> frames, String frame) {
252         int pos = 0;
253         for (RecordedFrame f : frames) {
254             if (frameToString(f).contains(frame)) {
255                 return pos;
256             }
257             pos++;
258         }
259         return -1;
260     }
261 
262     private static String frameToString(RecordedFrame f) {
263         RecordedMethod m = f.getMethod();
264         String methodName = m.getName();
265         String className = m.getType().getName();
266         return className + "." + methodName;
267     }
268 
269     public static void validateReferenceChainLimit(RecordedEvent e, int maxLength) {
270         int length = 0;
271         RecordedObject object = e.getValue("object");
272         while (object != null) {
273             ++length;
274             RecordedObject referrer = object.getValue("referrer");
275             object = referrer != null ? referrer.getValue("object") : null;
276         }
277         if (length > maxLength) {
278             throw new RuntimeException("Reference chain max length not respected. Found a chain of length " + length);
279         }
280     }
281 }
282