1 /*
   2  * Copyright (c) 2016, 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 package org.graalvm.compiler.hotspot;
  24 
  25 import static org.graalvm.compiler.hotspot.HotSpotGraalCompiler.fmt;
  26 import static org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory.GRAAL_OPTION_PROPERTY_PREFIX;
  27 
  28 import java.util.Arrays;
  29 import java.util.HashMap;
  30 import java.util.Map;
  31 import java.util.concurrent.atomic.AtomicInteger;
  32 
  33 import org.graalvm.compiler.debug.TTY;
  34 import org.graalvm.compiler.options.Option;
  35 import org.graalvm.compiler.options.OptionType;
  36 import org.graalvm.compiler.options.OptionValue;
  37 
  38 import jdk.vm.ci.code.CompilationRequest;
  39 
  40 /**
  41  * A watch dog that monitors the duration and compilation rate during a
  42  * {@linkplain HotSpotGraalRuntimeProvider#isBootstrapping() bootstrap}. If time spent bootstrapping
  43  * exceeds a specified timeout or the compilation rate falls below a given ratio of the maximum
  44  * observed compilation rate (i.e., compilation slows down too much), the compiler will ignore all
  45  * subsequent compilation requests, effectively draining the bootstrap completion queue and
  46  * expediting completion of bootstrap. Note that the compilation rate is computed over the whole
  47  * execution, not just the most recent measurement period. This means a sudden but temporary drop in
  48  * any given measurement period won't cause bootstrapping to terminate early.
  49  *
  50  * This mechanism is based on past observations that a significantly falling bootstrap compilation
  51  * rate implies a configuration where bootstrapping will take an unreasonably long time and it's
  52  * better to drain the bootstrap compilation queue at some point that risk triggering timeouts in
  53  * external harnesses such as integration tests.
  54  */
  55 final class BootstrapWatchDog extends Thread {
  56 
  57     public static class Options {
  58         // @formatter:off
  59         @Option(help = "Ratio of the maximum compilation rate below which the bootstrap compilation rate must not fall " +
  60                        "(0 or less disables monitoring).", type = OptionType.Debug)
  61         public static final OptionValue<Double> BootstrapWatchDogCriticalRateRatio = new OptionValue<>(0.25D);
  62         @Option(help = "Maximum time in minutes to spend bootstrapping (0 to disable this limit).", type = OptionType.Debug)
  63         public static final OptionValue<Double> BootstrapTimeout = new OptionValue<>(15D);
  64         // @formatter:on
  65     }
  66 
  67     /**
  68      * Count of completed compilations. This is updated by the compiler threads and read by the
  69      * watch dog thread.
  70      */
  71     private final AtomicInteger compilations = new AtomicInteger();
  72 
  73     /**
  74      * Set to true once the compilation rate drops too low or bootstrapping times out.
  75      */
  76     private boolean hitCriticalRateOrTimeout;
  77 
  78     /**
  79      * The maximum compilation rate seen during execution.
  80      */
  81     private double maxRate;
  82 
  83     private final HotSpotGraalRuntimeProvider graalRuntime;
  84 
  85     /**
  86      * Creates and returns a {@link BootstrapWatchDog} if
  87      * {@link Options#BootstrapWatchDogCriticalRateRatio} is not set to 0 otherwise returns
  88      * {@code null}.
  89      */
  90     static BootstrapWatchDog maybeCreate(HotSpotGraalRuntimeProvider graalRuntime) {
  91         return MAX_RATE_DECREASE <= 0.0D && TIMEOUT == 0 ? null : new BootstrapWatchDog(graalRuntime);
  92     }
  93 
  94     private BootstrapWatchDog(HotSpotGraalRuntimeProvider graalRuntime) {
  95         this.setName(getClass().getSimpleName());
  96         this.start();
  97         this.graalRuntime = graalRuntime;
  98     }
  99 
 100     /**
 101      * Set to true to debug the watch dog.
 102      */
 103     private static final boolean DEBUG = Boolean.getBoolean("debug.graal.BootstrapWatchDog");
 104 
 105     /**
 106      * Seconds to delay before starting to measure the compilation rate.
 107      */
 108     private static final int INITIAL_DELAY = 10;
 109 
 110     /**
 111      * Seconds between each compilation rate measurement.
 112      */
 113     private static final long EPOCH = 5;
 114 
 115     /**
 116      * Time in seconds before stopping a bootstrap.
 117      */
 118     private static final int TIMEOUT = (int) (Options.BootstrapTimeout.getValue() * 60);
 119 
 120     /**
 121      * The watch dog {@link #hitCriticalCompilationRateOrTimeout() hits} a critical compilation rate
 122      * if the current compilation rate falls below this ratio of the maximum compilation rate.
 123      */
 124     private static final double MAX_RATE_DECREASE = Options.BootstrapWatchDogCriticalRateRatio.getValue();
 125 
 126     @Override
 127     public void run() {
 128         if (DEBUG) {
 129             TTY.printf("%nStarted %s%n", this);
 130         }
 131         long start = System.currentTimeMillis();
 132         Map<Thread, Watch> requestsAtTimeout = null;
 133         Map<Thread, StackTraceElement[]> stacksAtTimeout = null;
 134         try {
 135             Thread.sleep(INITIAL_DELAY * 1000);
 136             while (true) {
 137                 int currentCompilations = compilations.get();
 138                 long elapsed = System.currentTimeMillis() - start;
 139                 double rate = currentCompilations / seconds(elapsed);
 140                 if (DEBUG) {
 141                     TTY.printf("%.2f: compilation rate is %.2f/sec%n", seconds(elapsed), rate);
 142                 }
 143                 if (rate > maxRate) {
 144                     maxRate = rate;
 145                 } else if (rate < (maxRate * MAX_RATE_DECREASE)) {
 146                     TTY.printf("%nAfter %.2f seconds bootstrapping, compilation rate is %.2f compilations per second " +
 147                                     "which is below %.2f times the max compilation rate of %.2f%n", seconds(elapsed), rate, MAX_RATE_DECREASE, maxRate);
 148                     TTY.printf("To enable monitoring of long running individual compilations, re-run with -D%s%s=%.2f%n",
 149                                     GRAAL_OPTION_PROPERTY_PREFIX, CompilationWatchDog.Options.CompilationWatchDogStartDelay.getName(),
 150                                     seconds(elapsed) - 5);
 151                     hitCriticalRateOrTimeout = true;
 152                     return;
 153                 }
 154                 if (elapsed > TIMEOUT * 1000) {
 155                     if (requestsAtTimeout == null) {
 156                         requestsAtTimeout = snapshotRequests();
 157                         stacksAtTimeout = new HashMap<>();
 158                         for (Thread t : requestsAtTimeout.keySet()) {
 159                             stacksAtTimeout.put(t, t.getStackTrace());
 160                         }
 161                     } else {
 162                         TTY.printf("%nHit bootstrapping timeout after %.2f seconds%n", seconds(elapsed));
 163                         Map<Thread, Watch> requestsNow = snapshotRequests();
 164                         for (Map.Entry<Thread, Watch> e : requestsAtTimeout.entrySet()) {
 165                             Thread t = e.getKey();
 166                             CompilationRequest request1 = requestsAtTimeout.get(t).request;
 167                             CompilationRequest request2 = requestsNow.get(t).request;
 168                             if (request1 != null && request1 == request2) {
 169                                 StackTraceElement[] stackTraceNow = t.getStackTrace();
 170                                 TTY.printf("Printing stack trace for current compilation of %s lasting more than %d seconds:%n%s",
 171                                                 fmt(request1.getMethod()), EPOCH, fmt(stackTraceNow));
 172                                 if (Arrays.equals(stacksAtTimeout.get(t), stackTraceNow)) {
 173                                     TTY.printf("\t** Identical stack trace %d seconds ago, implying a hung compilation **%n",
 174                                                     EPOCH);
 175                                 }
 176                             } else {
 177                                 if (DEBUG) {
 178                                     TTY.printf("%s was compiling %s%n", t, fmt(request1.getMethod()));
 179                                 }
 180                             }
 181                         }
 182                         hitCriticalRateOrTimeout = true;
 183                         return;
 184                     }
 185                 }
 186                 if (!graalRuntime.isBootstrapping()) {
 187                     return;
 188                 }
 189 
 190                 Thread.sleep(EPOCH * 1000);
 191             }
 192         } catch (InterruptedException e) {
 193             e.printStackTrace(TTY.out);
 194         }
 195     }
 196 
 197     private Map<Thread, Watch> snapshotRequests() {
 198         synchronized (requests) {
 199             return new HashMap<>(requests);
 200         }
 201     }
 202 
 203     private static double seconds(long ms) {
 204         return (double) ms / 1000;
 205     }
 206 
 207     /**
 208      * Queries whether a critically low compilation rate or {@link #TIMEOUT} occurred.
 209      */
 210     boolean hitCriticalCompilationRateOrTimeout() {
 211         return hitCriticalRateOrTimeout;
 212     }
 213 
 214     private final Map<Thread, Watch> requests = new HashMap<>();
 215     private final ThreadLocal<Watch> requestForThread = new ThreadLocal<>();
 216 
 217     /**
 218      * Opens a scope for watching the compilation of a given method.
 219      *
 220      * @param request a compilation request about to be processed
 221      * @return {@code null} if the compilation watch dog is disabled otherwise this object. The
 222      *         returned value should be used in a {@code try}-with-resources statement whose scope
 223      *         is the whole compilation so that leaving the scope will cause {@link Watch#close()}
 224      *         to be called.
 225      */
 226     Watch watch(CompilationRequest request) {
 227         Watch watch = requestForThread.get();
 228         if (watch == null) {
 229             watch = new Watch();
 230             synchronized (requests) {
 231                 requests.put(Thread.currentThread(), watch);
 232             }
 233         }
 234         watch.open(request);
 235         return watch;
 236     }
 237 
 238     /**
 239      * Object for watching the compilations requests of a single compiler thread.
 240      */
 241     class Watch implements AutoCloseable {
 242         CompilationRequest request;
 243 
 244         void open(CompilationRequest r) {
 245             assert this.request == null;
 246             this.request = r;
 247         }
 248 
 249         @Override
 250         public void close() {
 251             compilations.incrementAndGet();
 252             request = null;
 253         }
 254     }
 255 }