1 /* 2 * Copyright (c) 2017, 2018, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.jfr.event.runtime; 27 28 import com.sun.management.ThreadMXBean; 29 import jdk.jfr.Recording; 30 import jdk.jfr.consumer.RecordedEvent; 31 import jdk.jfr.consumer.RecordedThread; 32 import jdk.test.lib.Asserts; 33 import jdk.test.lib.jfr.EventNames; 34 import jdk.test.lib.jfr.Events; 35 36 import java.lang.management.ManagementFactory; 37 import java.time.Duration; 38 import java.time.Instant; 39 import java.util.Comparator; 40 import java.util.Iterator; 41 import java.util.List; 42 import java.util.concurrent.BrokenBarrierException; 43 import java.util.concurrent.CyclicBarrier; 44 import java.util.stream.Collectors; 45 46 /* 47 * @test 48 * @key jfr 49 * @library /test/lib 50 * @modules jdk.jfr 51 * jdk.management 52 * 53 * @run main/othervm jdk.jfr.event.runtime.TestThreadCpuTimeEvent 54 */ 55 56 /** 57 */ 58 public class TestThreadCpuTimeEvent { 59 60 public static void main(String[] args) throws Throwable { 61 testSimple(); 62 testCompareWithMXBean(); 63 testEventAtThreadExit(); 64 } 65 66 private static final long eventPeriodMillis = 50; 67 private static final String cpuConsumerThreadName = "cpuConsumer"; 68 69 // The cpu consumer will run for eventPeriodMillis times this factor to ensure that we see some 70 // events even if the scheduler isn't cooperating. 71 private static final long cpuConsumerRunFactor = 10; 72 73 // The cpu consumer will run at least this number of loops, even if it takes longer than 74 // the requested period of time (in case the thread didn't get scheduled within the allotted time). 75 private static final long cpuConsumerMinCount = 1000000; 76 77 static class CpuConsumingThread extends Thread { 78 79 Duration runTime; 80 CyclicBarrier barrier; 81 volatile long counter; 82 83 CpuConsumingThread(Duration runTime, CyclicBarrier barrier, String threadName) { 84 super(threadName); 85 this.runTime = runTime; 86 this.barrier = barrier; 87 } 88 89 CpuConsumingThread(Duration runTime, CyclicBarrier barrier) { 90 this(runTime, barrier, cpuConsumerThreadName); 91 } 92 93 @Override 94 public void run() { 95 try { 96 while (true) { 97 barrier.await(); 98 Instant start = Instant.now(); 99 counter = 0; 100 while ((Duration.between(start, Instant.now()).compareTo(runTime) < 0) || 101 (counter < cpuConsumerMinCount)) { 102 counter++; 103 } 104 barrier.await(); 105 } 106 } catch (BrokenBarrierException e) { 107 // Another thread has been interrupted - wait for us to be interrupted as well 108 while (!interrupted()) { 109 yield(); 110 } 111 } catch (InterruptedException e) { 112 // Normal way of stopping the thread 113 } 114 } 115 } 116 117 // For a given thread, check that accumulated processTime >= cpuTime >= userTime. 118 // This may not hold for a single event instance due to differences in counter resolution 119 static void verifyPerThreadInvariant(List<RecordedEvent> events, String threadName) { 120 List<RecordedEvent> filteredEvents = events.stream() 121 .filter(e -> e.getThread().getJavaName().equals(threadName)) 122 .sorted(Comparator.comparing(RecordedEvent::getStartTime)) 123 .collect(Collectors.toList()); 124 125 int numCpus = Runtime.getRuntime().availableProcessors(); 126 Iterator<RecordedEvent> i = filteredEvents.iterator(); 127 while (i.hasNext()) { 128 RecordedEvent event = i.next(); 129 130 Float systemLoad = (Float)event.getValue("system"); 131 Float userLoad = (Float)event.getValue("user"); 132 133 Asserts.assertLessThan(systemLoad + userLoad, 1.01f / numCpus); // 100% + rounding errors 134 } 135 } 136 137 static Duration getAccumulatedTime(List<RecordedEvent> events, String threadName, String fieldName) { 138 List<RecordedEvent> filteredEvents = events.stream() 139 .filter(e -> e.getThread().getJavaName().equals(threadName)) 140 .sorted(Comparator.comparing(RecordedEvent::getStartTime)) 141 .collect(Collectors.toList()); 142 143 int numCpus = Runtime.getRuntime().availableProcessors(); 144 Iterator<RecordedEvent> i = filteredEvents.iterator(); 145 RecordedEvent cur = i.next(); 146 Duration totalTime = Duration.ZERO; 147 while (i.hasNext()) { 148 RecordedEvent prev = cur; 149 cur = i.next(); 150 151 Duration sampleTime = Duration.between(prev.getStartTime(), cur.getStartTime()); 152 Float load = (Float)cur.getValue(fieldName); 153 154 // Adjust load to be thread-relative (fully loaded thread would give 100%) 155 Float totalLoadForThread = load * numCpus; 156 Duration threadTime = Duration.ofMillis((long) (sampleTime.toMillis() * totalLoadForThread)); 157 totalTime = totalTime.plus(threadTime); 158 } 159 160 return totalTime; 161 } 162 163 static void testSimple() throws Throwable { 164 Recording recording = new Recording(); 165 166 // Default period is once per chunk 167 recording.enable(EventNames.ThreadCPULoad).withPeriod(Duration.ofMillis(eventPeriodMillis)); 168 recording.start(); 169 170 Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor); 171 CyclicBarrier barrier = new CyclicBarrier(2); 172 CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier); 173 174 // Run a single pass 175 thread.start(); 176 barrier.await(); 177 barrier.await(); 178 179 recording.stop(); 180 List<RecordedEvent> events = Events.fromRecording(recording); 181 182 Events.hasEvents(events); 183 verifyPerThreadInvariant(events, cpuConsumerThreadName); 184 185 thread.interrupt(); 186 thread.join(); 187 } 188 189 static void testCompareWithMXBean() throws Throwable { 190 Recording recording = new Recording(); 191 192 recording.enable(EventNames.ThreadCPULoad).withPeriod(Duration.ofMillis(eventPeriodMillis)); 193 recording.start(); 194 195 Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor); 196 CyclicBarrier barrier = new CyclicBarrier(2); 197 CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier); 198 199 // Run a single pass 200 thread.start(); 201 barrier.await(); 202 barrier.await(); 203 204 recording.stop(); 205 List<RecordedEvent> beforeEvents = Events.fromRecording(recording); 206 207 verifyPerThreadInvariant(beforeEvents, cpuConsumerThreadName); 208 209 // Run a second single pass 210 barrier.await(); 211 barrier.await(); 212 213 ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean(); 214 Duration cpuTime = Duration.ofNanos(bean.getThreadCpuTime(thread.getId())); 215 Duration userTime = Duration.ofNanos(bean.getThreadUserTime(thread.getId())); 216 217 // Check something that should hold even in the presence of unfortunate scheduling 218 Asserts.assertGreaterThanOrEqual(cpuTime.toMillis(), eventPeriodMillis); 219 Asserts.assertGreaterThanOrEqual(userTime.toMillis(), eventPeriodMillis); 220 221 Duration systemTimeBefore = getAccumulatedTime(beforeEvents, cpuConsumerThreadName, "system"); 222 Duration userTimeBefore = getAccumulatedTime(beforeEvents, cpuConsumerThreadName, "user"); 223 Duration cpuTimeBefore = userTimeBefore.plus(systemTimeBefore); 224 225 Asserts.assertLessThan(cpuTimeBefore, cpuTime); 226 Asserts.assertLessThan(userTimeBefore, userTime); 227 Asserts.assertGreaterThan(cpuTimeBefore, Duration.ZERO); 228 229 thread.interrupt(); 230 thread.join(); 231 } 232 233 static void testEventAtThreadExit() throws Throwable { 234 Recording recording = new Recording(); 235 236 recording.enable(EventNames.ThreadCPULoad).withPeriod(Duration.ofHours(10)); 237 recording.start(); 238 239 Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor); 240 CyclicBarrier barrier = new CyclicBarrier(2); 241 CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier); 242 243 // Run a single pass 244 thread.start(); 245 barrier.await(); 246 barrier.await(); 247 248 thread.interrupt(); 249 thread.join(); 250 251 recording.stop(); 252 253 List<RecordedEvent> events = Events.fromRecording(recording); 254 verifyPerThreadInvariant(events, cpuConsumerThreadName); 255 256 int exitingCount = 0; 257 for (RecordedEvent event : events) { 258 RecordedThread eventThread = event.getThread(); 259 if (eventThread.getJavaName().equals(cpuConsumerThreadName)) { 260 exitingCount++; 261 } 262 } 263 Asserts.assertEquals(exitingCount, 1); 264 } 265 }