1 /*
   2  * Copyright (c) 2017, 2019, 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.testlibrary.Asserts;
  33 import jdk.testlibrary.jfr.EventNames;
  34 import jdk.testlibrary.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 /lib/testlibrary
  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 }