/*
* Copyright (c) 2003, 2005, 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.
*
* 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.
*/
/*
* @test
* @bug 4530538
* @summary Basic unit test of the synchronization statistics support:
*
* @author Mandy Chung
*
* @ignore 6309226
* @build Semaphore
* @run main SynchronizationStatistics
*/
import java.lang.management.*;
public class SynchronizationStatistics {
private static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
private static boolean blockedTimeCheck =
mbean.isThreadContentionMonitoringSupported();
private static boolean trace = false;
private static Object lockA = new Object();
private static Object lockB = new Object();
private static Object lockC = new Object();
private static Object lockD = new Object();
private static Object waiter = new Object();
private static volatile boolean testFailed = false;
private static Object go = new Object();
private static void goSleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("Unexpected exception.");
testFailed = true;
}
}
public static void main(String args[]) throws Exception {
if (args.length > 0 && args[0].equals("trace")) {
trace = true;
}
if (blockedTimeCheck) {
mbean.setThreadContentionMonitoringEnabled(true);
}
if (!mbean.isThreadContentionMonitoringEnabled()) {
throw new RuntimeException("TEST FAILED: " +
"Thread Contention Monitoring is not enabled");
}
Examiner examiner = new Examiner("Examiner");
BlockedThread blocked = new BlockedThread("BlockedThread");
examiner.setThread(blocked);
// Start the threads and check them in Blocked and Waiting states
examiner.start();
// wait until the examiner acquires all the locks and waiting
// for the BlockedThread to start
examiner.waitUntilWaiting();
System.out.println("Checking the thread state for the examiner thread " +
"is waiting to begin.");
// The Examiner should be waiting to be notified by the BlockedThread
checkThreadState(examiner, Thread.State.WAITING);
System.out.println("Now starting the blocked thread");
blocked.start();
try {
examiner.join();
blocked.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("Unexpected exception.");
testFailed = true;
}
if (testFailed)
throw new RuntimeException("TEST FAILED.");
System.out.println("Test passed.");
}
private static String INDENT = " ";
private static void printStack(Thread t, StackTraceElement[] stack) {
System.out.println(INDENT + t +
" stack: (length = " + stack.length + ")");
if (t != null) {
for (int j = 0; j < stack.length; j++) {
System.out.println(INDENT + stack[j]);
}
System.out.println();
}
}
private static void checkThreadState(Thread thread, Thread.State s)
throws Exception {
ThreadInfo ti = mbean.getThreadInfo(thread.getId());
if (ti.getThreadState() != s) {
ThreadInfo info = mbean.getThreadInfo(thread.getId(),
Integer.MAX_VALUE);
System.out.println(INDENT + "TEST FAILED:");
printStack(thread, info.getStackTrace());
System.out.println(INDENT + "Thread state: " + info.getThreadState());
throw new RuntimeException("TEST FAILED: " +
"Thread state for " + thread + " returns " + ti.getThreadState() +
". Expected to be " + s);
}
}
private static void checkThreadState(Thread thread,
Thread.State s1, Thread.State s2)
throws Exception {
ThreadInfo ti = mbean.getThreadInfo(thread.getId());
if (ti.getThreadState() != s1 && ti.getThreadState() != s2) {
throw new RuntimeException("TEST FAILED: " +
"Thread state for " + thread + " returns " + ti.getThreadState() +
". Expected to be " + s1 + " or " + s2);
}
}
static class StatThread extends Thread {
private long blockingBaseTime = 0;
private long totalWaitTime = 0;
private long totalBlockedEnterTime = 0;
StatThread(String name) {
super(name);
}
void addWaitTime(long ns) {
totalWaitTime = totalWaitTime + ns;
}
void addBlockedEnterTime(long ns) {
totalBlockedEnterTime = totalBlockedEnterTime + ns;
}
void setBlockingBaseTime(long time) {
blockingBaseTime = time;
}
long totalBlockedTimeMs() {
return totalBlockedEnterTime / 1000000;
}
long totalBlockedTimeMs(long now) {
long t = totalBlockedEnterTime + (now - blockingBaseTime);
return t / 1000000;
}
long totalWaitTimeMs() {
return totalWaitTime / 1000000;
}
long totalWaitTimeMs(long now) {
long t = totalWaitTime + (now - blockingBaseTime);
return t / 1000000;
}
}
static class BlockedThread extends StatThread {
private Semaphore handshake = new Semaphore();
BlockedThread(String name) {
super(name);
}
void waitUntilBlocked() {
handshake.semaP();
// give a chance for the examiner thread to really wait
goSleep(20);
}
void waitUntilWaiting() {
waitUntilBlocked();
}
boolean hasWaitersForBlocked() {
return (handshake.getWaiterCount() > 0);
}
private void notifyWaiter() {
// wait until the examiner waits on the semaphore
while (handshake.getWaiterCount() == 0) {
goSleep(20);
}
handshake.semaV();
}
private void waitObj(long ms) {
synchronized (waiter) {
try {
// notify examinerabout to wait on a monitor
notifyWaiter();
long base = System.nanoTime();
setBlockingBaseTime(base);
waiter.wait(ms);
long now = System.nanoTime();
addWaitTime(now - base);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unexpected exception.");
testFailed = true;
}
}
}
private void test() {
// notify examiner about to block on lockA
notifyWaiter();
long base = System.nanoTime();
setBlockingBaseTime(base);
synchronized (lockA) {
long now = System.nanoTime();
addBlockedEnterTime(now - base);
A(); // Expected blocked count = 1
}
E();
}
private void A() {
// notify examiner about to block on lockB
notifyWaiter();
long base = System.nanoTime();
setBlockingBaseTime(base);
synchronized (lockB) {
long now = System.nanoTime();
addBlockedEnterTime(now - base);
B(); // Expected blocked count = 2
}
}
private void B() {
// notify examiner about to block on lockC
notifyWaiter();
long base = System.nanoTime();
setBlockingBaseTime(base);
synchronized (lockC) {
long now = System.nanoTime();
addBlockedEnterTime(now - base);
C(); // Expected blocked count = 3
}
}
private void C() {
// notify examiner about to block on lockD
notifyWaiter();
long base = System.nanoTime();
setBlockingBaseTime(base);
synchronized (lockD) {
long now = System.nanoTime();
addBlockedEnterTime(now - base);
D(); // Expected blocked count = 4
}
}
private void D() {
goSleep(50);
}
private void E() {
final int WAIT = 1000;
waitObj(WAIT);
waitObj(WAIT);
waitObj(WAIT);
}
public void run() {
test();
} // run()
} // BlockedThread
static int blockedCount = 0;
static int waitedCount = 0;
static class Examiner extends StatThread {
private BlockedThread blockedThread;
private Semaphore semaphore = new Semaphore();
Examiner(String name) {
super(name);
}
public void setThread(BlockedThread thread) {
blockedThread = thread;
}
private void blockedTimeRangeCheck(StatThread t,
long blockedTime,
long nowNano)
throws Exception {
long expected = t.totalBlockedTimeMs(nowNano);
// accept 5% range
timeRangeCheck(blockedTime, expected, 5);
}
private void waitedTimeRangeCheck(StatThread t,
long waitedTime,
long nowNano)
throws Exception {
long expected = t.totalWaitTimeMs(nowNano);
// accept 5% range
timeRangeCheck(waitedTime, expected, 5);
}
private void timeRangeCheck(long time, long expected, int percent)
throws Exception {
double diff = expected - time;
if (trace) {
System.out.println(" Time = " + time +
" expected = " + expected +
". Diff = " + diff);
}
// throw an exception if blockedTime and expectedTime
// differs > percent%
if (diff < 0) {
diff = diff * -1;
}
long range = (expected * percent) / 100;
// minimum range = 2 ms
if (range < 2) {
range = 2;
}
if (diff > range) {
throw new RuntimeException("TEST FAILED: " +
"Time returned = " + time +
" expected = " + expected + ". Diff = " + diff);
}
}
private void checkInfo(StatThread t, Thread.State s, Object lock,
String lockName, int bcount, int wcount)
throws Exception {
String action = "ERROR";
if (s == Thread.State.WAITING || s == Thread.State.TIMED_WAITING) {
action = "wait on ";
} else if (s == Thread.State.BLOCKED) {
action = "block on ";
}
System.out.println(t + " expected to " + action + lockName +
" with blocked count = " + bcount +
" and waited count = " + wcount);
long now = System.nanoTime();
ThreadInfo info = mbean.getThreadInfo(t.getId());
if (info.getThreadState() != s) {
printStack(t, info.getStackTrace());
throw new RuntimeException("TEST FAILED: " +
"Thread state returned is " + info.getThreadState() +
". Expected to be " + s);
}
if (info.getLockName() == null ||
!info.getLockName().equals(lock.toString())) {
throw new RuntimeException("TEST FAILED: " +
"getLockName() returned " + info.getLockName() +
". Expected to be " + lockName + " - " + lock.toString());
}
if (info.getBlockedCount() != bcount) {
throw new RuntimeException("TEST FAILED: " +
"Blocked Count returned is " + info.getBlockedCount() +
". Expected to be " + bcount);
}
if (info.getWaitedCount() != wcount) {
throw new RuntimeException("TEST FAILED: " +
"Waited Count returned is " + info.getWaitedCount() +
". Expected to be " + wcount);
}
String lockObj = info.getLockName();
if (lockObj == null || !lockObj.equals(lock.toString())) {
throw new RuntimeException("TEST FAILED: " +
"Object blocked on is " + lockObj +
". Expected to be " + lock.toString());
}
if (!blockedTimeCheck) {
return;
}
long blockedTime = info.getBlockedTime();
if (blockedTime < 0) {
throw new RuntimeException("TEST FAILED: " +
"Blocked time returned is negative = " + blockedTime);
}
if (s == Thread.State.BLOCKED) {
blockedTimeRangeCheck(t, blockedTime, now);
} else {
timeRangeCheck(blockedTime, t.totalBlockedTimeMs(), 5);
}
long waitedTime = info.getWaitedTime();
if (waitedTime < 0) {
throw new RuntimeException("TEST FAILED: " +
"Waited time returned is negative = " + waitedTime);
}
if (s == Thread.State.WAITING || s == Thread.State.TIMED_WAITING) {
waitedTimeRangeCheck(t, waitedTime, now);
} else {
timeRangeCheck(waitedTime, t.totalWaitTimeMs(), 5);
}
}
private void examine() {
try {
synchronized (lockD) {
synchronized (lockC) {
synchronized (lockB) {
synchronized (lockA) {
// notify main thread to continue
semaphore.semaV();
// wait until BlockedThread has started
blockedThread.waitUntilBlocked();
blockedCount++;
checkInfo(blockedThread, Thread.State.BLOCKED,
lockA, "lockA",
blockedCount, waitedCount);
}
// wait until BlockedThread to block on lockB
blockedThread.waitUntilBlocked();
blockedCount++;
checkInfo(blockedThread, Thread.State.BLOCKED,
lockB, "lockB",
blockedCount, waitedCount);
}
// wait until BlockedThread to block on lockC
blockedThread.waitUntilBlocked();
blockedCount++;
checkInfo(blockedThread, Thread.State.BLOCKED,
lockC, "lockC",
blockedCount, waitedCount);
}
// wait until BlockedThread to block on lockD
blockedThread.waitUntilBlocked();
blockedCount++;
checkInfo(blockedThread, Thread.State.BLOCKED,
lockD, "lockD",
blockedCount, waitedCount);
}
// wait until BlockedThread about to call E()
// BlockedThread will wait on waiter for 3 times
blockedThread.waitUntilWaiting();
waitedCount++;
checkInfo(blockedThread, Thread.State.TIMED_WAITING,
waiter, "waiter", blockedCount, waitedCount);
blockedThread.waitUntilWaiting();
waitedCount++;
checkInfo(blockedThread, Thread.State.TIMED_WAITING,
waiter, "waiter", blockedCount, waitedCount);
blockedThread.waitUntilWaiting();
waitedCount++;
checkInfo(blockedThread, Thread.State.TIMED_WAITING,
waiter, "waiter", blockedCount, waitedCount);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unexpected exception.");
testFailed = true;
}
}
public void run() {
examine();
} // run()
public void waitUntilWaiting() {
semaphore.semaP();
// wait until the examiner is waiting for
while (!blockedThread.hasWaitersForBlocked()) {
goSleep(50);
}
// give a chance for the examiner thread to really wait
goSleep(20);
}
} // Examiner
}