1 /* 2 * Copyright (c) 2013, 2014, 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.debug; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.io.PrintStream; 31 import java.util.Iterator; 32 import java.util.Locale; 33 import java.util.Map; 34 import java.util.Map.Entry; 35 import java.util.Set; 36 import java.util.TreeMap; 37 import java.util.TreeSet; 38 import java.util.concurrent.ConcurrentHashMap; 39 import java.util.concurrent.atomic.AtomicLong; 40 41 import org.graalvm.compiler.core.common.SuppressFBWarnings; 42 import org.graalvm.compiler.debug.CSVUtil; 43 import org.graalvm.compiler.debug.GraalError; 44 import org.graalvm.compiler.debug.TTY; 45 import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig; 46 import org.graalvm.compiler.nodes.debug.DynamicCounterNode; 47 import org.graalvm.compiler.options.Option; 48 import org.graalvm.compiler.options.OptionKey; 49 import org.graalvm.compiler.options.OptionType; 50 import org.graalvm.compiler.options.OptionValues; 51 52 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; 53 54 //JaCoCo Exclude 55 56 /** 57 * This class contains infrastructure to maintain counters based on {@link DynamicCounterNode}s. The 58 * infrastructure is enabled by specifying either the GenericDynamicCounters or 59 * BenchmarkDynamicCounters option. 60 * <p> 61 * 62 * The counters are kept in a special area allocated for each native JavaThread object, and the 63 * number of counters is configured using {@code -XX:JVMCICounterSize=value}. 64 * {@code -XX:+/-JVMCICountersExcludeCompiler} configures whether to exclude compiler threads 65 * (defaults to true). 66 * 67 * The subsystems that use the logging need to have their own options to turn on the counters, and 68 * insert DynamicCounterNodes when they're enabled. 69 * 70 * Counters will be displayed as a rate (per second) if their group name starts with "~", otherwise 71 * they will be displayed as a total number. 72 * 73 * See <a href="BenchmarkDynamicCountersHelp.txt">here</a> for a detailed example of how to use 74 * benchmark counters. 75 */ 76 public class BenchmarkCounters { 77 78 public static class Options { 79 80 //@formatter:off 81 @Option(help = "Turn on the benchmark counters, and displays the results on VM shutdown", type = OptionType.Debug) 82 public static final OptionKey<Boolean> GenericDynamicCounters = new OptionKey<>(false); 83 @Option(help = "Turn on the benchmark counters, and displays the results every n milliseconds", type = OptionType.Debug) 84 public static final OptionKey<Integer> TimedDynamicCounters = new OptionKey<>(-1); 85 86 @Option(help = "file:doc-files/BenchmarkDynamicCountersHelp.txt", type = OptionType.Debug) 87 public static final OptionKey<String> BenchmarkDynamicCounters = new OptionKey<>(null); 88 @Option(help = "Use grouping separators for number printing", type = OptionType.Debug) 89 public static final OptionKey<Boolean> DynamicCountersPrintGroupSeparator = new OptionKey<>(true); 90 @Option(help = "File to which benchmark counters are dumped. A CSV format is used if the file ends with .csv " + 91 "otherwise a more human readable format is used. The fields in the CSV format are: " + 92 "category, group, name, value", type = OptionType.Debug) 93 public static final OptionKey<String> BenchmarkCountersFile = new OptionKey<>(null); 94 @Option(help = "Dump dynamic counters", type = OptionType.Debug) 95 public static final OptionKey<Boolean> BenchmarkCountersDumpDynamic = new OptionKey<>(true); 96 @Option(help = "Dump static counters", type = OptionType.Debug) 97 public static final OptionKey<Boolean> BenchmarkCountersDumpStatic = new OptionKey<>(false); 98 @Option(help = "file:doc-files/AbortOnBenchmarkCounterOverflowHelp.txt", type = OptionType.Debug) 99 public static final OptionKey<Boolean> AbortOnBenchmarkCounterOverflow = new OptionKey<>(false); 100 //@formatter:on 101 } 102 103 public static boolean enabled = false; 104 105 private static class Counter { 106 public final int index; 107 public final String group; 108 public final AtomicLong staticCounters; 109 110 Counter(int index, String group, AtomicLong staticCounters) { 111 this.index = index; 112 this.group = group; 113 this.staticCounters = staticCounters; 114 } 115 } 116 117 public static final ConcurrentHashMap<String, Counter> counterMap = new ConcurrentHashMap<>(); 118 public static long[] delta; 119 120 public static int getIndexConstantIncrement(String name, String group, GraalHotSpotVMConfig config, long increment) { 121 Counter counter = getCounter(name, group, config); 122 counter.staticCounters.addAndGet(increment); 123 return counter.index; 124 } 125 126 public static int getIndex(String name, String group, GraalHotSpotVMConfig config) { 127 Counter counter = getCounter(name, group, config); 128 return counter.index; 129 } 130 131 @SuppressFBWarnings(value = "AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION", justification = "concurrent abstraction calls are in synchronized block") 132 private static Counter getCounter(String name, String group, GraalHotSpotVMConfig config) throws GraalError { 133 if (!enabled) { 134 throw new GraalError("cannot access count index when counters are not enabled: " + group + ", " + name); 135 } 136 String nameGroup = name + "#" + group; 137 Counter counter = counterMap.get(nameGroup); 138 if (counter == null) { 139 synchronized (BenchmarkCounters.class) { 140 counter = counterMap.get(nameGroup); 141 if (counter == null) { 142 counter = new Counter(counterMap.size(), group, new AtomicLong()); 143 counterMap.put(nameGroup, counter); 144 } 145 } 146 } 147 assert counter.group.equals(group) : "mismatching groups: " + counter.group + " vs. " + group; 148 int countersSize = config.jvmciCountersSize; 149 if (counter.index >= countersSize) { 150 throw new GraalError("too many counters, reduce number of counters or increase -XX:JVMCICounterSize=... (current value: " + countersSize + ")"); 151 } 152 return counter; 153 } 154 155 private static synchronized void dump(OptionValues options, PrintStream out, double seconds, long[] counters, int maxRows) { 156 if (!counterMap.isEmpty()) { 157 try (Dumper dumper = Dumper.getDumper(options, out, counterMap.size(), seconds, maxRows)) { 158 TreeSet<String> set = new TreeSet<>(); 159 counterMap.forEach((nameGroup, counter) -> set.add(counter.group)); 160 for (String group : set) { 161 if (group != null) { 162 if (Options.BenchmarkCountersDumpStatic.getValue(options)) { 163 dumper.dumpCounters(true, group, collectStaticCounters(), counterMap.entrySet(), options); 164 } 165 if (Options.BenchmarkCountersDumpDynamic.getValue(options)) { 166 dumper.dumpCounters(false, group, collectDynamicCounters(counters), counterMap.entrySet(), options); 167 } 168 } 169 } 170 } 171 172 clear(counters); 173 } 174 } 175 176 private static synchronized long[] collectDynamicCounters(long[] counters) { 177 long[] array = counters.clone(); 178 for (int i = 0; i < array.length; i++) { 179 array[i] -= delta[i]; 180 } 181 return array; 182 } 183 184 private static synchronized long[] collectStaticCounters() { 185 long[] array = new long[counterMap.size()]; 186 for (Counter counter : counterMap.values()) { 187 array[counter.index] = counter.staticCounters.get(); 188 } 189 return array; 190 } 191 192 private static synchronized void clear(long[] counters) { 193 delta = counters; 194 } 195 196 private static boolean shouldDumpComputerReadable(OptionValues options) { 197 String dumpFile = Options.BenchmarkCountersFile.getValue(options); 198 return dumpFile != null && (dumpFile.endsWith(".csv") || dumpFile.endsWith(".CSV")); 199 } 200 201 private abstract static class Dumper implements AutoCloseable { 202 public static Dumper getDumper(OptionValues options, PrintStream out, int counterSize, double second, int maxRows) { 203 Dumper dumper = shouldDumpComputerReadable(options) ? new ComputerReadableDumper(out) : new HumanReadableDumper(out, second, maxRows); 204 dumper.start(counterSize); 205 return dumper; 206 } 207 208 protected final PrintStream out; 209 210 private Dumper(PrintStream out) { 211 this.out = out; 212 } 213 214 protected abstract void start(int size); 215 216 public abstract void dumpCounters(boolean staticCounter, String group, long[] array, Set<Entry<String, Counter>> counterEntrySet, OptionValues options); 217 218 @Override 219 public abstract void close(); 220 221 } 222 223 private static String getName(String nameGroup, String group) { 224 return nameGroup.substring(0, nameGroup.length() - group.length() - 1); 225 } 226 227 private static long percentage(long counter, long sum) { 228 return (counter * 200 + 1) / sum / 2; 229 } 230 231 private static class HumanReadableDumper extends Dumper { 232 private final double seconds; 233 private final int maxRows; 234 235 HumanReadableDumper(PrintStream out, double seconds, int maxRows) { 236 super(out); 237 this.seconds = seconds; 238 this.maxRows = maxRows; 239 } 240 241 @Override 242 public void start(int size) { 243 out.println("====== dynamic counters (" + size + " in total) ======"); 244 } 245 246 @Override 247 public void close() { 248 out.println("============================"); 249 } 250 251 @Override 252 public void dumpCounters(boolean staticCounter, String group, long[] array, Set<Entry<String, Counter>> counterEntrySet, OptionValues options) { 253 // sort the counters by putting them into a sorted map 254 TreeMap<Long, String> sorted = new TreeMap<>(); 255 long sum = 0; 256 for (Map.Entry<String, Counter> entry : counterEntrySet) { 257 Counter counter = entry.getValue(); 258 int index = counter.index; 259 if (counter.group.equals(group)) { 260 sum += array[index]; 261 sorted.put(array[index] * array.length + index, getName(entry.getKey(), group)); 262 } 263 } 264 265 if (sum > 0) { 266 long cutoff = sorted.size() < 10 ? 1 : Math.max(1, sum / 100); 267 int cnt = sorted.size(); 268 269 // remove everything below cutoff and keep at most maxRows 270 Iterator<Map.Entry<Long, String>> iter = sorted.entrySet().iterator(); 271 while (iter.hasNext()) { 272 Map.Entry<Long, String> entry = iter.next(); 273 long counter = entry.getKey() / array.length; 274 if (counter < cutoff || cnt > maxRows) { 275 iter.remove(); 276 } 277 cnt--; 278 } 279 280 String numFmt = Options.DynamicCountersPrintGroupSeparator.getValue(options) ? "%,19d" : "%19d"; 281 if (staticCounter) { 282 out.println("=========== " + group + " (static counters):"); 283 for (Map.Entry<Long, String> entry : sorted.entrySet()) { 284 long counter = entry.getKey() / array.length; 285 out.format(Locale.US, numFmt + " %3d%% %s\n", counter, percentage(counter, sum), entry.getValue()); 286 } 287 out.format(Locale.US, numFmt + " total\n", sum); 288 } else { 289 if (group.startsWith("~")) { 290 out.println("=========== " + group + " (dynamic counters), time = " + seconds + " s:"); 291 for (Map.Entry<Long, String> entry : sorted.entrySet()) { 292 long counter = entry.getKey() / array.length; 293 out.format(Locale.US, numFmt + "/s %3d%% %s\n", (long) (counter / seconds), percentage(counter, sum), entry.getValue()); 294 } 295 out.format(Locale.US, numFmt + "/s total\n", (long) (sum / seconds)); 296 } else { 297 out.println("=========== " + group + " (dynamic counters):"); 298 for (Map.Entry<Long, String> entry : sorted.entrySet()) { 299 long counter = entry.getKey() / array.length; 300 out.format(Locale.US, numFmt + " %3d%% %s\n", counter, percentage(counter, sum), entry.getValue()); 301 } 302 out.format(Locale.US, numFmt + " total\n", sum); 303 } 304 } 305 } 306 } 307 } 308 309 private static final String CSV_FMT = CSVUtil.buildFormatString("%s", "%s", "%s", "%d"); 310 311 private static class ComputerReadableDumper extends Dumper { 312 313 ComputerReadableDumper(PrintStream out) { 314 super(out); 315 } 316 317 @Override 318 public void start(int size) { 319 // do nothing 320 } 321 322 @Override 323 public void close() { 324 // do nothing 325 } 326 327 @Override 328 public void dumpCounters(boolean staticCounter, String group, long[] array, Set<Entry<String, Counter>> counterEntrySet, OptionValues options) { 329 String category = staticCounter ? "static counters" : "dynamic counters"; 330 for (Map.Entry<String, Counter> entry : counterEntrySet) { 331 Counter counter = entry.getValue(); 332 if (counter.group.equals(group)) { 333 String name = getName(entry.getKey(), group); 334 int index = counter.index; 335 long value = array[index]; 336 CSVUtil.Escape.println(out, CSV_FMT, category, group, name, value); 337 } 338 } 339 } 340 } 341 342 private abstract static class CallbackOutputStream extends OutputStream { 343 344 protected final PrintStream delegate; 345 private final byte[][] patterns; 346 private final int[] positions; 347 348 CallbackOutputStream(PrintStream delegate, String... patterns) { 349 this.delegate = delegate; 350 this.positions = new int[patterns.length]; 351 this.patterns = new byte[patterns.length][]; 352 for (int i = 0; i < patterns.length; i++) { 353 this.patterns[i] = patterns[i].getBytes(); 354 } 355 } 356 357 protected abstract void patternFound(int index); 358 359 @Override 360 public void write(int b) throws IOException { 361 try { 362 delegate.write(b); 363 for (int i = 0; i < patterns.length; i++) { 364 int j = positions[i]; 365 byte[] cs = patterns[i]; 366 byte patternChar = cs[j]; 367 if (patternChar == '~' && Character.isDigit(b)) { 368 // nothing to do... 369 } else { 370 if (patternChar == '~') { 371 patternChar = cs[++positions[i]]; 372 } 373 if (b == patternChar) { 374 positions[i]++; 375 } else { 376 positions[i] = 0; 377 } 378 } 379 if (positions[i] == patterns[i].length) { 380 positions[i] = 0; 381 patternFound(i); 382 } 383 } 384 } catch (RuntimeException e) { 385 e.printStackTrace(delegate); 386 throw e; 387 } 388 } 389 } 390 391 public static void initialize(final HotSpotJVMCIRuntime jvmciRuntime, OptionValues options) { 392 final class BenchmarkCountersOutputStream extends CallbackOutputStream { 393 394 private long startTime; 395 private boolean running; 396 private boolean waitingForEnd; 397 398 private BenchmarkCountersOutputStream(PrintStream delegate, String start, String end) { 399 super(delegate, new String[]{"\n", end, start}); 400 } 401 402 @Override 403 protected void patternFound(int index) { 404 switch (index) { 405 case 2: 406 startTime = System.nanoTime(); 407 BenchmarkCounters.clear(jvmciRuntime.collectCounters()); 408 running = true; 409 break; 410 case 1: 411 if (running) { 412 waitingForEnd = true; 413 } 414 break; 415 case 0: 416 if (waitingForEnd) { 417 waitingForEnd = false; 418 running = false; 419 BenchmarkCounters.dump(options, getPrintStream(options), (System.nanoTime() - startTime) / 1000000000d, jvmciRuntime.collectCounters(), 100); 420 } 421 break; 422 } 423 } 424 } 425 426 if (Options.BenchmarkDynamicCounters.getValue(options) != null) { 427 String[] arguments = Options.BenchmarkDynamicCounters.getValue(options).split(","); 428 if (arguments.length == 0 || (arguments.length % 3) != 0) { 429 throw new GraalError("invalid arguments to BenchmarkDynamicCounters: (err|out),start,end,(err|out),start,end,... (~ matches multiple digits)"); 430 } 431 for (int i = 0; i < arguments.length; i += 3) { 432 if (arguments[i].equals("err")) { 433 System.setErr(new PrintStream(new BenchmarkCountersOutputStream(System.err, arguments[i + 1], arguments[i + 2]))); 434 } else if (arguments[i].equals("out")) { 435 System.setOut(new PrintStream(new BenchmarkCountersOutputStream(System.out, arguments[i + 1], arguments[i + 2]))); 436 } else { 437 throw new GraalError("invalid arguments to BenchmarkDynamicCounters: err|out"); 438 } 439 } 440 enabled = true; 441 } 442 if (Options.GenericDynamicCounters.getValue(options)) { 443 enabled = true; 444 } 445 if (Options.TimedDynamicCounters.getValue(options) > 0) { 446 Thread thread = new Thread() { 447 long lastTime = System.nanoTime(); 448 PrintStream out = getPrintStream(options); 449 450 @Override 451 public void run() { 452 while (true) { 453 try { 454 Thread.sleep(Options.TimedDynamicCounters.getValue(options)); 455 } catch (InterruptedException e) { 456 } 457 long time = System.nanoTime(); 458 dump(options, out, (time - lastTime) / 1000000000d, jvmciRuntime.collectCounters(), 10); 459 lastTime = time; 460 } 461 } 462 }; 463 thread.setDaemon(true); 464 thread.setPriority(Thread.MAX_PRIORITY); 465 thread.start(); 466 enabled = true; 467 } 468 if (enabled) { 469 clear(jvmciRuntime.collectCounters()); 470 } 471 } 472 473 public static void shutdown(HotSpotJVMCIRuntime jvmciRuntime, OptionValues options, long compilerStartTime) { 474 if (Options.GenericDynamicCounters.getValue(options)) { 475 dump(options, getPrintStream(options), (System.nanoTime() - compilerStartTime) / 1000000000d, jvmciRuntime.collectCounters(), 100); 476 } 477 } 478 479 private static PrintStream getPrintStream(OptionValues options) { 480 if (Options.BenchmarkCountersFile.getValue(options) != null) { 481 try { 482 483 File file = new File(Options.BenchmarkCountersFile.getValue(options)); 484 TTY.println("Writing benchmark counters to '%s'", file.getAbsolutePath()); 485 return new PrintStream(file); 486 } catch (IOException e) { 487 TTY.out().println(e.getMessage()); 488 TTY.out().println("Fallback to default"); 489 } 490 } 491 return TTY.out; 492 } 493 }