test/jdk/jdk/jfr/event/oldobject/OldObjects.java
author egahlin
Tue, 15 May 2018 20:24:34 +0200
changeset 50113 caf115bb98ad
permissions -rw-r--r--
8199712: Flight Recorder Reviewed-by: coleenp, ihse, erikj, dsamersoff, mseledtsov, egahlin, mgronlun Contributed-by: erik.gahlin@oracle.com, markus.gronlund@oracle.com

/*
 * 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<RecordedEvent> 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<RecordedEvent> 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<RecordedEvent> hasJavaThread(String expectedThread) {
        if (expectedThread != null) {
            return e -> e.getThread() != null && expectedThread.equals(e.getThread().getJavaName());
        } else {
            return e -> true;
        }
    }

    private static Predicate<RecordedEvent> hasStackTrace(String[] expectedStack) {
        if (expectedStack != null) {
            return e -> matchingStackTrace(e.getStackTrace(), expectedStack);
        } else {
            return e -> true;
        }
    }

    private static Predicate<RecordedEvent> 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<RecordedEvent> 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<RecordedEvent> 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<RecordedEvent> 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<RecordedFrame> 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<RecordedFrame> 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);
        }
    }
}