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  * @requires vm.hasJFR
  50  * @library /test/lib
  51  * @modules jdk.jfr
  52  *          jdk.management
  53  *
  54  * @run main/othervm jdk.jfr.event.runtime.TestThreadCpuTimeEvent
  55  */
  56 
  57 /**
  58  */
  59 public class TestThreadCpuTimeEvent {
  60 
  61     public static void main(String[] args) throws Throwable {
  62         testSimple();
  63         testCompareWithMXBean();
  64         testEventAtThreadExit();
  65     }
  66 
  67     private static final long eventPeriodMillis = 50;
  68     private static final String cpuConsumerThreadName = "cpuConsumer";
  69 
  70     // The cpu consumer will run for eventPeriodMillis times this factor to ensure that we see some
  71     // events even if the scheduler isn't cooperating.
  72     private static final long cpuConsumerRunFactor = 10;
  73 
  74     // The cpu consumer will run at least this number of loops, even if it takes longer than
  75     // the requested period of time (in case the thread didn't get scheduled within the allotted time).
  76     private static final long cpuConsumerMinCount = 1000000;
  77 
  78     static class CpuConsumingThread extends Thread {
  79 
  80         Duration runTime;
  81         CyclicBarrier barrier;
  82         volatile long counter;
  83 
  84         CpuConsumingThread(Duration runTime, CyclicBarrier barrier, String threadName) {
  85             super(threadName);
  86             this.runTime = runTime;
  87             this.barrier = barrier;
  88         }
  89 
  90         CpuConsumingThread(Duration runTime, CyclicBarrier barrier) {
  91             this(runTime, barrier, cpuConsumerThreadName);
  92         }
  93 
  94         @Override
  95         public void run() {
  96             try {
  97                 while (true) {
  98                     barrier.await();
  99                     Instant start = Instant.now();
 100                     counter = 0;
 101                     while ((Duration.between(start, Instant.now()).compareTo(runTime) < 0) ||
 102                             (counter < cpuConsumerMinCount)) {
 103                         counter++;
 104                     }
 105                     barrier.await();
 106                 }
 107             } catch (BrokenBarrierException e) {
 108                 // Another thread has been interrupted - wait for us to be interrupted as well
 109                 while (!interrupted()) {
 110                     yield();
 111                 }
 112             } catch (InterruptedException e) {
 113                 // Normal way of stopping the thread
 114             }
 115         }
 116     }
 117 
 118     // For a given thread, check that accumulated processTime >= cpuTime >= userTime.
 119     // This may not hold for a single event instance due to differences in counter resolution
 120     static void verifyPerThreadInvariant(List<RecordedEvent> events, String threadName) {
 121         List<RecordedEvent> filteredEvents = events.stream()
 122                 .filter(e -> e.getThread().getJavaName().equals(threadName))
 123                 .sorted(Comparator.comparing(RecordedEvent::getStartTime))
 124                 .collect(Collectors.toList());
 125 
 126         int numCpus = Runtime.getRuntime().availableProcessors();
 127         Iterator<RecordedEvent> i = filteredEvents.iterator();
 128         while (i.hasNext()) {
 129             RecordedEvent event = i.next();
 130 
 131             Float systemLoad = (Float)event.getValue("system");
 132             Float userLoad = (Float)event.getValue("user");
 133 
 134             Asserts.assertLessThan(systemLoad + userLoad, 1.01f / numCpus); // 100% + rounding errors
 135         }
 136     }
 137 
 138     static Duration getAccumulatedTime(List<RecordedEvent> events, String threadName, String fieldName) {
 139         List<RecordedEvent> filteredEvents = events.stream()
 140                 .filter(e -> e.getThread().getJavaName().equals(threadName))
 141                 .sorted(Comparator.comparing(RecordedEvent::getStartTime))
 142                 .collect(Collectors.toList());
 143 
 144         int numCpus = Runtime.getRuntime().availableProcessors();
 145         Iterator<RecordedEvent> i = filteredEvents.iterator();
 146         RecordedEvent cur = i.next();
 147         Duration totalTime = Duration.ZERO;
 148         while (i.hasNext()) {
 149             RecordedEvent prev = cur;
 150             cur = i.next();
 151 
 152             Duration sampleTime = Duration.between(prev.getStartTime(), cur.getStartTime());
 153             Float load = (Float)cur.getValue(fieldName);
 154 
 155             // Adjust load to be thread-relative (fully loaded thread would give 100%)
 156             Float totalLoadForThread = load * numCpus;
 157             Duration threadTime = Duration.ofMillis((long) (sampleTime.toMillis() * totalLoadForThread));
 158             totalTime = totalTime.plus(threadTime);
 159         }
 160 
 161         return totalTime;
 162     }
 163 
 164     static List<RecordedEvent> generateEvents(int minimumEventCount, CyclicBarrier barrier) throws Throwable {
 165         int retryCount = 0;
 166 
 167         while (true) {
 168             Recording recording = new Recording();
 169 
 170             // Default period is once per chunk
 171             recording.enable(EventNames.ThreadCPULoad).withPeriod(Duration.ofMillis(eventPeriodMillis));
 172             recording.start();
 173 
 174             // Run a single pass
 175             barrier.await();
 176             barrier.await();
 177 
 178             recording.stop();
 179             List<RecordedEvent> events = Events.fromRecording(recording);
 180 
 181             long numEvents = events.stream()
 182                     .filter(e -> e.getThread().getJavaName().equals(cpuConsumerThreadName))
 183                     .count();
 184 
 185             // If the JFR periodicals thread is really starved, we may not get enough events.
 186             // In that case, we simply retry the operation.
 187             if (numEvents < minimumEventCount) {
 188                 System.out.println("Not enough events recorded, trying again...");
 189                 if (retryCount++ > 10) {
 190                     Asserts.fail("Retry count exceeded");
 191                     throw new RuntimeException();
 192                 }
 193             } else {
 194                 return events;
 195             }
 196         }
 197     }
 198 
 199     static void testSimple() throws Throwable {
 200         Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor);
 201         CyclicBarrier barrier = new CyclicBarrier(2);
 202         CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier);
 203         thread.start();
 204 
 205         List<RecordedEvent> events = generateEvents(1, barrier);
 206         verifyPerThreadInvariant(events, cpuConsumerThreadName);
 207 
 208         thread.interrupt();
 209         thread.join();
 210     }
 211 
 212     static void testCompareWithMXBean() throws Throwable {
 213         Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor);
 214         CyclicBarrier barrier = new CyclicBarrier(2);
 215         CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier);
 216         thread.start();
 217 
 218         List<RecordedEvent> beforeEvents = generateEvents(2, barrier);
 219         verifyPerThreadInvariant(beforeEvents, cpuConsumerThreadName);
 220 
 221         // Run a second single pass
 222         barrier.await();
 223         barrier.await();
 224 
 225         ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
 226         Duration cpuTime = Duration.ofNanos(bean.getThreadCpuTime(thread.getId()));
 227         Duration userTime = Duration.ofNanos(bean.getThreadUserTime(thread.getId()));
 228 
 229         // Check something that should hold even in the presence of unfortunate scheduling
 230         Asserts.assertGreaterThanOrEqual(cpuTime.toMillis(), eventPeriodMillis);
 231         Asserts.assertGreaterThanOrEqual(userTime.toMillis(), eventPeriodMillis);
 232 
 233         Duration systemTimeBefore = getAccumulatedTime(beforeEvents, cpuConsumerThreadName, "system");
 234         Duration userTimeBefore = getAccumulatedTime(beforeEvents, cpuConsumerThreadName, "user");
 235         Duration cpuTimeBefore = userTimeBefore.plus(systemTimeBefore);
 236 
 237         Asserts.assertLessThan(cpuTimeBefore, cpuTime);
 238         Asserts.assertLessThan(userTimeBefore, userTime);
 239         Asserts.assertGreaterThan(cpuTimeBefore, Duration.ZERO);
 240 
 241         thread.interrupt();
 242         thread.join();
 243     }
 244 
 245     static void testEventAtThreadExit() throws Throwable {
 246         Recording recording = new Recording();
 247 
 248         recording.enable(EventNames.ThreadCPULoad).withPeriod(Duration.ofHours(10));
 249         recording.start();
 250 
 251         Duration testRunTime = Duration.ofMillis(eventPeriodMillis * cpuConsumerRunFactor);
 252         CyclicBarrier barrier = new CyclicBarrier(2);
 253         CpuConsumingThread thread = new CpuConsumingThread(testRunTime, barrier);
 254 
 255         // Run a single pass
 256         thread.start();
 257         barrier.await();
 258         barrier.await();
 259 
 260         thread.interrupt();
 261         thread.join();
 262 
 263         recording.stop();
 264 
 265         List<RecordedEvent> events = Events.fromRecording(recording);
 266         verifyPerThreadInvariant(events, cpuConsumerThreadName);
 267 
 268         int exitingCount = 0;
 269         for (RecordedEvent event : events) {
 270             RecordedThread eventThread = event.getThread();
 271             if (eventThread.getJavaName().equals(cpuConsumerThreadName)) {
 272                 exitingCount++;
 273             }
 274         }
 275         Asserts.assertEquals(exitingCount, 1);
 276     }
 277 }