test/jdk/jdk/jfr/event/runtime/TestBiasedLockRevocationEvents.java
changeset 50113 caf115bb98ad
child 50336 1b6ea6bcd21a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/event/runtime/TestBiasedLockRevocationEvents.java	Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2017, 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.runtime;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.*;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.dcmd.PidJcmdExecutor;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.process.OutputAnalyzer;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+/*
+ * @test
+ * @key jfr
+ * @library /test/lib
+ *
+ * @run main/othervm jdk.jfr.event.runtime.TestBiasedLockRevocationEvents
+ */
+public class TestBiasedLockRevocationEvents {
+
+    public static void main(String[] args) throws Throwable {
+        testSingleRevocation();
+        testBulkRevocation();
+        testSelfRevocation();
+        testExitedThreadRevocation();
+        testBulkRevocationNoRebias();
+        testRevocationSafepointIdCorrelation();
+    }
+
+    // Default value of BiasedLockingBulkRebiasThreshold is 20, and BiasedLockingBulkRevokeTreshold is 40.
+    // Using a value that will hit the first threshold once, and the second one the next time.
+    private static final int BULK_REVOKE_THRESHOLD = 25;
+
+    static void touch(Object lock) {
+        synchronized(lock) {
+        }
+    }
+
+    static Thread triggerRevocation(int numRevokes, Class<?> lockClass) throws Throwable {
+        Object[] locks = new Object[numRevokes];
+        for (int i = 0; i < locks.length; ++i) {
+            locks[i] = lockClass.getDeclaredConstructor().newInstance();
+            touch(locks[i]);
+        }
+
+        Thread biasBreaker = new Thread("BiasBreaker") {
+            @Override
+            public void run() {
+                for (Object lock : locks) {
+                    touch(lock);
+                }
+            }
+        };
+
+        biasBreaker.start();
+        biasBreaker.join();
+
+        return biasBreaker;
+    }
+
+    // Basic stack trace validation, checking the name of the leaf method
+    static void validateStackTrace(RecordedStackTrace stackTrace, String leafMethodName) {
+        List<RecordedFrame> frames = stackTrace.getFrames();
+        Asserts.assertFalse(frames.isEmpty());
+        String name = frames.get(0).getMethod().getName();
+        Asserts.assertEquals(name, leafMethodName);
+    }
+
+    // Validates that the given stack trace refers to lock.touch(); in triggerRevocation
+    static void validateStackTrace(RecordedStackTrace stackTrace) {
+        validateStackTrace(stackTrace, "touch");
+    }
+
+    static void testSingleRevocation() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockRevocation);
+        recording.start();
+
+        Thread biasBreaker = triggerRevocation(1, MyLock.class);
+
+        recording.stop();
+
+        List<RecordedEvent> events = Events.fromRecording(recording);
+
+        // We may or may not catch a second revocation from the biasBreaker thread exiting
+        Asserts.assertGreaterThanOrEqual(events.size(), 1);
+
+        RecordedEvent event = events.get(0);
+        Events.assertEventThread(event, biasBreaker);
+        Events.assertEventThread(event, "previousOwner", Thread.currentThread());
+
+        RecordedClass lockClass = event.getValue("lockClass");
+        Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
+
+        validateStackTrace(event.getStackTrace());
+    }
+
+    static void testBulkRevocation() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockClassRevocation);
+        recording.start();
+
+        Thread biasBreaker = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
+
+        recording.stop();
+        List<RecordedEvent> events = Events.fromRecording(recording);
+        Asserts.assertEQ(events.size(), 1);
+
+        RecordedEvent event = events.get(0);
+        Events.assertEventThread(event, biasBreaker);
+        Events.assertField(event, "disableBiasing").equal(false);
+
+        RecordedClass lockClass = event.getValue("revokedClass");
+        Asserts.assertEquals(lockClass.getName(), MyLock.class.getName());
+
+        validateStackTrace(event.getStackTrace());
+    }
+
+    static void testSelfRevocation() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockSelfRevocation);
+        recording.start();
+
+        MyLock l = new MyLock();
+        touch(l);
+        Thread.holdsLock(l);
+
+        recording.stop();
+
+        List<RecordedEvent> events = Events.fromRecording(recording);
+        Asserts.assertEQ(events.size(), 1);
+
+        RecordedEvent event = events.get(0);
+        Events.assertEventThread(event, Thread.currentThread());
+
+        validateStackTrace(event.getStackTrace(), "holdsLock");
+    }
+
+    static void testExitedThreadRevocation() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockRevocation);
+        recording.start();
+
+        FutureTask<MyLock> lockerTask = new FutureTask<>(() -> {
+           MyLock l = new MyLock();
+           touch(l);
+           return l;
+        });
+
+        Thread locker = new Thread(lockerTask, "BiasLocker");
+        locker.start();
+        locker.join();
+
+        // Even after joining, the VM has a bit more work to do before the thread is actually removed
+        // from the threads list. Ensure that this has happened before proceeding.
+        while (true) {
+            PidJcmdExecutor jcmd = new PidJcmdExecutor();
+            OutputAnalyzer oa = jcmd.execute("Thread.print", true);
+            String lockerThreadFound = oa.firstMatch("BiasLocker");
+            if (lockerThreadFound == null) {
+                break;
+            }
+        };
+
+        MyLock l = lockerTask.get();
+        touch(l);
+
+        recording.stop();
+        List<RecordedEvent> events = Events.fromRecording(recording);
+        Events.hasEvents(events);
+
+        // Joining the locker thread can cause revocations as well, search for the interesting one
+        for (RecordedEvent event : events) {
+            RecordedClass lockClass = event.getValue("lockClass");
+            if (lockClass.getName().equals(MyLock.class.getName())) {
+                Events.assertEventThread(event, Thread.currentThread());
+                // Previous owner will usually be null, but can also be a thread that
+                // was created after the BiasLocker thread exited due to address reuse.
+                RecordedThread prevOwner = event.getValue("previousOwner");
+                if (prevOwner != null) {
+                    Asserts.assertNE(prevOwner.getJavaName(), "BiasLocker");
+                }
+                validateStackTrace(event.getStackTrace());
+                return;
+            }
+        }
+        Asserts.fail("Did not find any revocation event for MyLock");
+    }
+
+    static void testBulkRevocationNoRebias() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockClassRevocation);
+        recording.start();
+
+        Thread biasBreaker0 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
+        Thread biasBreaker1 = triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
+
+        recording.stop();
+        List<RecordedEvent> events = Events.fromRecording(recording);
+        Asserts.assertEQ(events.size(), 2);
+
+        // The rebias event should occur before the noRebias one
+        RecordedEvent eventRebias = events.get(0).getStartTime().isBefore(events.get(1).getStartTime()) ? events.get(0) : events.get(1);
+        RecordedEvent eventNoRebias = events.get(0).getStartTime().isBefore(events.get(1).getStartTime()) ? events.get(1) : events.get(0);
+
+        Events.assertEventThread(eventRebias, biasBreaker0);
+        Events.assertField(eventRebias, "disableBiasing").equal(false);
+
+        Events.assertEventThread(eventNoRebias, biasBreaker1);
+        Events.assertField(eventNoRebias, "disableBiasing").equal(true);
+
+        RecordedClass lockClassRebias = eventRebias.getValue("revokedClass");
+        Asserts.assertEquals(lockClassRebias.getName(), MyLock.class.getName());
+        RecordedClass lockClassNoRebias = eventNoRebias.getValue("revokedClass");
+        Asserts.assertEquals(lockClassNoRebias.getName(), MyLock.class.getName());
+
+        validateStackTrace(eventRebias.getStackTrace());
+        validateStackTrace(eventNoRebias.getStackTrace());
+    }
+
+    static void testRevocationSafepointIdCorrelation() throws Throwable {
+        class MyLock {};
+
+        Recording recording = new Recording();
+
+        recording.enable(EventNames.BiasedLockRevocation);
+        recording.enable(EventNames.BiasedLockClassRevocation);
+        recording.enable(EventNames.ExecuteVMOperation);
+        recording.start();
+
+        triggerRevocation(BULK_REVOKE_THRESHOLD, MyLock.class);
+
+        recording.stop();
+        List<RecordedEvent> events = Events.fromRecording(recording);
+
+        // Find all biased locking related VMOperation events
+        HashMap<Integer, RecordedEvent> vmOperations = new HashMap<Integer, RecordedEvent>();
+        for (RecordedEvent event : events) {
+            if ((event.getEventType().getName().equals(EventNames.ExecuteVMOperation)) &&
+                    (event.getValue("operation").toString().contains("Bias"))) {
+                vmOperations.put(event.getValue("safepointId"), event);
+            }
+        }
+
+        int revokeCount = 0;
+        int bulkRevokeCount = 0;
+
+        // Match all revoke events to a corresponding VMOperation event
+        for (RecordedEvent event : events) {
+            if (event.getEventType().getName().equals(EventNames.BiasedLockRevocation)) {
+                RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
+                if (event.getValue("safepointId").toString().equals("-1")) {
+                    Asserts.assertEquals(vmOpEvent, null);
+                } else {
+                    Events.assertField(vmOpEvent, "operation").equal("RevokeBias");
+                    revokeCount++;
+                }
+            } else if (event.getEventType().getName().equals(EventNames.BiasedLockClassRevocation)) {
+                RecordedEvent vmOpEvent = vmOperations.remove(event.getValue("safepointId"));
+                Events.assertField(vmOpEvent, "operation").equal("BulkRevokeBias");
+                bulkRevokeCount++;
+            }
+        }
+
+        // All VMOperations should have had a matching revoke event
+        Asserts.assertEQ(vmOperations.size(), 0);
+
+        Asserts.assertGT(bulkRevokeCount, 0);
+        Asserts.assertGT(revokeCount, bulkRevokeCount);
+    }
+}