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