1 /* 2 * Copyright (c) 2006, 2015, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 6338874 27 * @summary Check that notification dispatch is not linear in number of MBeans. 28 * @author Eamonn McManus 29 * 30 * @library /test/lib 31 * 32 * @run build jdk.test.lib.Platform ListenerScaleTest 33 * @run main ListenerScaleTest 34 */ 35 36 /* 37 * The notification dispatch logic in the connector server used to be 38 * linear in the number of listeners there were on any MBean. For 39 * example, if there were 1000 MBeans, each with one listener, then 40 * every time a notification was sent it would be compared against 41 * every one of the 1000 MBeans, even though its source ObjectName was 42 * known and could not possibly match the name of 999 of the MBeans. 43 * This test checks that we no longer have linear behaviour. We do 44 * this by registering just one MBean and measuring the time it takes 45 * to send and receive a certain number of notifications from that 46 * MBean. Then we register many other MBeans, each with a listener, 47 * and we make the same measurement as before. The presence of the 48 * extra MBeans with their listeners should not impact the dispatch 49 * time significantly. If it does, the test fails. 50 * 51 * As usual with timing-sensitive tests, we could potentially get 52 * sporadic failures. We fail if the ratio of the time with many 53 * MBeans to the time with just one MBean is more than 500. With the 54 * fix in place, it is usually less than 1, presumably because some 55 * code was being interpreted during the first measurement but had 56 * been compiled by the second. 57 */ 58 59 import java.util.concurrent.Semaphore; 60 import javax.management.MBeanServer; 61 import javax.management.MBeanServerConnection; 62 import javax.management.MBeanServerFactory; 63 import javax.management.MalformedObjectNameException; 64 import javax.management.Notification; 65 import javax.management.NotificationBroadcasterSupport; 66 import javax.management.NotificationListener; 67 import javax.management.ObjectName; 68 import javax.management.remote.JMXConnector; 69 import javax.management.remote.JMXConnectorFactory; 70 import javax.management.remote.JMXConnectorServer; 71 import javax.management.remote.JMXConnectorServerFactory; 72 import javax.management.remote.JMXServiceURL; 73 74 import jdk.test.lib.Platform; 75 76 public class ListenerScaleTest { 77 private static final int WARMUP_WITH_ONE_MBEAN = 1000; 78 private static final int NOTIFS_TO_TIME = 100; 79 private static final int EXTRA_MBEANS = 20000; 80 81 private static final ObjectName testObjectName; 82 static { 83 try { 84 testObjectName = new ObjectName("test:type=Sender,number=-1"); 85 } catch (MalformedObjectNameException e) { 86 throw new RuntimeException(e); 87 } 88 } 89 90 private static volatile int nnotifs; 91 private static volatile long startTime; 92 private static volatile long elapsed; 93 private static final Semaphore sema = new Semaphore(0); 94 95 private static final NotificationListener timingListener = 96 new NotificationListener() { 97 public void handleNotification(Notification n, Object h) { 98 if (++nnotifs == NOTIFS_TO_TIME) { 99 elapsed = System.nanoTime() - startTime; 100 sema.release(); 101 } 102 } 103 }; 104 105 private static final long timeNotif(MBeanServer mbs) { 106 try { 107 startTime = System.nanoTime(); 108 nnotifs = 0; 109 mbs.invoke(testObjectName, "send", null, null); 110 sema.acquire(); 111 return elapsed; 112 } catch (Exception e) { 113 throw new RuntimeException(e); 114 } 115 } 116 117 public static interface SenderMBean { 118 public void send(); 119 } 120 121 public static class Sender extends NotificationBroadcasterSupport 122 implements SenderMBean { 123 public void send() { 124 for (int i = 0; i < NOTIFS_TO_TIME; i++) 125 sendNotification(new Notification("type", this, 1L)); 126 } 127 } 128 129 private static final NotificationListener nullListener = 130 new NotificationListener() { 131 public void handleNotification(Notification n, Object h) {} 132 }; 133 134 public static void main(String[] args) throws Exception { 135 if (Platform.isDebugBuild()) { 136 System.out.println("Running on a debug build. Performance test not applicable. Skipping."); 137 return; 138 } 139 MBeanServer mbs = MBeanServerFactory.newMBeanServer(); 140 Sender sender = new Sender(); 141 mbs.registerMBean(sender, testObjectName); 142 JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://"); 143 JMXConnectorServer cs = 144 JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs); 145 cs.start(); 146 JMXServiceURL addr = cs.getAddress(); 147 JMXConnector cc = JMXConnectorFactory.connect(addr); 148 try { 149 test(mbs, cs, cc); 150 } finally { 151 cc.close(); 152 cs.stop(); 153 } 154 } 155 156 private static void test(MBeanServer mbs, JMXConnectorServer cs, 157 JMXConnector cc) throws Exception { 158 MBeanServerConnection mbsc = cc.getMBeanServerConnection(); 159 mbsc.addNotificationListener(testObjectName, timingListener, null, null); 160 long singleMBeanTime = 0; 161 for (int i = 0; i < WARMUP_WITH_ONE_MBEAN; i++) 162 singleMBeanTime = timeNotif(mbs); 163 if (singleMBeanTime == 0) 164 singleMBeanTime = 1; 165 System.out.println("Time with a single MBean: " + singleMBeanTime + "ns"); 166 System.out.println("Now registering " + EXTRA_MBEANS + " MBeans"); 167 for (int i = 0; i < EXTRA_MBEANS; i++) { 168 ObjectName on = new ObjectName("test:type=Sender,number=" + i); 169 mbs.registerMBean(new Sender(), on); 170 if (i % 1000 == 999) { 171 System.out.print("..." + (i+1)); 172 System.out.flush(); 173 } 174 } 175 System.out.println(); 176 System.out.println("Now registering " + EXTRA_MBEANS + " listeners"); 177 for (int i = 0; i < EXTRA_MBEANS; i++) { 178 ObjectName on = new ObjectName("test:type=Sender,number=" + i); 179 mbsc.addNotificationListener(on, nullListener, null, null); 180 if (i % 1000 == 999) { 181 System.out.print("..." + (i+1)); 182 System.out.flush(); 183 } 184 } 185 System.out.println(); 186 System.out.println("Timing a notification send now"); 187 long manyMBeansTime = timeNotif(mbs); 188 System.out.println("Time with many MBeans: " + manyMBeansTime + "ns"); 189 double ratio = (double) manyMBeansTime / singleMBeanTime; 190 if (ratio > 500.0) 191 throw new Exception("Failed: ratio=" + ratio); 192 System.out.println("Test passed: ratio=" + ratio); 193 } 194 }