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 
  24 /*
  25  * @test TestDynamicGCThreadsStats
  26  * @bug 8176140
  27  * @summary Check that G1 reports per thread statistics.
  28  * @requires vm.gc=="G1" | vm.gc=="null"
  29  * @key gc
  30  * @library /testlibrary /test/lib
  31  * @build sun.hotspot.WhiteBox
  32  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  33  * @run driver TestDynamicGCThreadsStats
  34  */
  35 
  36 import sun.hotspot.WhiteBox;
  37 
  38 import java.text.DecimalFormatSymbols;
  39 import java.util.regex.Matcher;
  40 import java.util.regex.Pattern;
  41 
  42 import jdk.test.lib.OutputAnalyzer;
  43 import jdk.test.lib.Platform;
  44 import jdk.test.lib.ProcessTools;
  45 
  46 import static jdk.test.lib.Asserts.*;
  47 
  48 public class TestDynamicGCThreadsStats {
  49 
  50     public static void runTest() throws Exception {
  51         final String[] arguments = {
  52             "-Xbootclasspath/a:.",
  53             "-XX:+UnlockExperimentalVMOptions",
  54             "-XX:+UnlockDiagnosticVMOptions",
  55             "-XX:+UseDynamicNumberOfGCThreads",
  56             "-XX:+WhiteBoxAPI",
  57             "-XX:+UseG1GC",
  58             "-Xlog:gc*=trace",
  59             "-Xmx10M",
  60             GCTest.class.getName()
  61             };
  62 
  63         ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(arguments);
  64         OutputAnalyzer output = new OutputAnalyzer(pb.start());
  65 
  66         output.shouldHaveExitValue(0);
  67 
  68         // Looking for: 
  69         //        [0.234s][debug  ][gc,phases            ] GC(0)    Evacuate Collection Set: 3.7 ms
  70         String parallel_phase_leader = "Evacuate Collection Set: \\d+\\.\\d+ms";
  71         String std_out = output.getStdout();
  72         Matcher m = Pattern.compile(parallel_phase_leader, Pattern.MULTILINE).matcher(std_out);
  73     
  74         if (!m.find()) {
  75           throw new Exception("Could not find correct output for Evacuate Collection Set: in stdout," +
  76             " should match the pattern \"" + parallel_phase_leader + "\", but stdout is \n" + output.getStdout());
  77         } else {
  78             // Find data with per thread times.
  79             // Any of the metrics with per thread times can be used.
  80             // Chose: 
  81             //        [0.234s][debug  ][gc,phases            ] GC(0)       Ext Root Scanning:        Min:  0.6, Avg:  1.1, Max:  1.6, Diff:  1.0, Sum:  2.2
  82             String stats = "Ext Root Scanning \\(ms\\):";
  83             Pattern stats_pattern = Pattern.compile(stats);
  84             m.usePattern(stats_pattern);
  85             if (!m.find()) {
  86               throw new Exception("Could not find correct output for chosen statistics in stdout," +
  87               " should match the pattern \"" + stats + "\", but stdout is \n" + output.getStdout());
  88             } else {
  89               // Find the printed average from the log.
  90               Pattern avg_pattern = Pattern.compile("(Avg: +)(\\d+\\.\\d+)(,)");
  91               m.usePattern(avg_pattern);
  92               if (!m.find()) {
  93                 throw new Exception("Could not find Avg: dd.d in stdout\n," + output.getStdout());
  94               } else {
  95                 String value_string = m.group(2);
  96                 // Count the decimal places in the printed average.  The
  97                 // number of decimal places will be used to calculate the
  98                 // rounding error when the per thread values are summed in
  99                 // this test.
 100                 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
 101                 char decimal_symbol = symbols.getDecimalSeparator();
 102                 String decimal_places_string = value_string.substring(value_string.indexOf(decimal_symbol) + 1);
 103                 int decimal_points = decimal_places_string.length();
 104                 double avg_value = 0.0;
 105                 try {
 106                   avg_value = Double.parseDouble(value_string);
 107                 } catch (java.lang.NumberFormatException e) {
 108                    System.out.println("Non Number Format - " + value_string);
 109                 }
 110                 // Go to the next line which will have a "GC" in it.  For
 111                 // example:
 112                 //     [0.234s][trace  ][gc,phases            ] GC(0)                                  1.58  0.58
 113                 Pattern GC_pattern = Pattern.compile("GC");
 114                 m.usePattern(GC_pattern);
 115                 if (m.find()) {
 116                   String[] per_thread_values =  std_out.substring(m.end()).split(System.lineSeparator(), 2);
 117                   String value_regex = " +\\d+\\.\\d+";
 118                   Matcher value_m = Pattern.compile(value_regex, Pattern.MULTILINE).matcher(per_thread_values[0]);
 119                   int workers = 0;
 120                   Double sum = 0.0;
 121                   // Count and sum the values.  Note that each parsed value has rounding error in it.
 122                   while (value_m.find()) {
 123                     try {
 124                       Double value = Double.parseDouble(value_m.group());
 125                       sum += value;
 126                       workers++;
 127                     } catch (java.lang.NumberFormatException e) {
 128                       System.out.println("Non Number Format - " + m.group());
 129                     }
 130                   }
 131                   if (workers > 0) {
 132                     Double calculated_avg = sum / workers;
 133                     // Rounding error in per thread data
 134                     Double rounding_error = workers * 0.5 * Math.pow(10.0,  -decimal_points);
 135                     if (Math.abs(calculated_avg - avg_value) > rounding_error) {
 136                       throw new Exception("Average from log " + avg_value + " and average calculated by test " + 
 137                         calculated_avg + " can differ by rounding error " + rounding_error);
 138                     } else {
 139 
 140                       System.err.println("PASSED: Average from log " + avg_value + " and average calculated by test " + 
 141                         calculated_avg + " can differ by rounding error " + rounding_error);
 142                     }
 143                   }
 144                 }
 145                 System.out.println(output.getStdout());
 146               }
 147             }
 148         }
 149     }
 150 
 151     public static void main(String[] args) throws Exception {
 152         runTest();
 153     }
 154 
 155     static class GCTest {
 156         public static void main(String [] args) {
 157             int numGCs = 4;
 158 
 159             // Perform the requested amount of GCs.
 160             WhiteBox wb = WhiteBox.getWhiteBox();
 161             for (int i = 0; i < numGCs - 1; i++) {
 162                 wb.youngGC();
 163             }
 164             if (numGCs > 0) {
 165               wb.fullGC();
 166             }
 167             System.out.println("Done");
 168         }
 169     }
 170 }
 171