/* * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.jfr.event.oldobject; import java.io.IOException; import java.util.List; import java.util.function.Predicate; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedClass; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordedFrame; import jdk.jfr.consumer.RecordedMethod; import jdk.jfr.consumer.RecordedObject; import jdk.jfr.consumer.RecordedStackTrace; import jdk.test.lib.jfr.Events; /** * Utility class to perform Old Object provocation/detection and * stack trace/object verification for the Old Object Sample JFR event */ final public class OldObjects { public static final int MIN_SIZE = 99901; // prime number public final static int LEAK_CONTEXT = 100; // length of chain assiociated with the object sample public final static int ROOT_CONTEXT = 100; // length of chain assoicated with the root public final static int MAX_CHAIN_LENGTH = LEAK_CONTEXT + ROOT_CONTEXT; // the VM should not construct chains longer than this private static String[] getFrames(String expectedFrame) { if (expectedFrame != null) { return new String[] { expectedFrame }; } else { return null; } } /** * * @param r * A recording * @param expectedFrame * A frame that must be found on the stack. Null if no check is required. * @param fieldType * The object type (of the field). Null if no check is required. * @param fieldName * The field name. Null if no check is required. * @param referrerType * The class name. Null if no check is required. * @param minDuration * The minimum duration of the event, -1 if not applicable. * @return The count of matching events * @throws IOException */ public static long countMatchingEvents(Recording r, String expectedFrame, Class fieldType, String fieldName, Class referrerType, long minDuration) throws IOException { return countMatchingEvents(r, getFrames(expectedFrame), fieldType, fieldName, referrerType, minDuration); } /** * Gets the OldObjectSample events from the provided recording through a dump * and counts how many events matches the provided parameters. * * @param r * A recording * @param expectedStack * Some frames that must be found on the stack. Null if no check is required. * @param fieldType * The object type (of the field). Null if no check is required. * @param fieldName * The field name. Null if no check is required. * @param referrerType * The class name. Null if no check is required. * @param minDuration * The minimum duration of the event, -1 if not applicable. * @return The count of matching events * @throws IOException */ public static long countMatchingEvents(Recording r, String[] expectedStack, Class fieldType, String fieldName, Class referrerType, long minDuration) throws IOException { return countMatchingEvents(Events.fromRecording(r), fieldType, fieldName, referrerType, minDuration, expectedStack); } /** * * @param events * A list of RecordedEvent. * @param expectedFrame * A frame that must be found on the stack. Null if no check is required. * @param fieldType * The object type (of the field). Null if no check is required. * @param fieldName * The field name. Null if no check is required. * @param referrerType * The class name. Null if no check is required. * @param minDuration * The minimum duration of the event, -1 if not applicable. * @return The count of matching events * @throws IOException */ public static long countMatchingEvents(List events, String expectedFrame, Class fieldType, String fieldName, Class referrerType, long minDuration) throws IOException { return countMatchingEvents(events, fieldType, fieldName, referrerType, minDuration, getFrames(expectedFrame)); } /** * * @param events * The list of events to find matching events in * @param expectedStack * Some frames that must be found on the stack. Null if no check is required. * @param fieldType * The object type (of the field). Null if no check is required. * @param fieldName * The field name. Null if no check is required. * @param referrerType * The class name. Null if no check is required. * @param minDuration * The minimum duration of the event, -1 if not applicable. * @return The count of matching events * @throws IOException */ public static long countMatchingEvents(List events, Class fieldType, String fieldName, Class referrerType, long minDuration, String... expectedStack) throws IOException { String currentThread = Thread.currentThread().getName(); return events.stream() .filter(hasJavaThread(currentThread)) .filter(fieldIsType(fieldType)) .filter(hasFieldName(fieldName)) .filter(isReferrerType(referrerType)) .filter(durationAtLeast(minDuration)) .filter(hasStackTrace(expectedStack)) .count(); } private static Predicate hasJavaThread(String expectedThread) { if (expectedThread != null) { return e -> e.getThread() != null && expectedThread.equals(e.getThread().getJavaName()); } else { return e -> true; } } private static Predicate hasStackTrace(String[] expectedStack) { if (expectedStack != null) { return e -> matchingStackTrace(e.getStackTrace(), expectedStack); } else { return e -> true; } } private static Predicate fieldIsType(Class fieldType) { if (fieldType != null) { return e -> e.hasField("object.type") && ((RecordedClass) e.getValue("object.type")).getName().equals(fieldType.getName()); } else { return e -> true; } } private static Predicate hasFieldName(String fieldName) { if (fieldName != null) { return e -> { RecordedObject referrer = e.getValue("object.referrer"); return referrer != null ? referrer.hasField("field.name") && referrer.getValue("field.name").equals(fieldName) : false; }; } else { return e -> true; } } private static Predicate isReferrerType(Class referrerType) { if (referrerType != null) { return e -> { RecordedObject referrer = e.getValue("object.referrer"); return referrer != null ? referrer.hasField("object.type") && ((RecordedClass) referrer.getValue("object.type")).getName().equals(referrerType.getName()) : false; }; } else { return e -> true; } } private static Predicate durationAtLeast(long minDurationMs) { if (minDurationMs > 0) { return e -> e.getDuration().toMillis() >= minDurationMs; } else { return e -> true; } } public static boolean matchingReferrerClass(RecordedEvent event, String className) { RecordedObject referrer = event.getValue("object.referrer"); if (referrer != null) { if (!referrer.hasField("object.type")) { return false; } String reportedClass = ((RecordedClass) referrer.getValue("object.type")).getName(); if (reportedClass.equals(className)) { return true; } } return false; } public static String getReferrerFieldName(RecordedEvent event) { RecordedObject referrer = event.getValue("object.referrer"); return referrer != null && referrer.hasField("field.name") ? referrer.getValue("field.name") : null; } public static boolean matchingStackTrace(RecordedStackTrace stack, String[] expectedStack) { if (stack == null) { return false; } List frames = stack.getFrames(); int pos = findFramePos(frames, expectedStack[0]); if (pos == -1) { return false; } for (String expectedFrame : expectedStack) { RecordedFrame f = frames.get(pos++); String frame = frameToString(f); if (!frame.contains(expectedFrame)) { return false; } } return true; } private static int findFramePos(List frames, String frame) { int pos = 0; for (RecordedFrame f : frames) { if (frameToString(f).contains(frame)) { return pos; } pos++; } return -1; } private static String frameToString(RecordedFrame f) { RecordedMethod m = f.getMethod(); String methodName = m.getName(); String className = m.getType().getName(); return className + "." + methodName; } public static void validateReferenceChainLimit(RecordedEvent e, int maxLength) { int length = 0; RecordedObject object = e.getValue("object"); while (object != null) { ++length; RecordedObject referrer = object.getValue("referrer"); object = referrer != null ? referrer.getValue("object") : null; } if (length > maxLength) { throw new RuntimeException("Reference chain max length not respected. Found a chain of length " + length); } } }