1 /*
   2  * Copyright (c) 2015, 2017, 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 TestPrintReferences
  26  * @bug 8136991 8186402 8186465 8188245
  27  * @summary Validate the reference processing logging
  28  * @key gc
  29  * @library /test/lib
  30  * @modules java.base/jdk.internal.misc
  31  *          java.management
  32  */
  33 
  34 import java.lang.ref.SoftReference;
  35 import java.math.BigDecimal;
  36 import java.util.ArrayList;
  37 
  38 import jdk.test.lib.process.OutputAnalyzer;
  39 import jdk.test.lib.process.ProcessTools;
  40 import java.util.regex.Pattern;
  41 import java.util.regex.Matcher;
  42 
  43 public class TestPrintReferences {
  44   static String output;
  45   static final String doubleRegex = "[0-9]+[.,][0-9]+";
  46   static final String referenceProcessing = "Reference Processing";
  47   static final String softReference = "SoftReference";
  48   static final String weakReference = "WeakReference";
  49   static final String finalReference = "FinalReference";
  50   static final String phantomReference = "PhantomReference";
  51   static final String phase1 = "Phase1";
  52   static final String phase2 = "Phase2";
  53   static final String phase3 = "Phase3";
  54   static final String gcLogTimeRegex = ".* GC\\([0-9]+\\) ";
  55 
  56   public static void main(String[] args) throws Exception {
  57     ProcessBuilder pb_enabled = ProcessTools.createJavaProcessBuilder("-Xlog:gc+phases+ref=debug",
  58                                                                       "-XX:+UseG1GC",
  59                                                                       "-Xmx32M",
  60                                                                       // Explicit thread setting is required to avoid using only 1 thread
  61                                                                       "-XX:ParallelGCThreads=2",
  62                                                                       GCTest.class.getName());
  63     OutputAnalyzer output = new OutputAnalyzer(pb_enabled.start());
  64 
  65     checkLogFormat(output);
  66     checkLogValue(output);
  67 
  68     output.shouldHaveExitValue(0);
  69   }
  70 
  71   static String indent(int count) {
  72     return " {" + count + "}";
  73   }
  74 
  75   // Find the first Reference Processing log and check its format.
  76   public static void checkLogFormat(OutputAnalyzer output) {
  77     String countRegex = "[0-9]+";
  78     String timeRegex = doubleRegex + "ms";
  79     String totalRegex = gcLogTimeRegex + indent(4) + referenceProcessing + ": " + timeRegex + "\n";
  80     String balanceRegex = gcLogTimeRegex + indent(8) + "Balance queues: " + timeRegex + "\n";
  81     String softRefRegex = gcLogTimeRegex + indent(6) + softReference + ": " + timeRegex + "\n";
  82     String weakRefRegex = gcLogTimeRegex + indent(6) + weakReference + ": " + timeRegex + "\n";
  83     String finalRefRegex = gcLogTimeRegex + indent(6) + finalReference + ": " + timeRegex + "\n";
  84     String phantomRefRegex = gcLogTimeRegex + indent(6) + phantomReference + ": " + timeRegex + "\n";
  85     String refDetailRegex = gcLogTimeRegex + indent(8) + phase2 + ": " + timeRegex + "\n" +
  86                             gcLogTimeRegex + indent(8) + phase3 + ": " + timeRegex + "\n" +
  87                             gcLogTimeRegex + indent(8) + "Discovered: " + countRegex + "\n" +
  88                             gcLogTimeRegex + indent(8) + "Cleared: " + countRegex + "\n";
  89     String softRefDetailRegex = gcLogTimeRegex + indent(8) + phase1 + ": " + timeRegex + "\n" + refDetailRegex;
  90     String enqueueRegex = gcLogTimeRegex + indent(4) + "Reference Enqueuing: " + timeRegex + "\n";
  91     String enqueueDetailRegex = gcLogTimeRegex + indent(6) + "Reference Counts:  Soft: " + countRegex +
  92                                 "  Weak: " + countRegex + "  Final: " + countRegex + "  Phantom: " + countRegex + "\n";
  93 
  94     output.shouldMatch(/* Total Reference processing time */
  95                        totalRegex +
  96                        /* SoftReference processing */
  97                        softRefRegex + balanceRegex + softRefDetailRegex +
  98                        /* WeakReference processing */
  99                        weakRefRegex + balanceRegex + refDetailRegex +
 100                        /* FinalReference processing */
 101                        finalRefRegex + balanceRegex + refDetailRegex +
 102                        /* PhantomReference processing */
 103                        phantomRefRegex + balanceRegex + refDetailRegex +
 104                        /* Total Enqueuing time */
 105                        enqueueRegex +
 106                          /* Enqueued Stats */
 107                        enqueueDetailRegex
 108                        );
 109   }
 110 
 111   // After getting time value, update 'output' for next use.
 112   public static BigDecimal getTimeValue(String name, int indentCount) {
 113     // Pattern of 'name', 'value' and some extra strings.
 114     String patternString = gcLogTimeRegex + indent(indentCount) + name + ": " + "(" + doubleRegex + ")";
 115     Matcher m = Pattern.compile(patternString).matcher(output);
 116      if (!m.find()) {
 117       throw new RuntimeException("Could not find time log for " + patternString);
 118      }
 119 
 120     String match = m.group();
 121     String value = m.group(1);
 122 
 123     double result = Double.parseDouble(value);
 124 
 125     int index = output.indexOf(match);
 126     if (index != -1) {
 127       output = output.substring(index, output.length());
 128     }
 129 
 130     // Convert to BigDecimal to control the precision of floating point arithmetic.
 131     return BigDecimal.valueOf(result);
 132   }
 133 
 134   // Reference log is printing 1 decimal place of elapsed time.
 135   // So sum of each sub-phases could be slightly larger than the enclosing phase in some cases.
 136   // e.g. If there are 3 sub-phases:
 137   //      Actual value:  SoftReference(5.55) = phase1(1.85) + phase2(1.85) + phase3(1.85)
 138   //      Log value:     SoftReference(5.6) = phase1(1.9) + phase2(1.9) + phase3(1.9)
 139   //      When checked:  5.6 < 5.7 (sum of phase1~3)
 140   // Because of this we need method to verify that our measurements and calculations are valid.
 141   public static boolean greaterThanOrApproximatelyEqual(BigDecimal phaseTime, BigDecimal sumOfSubPhasesTime, BigDecimal tolerance) {
 142     if (phaseTime.compareTo(sumOfSubPhasesTime) >= 0) {
 143       // phaseTime is greater than or equal.
 144       return true;
 145     }
 146 
 147     BigDecimal diff = sumOfSubPhasesTime.subtract(phaseTime);
 148     if (diff.compareTo(tolerance) <= 0) {
 149       // Difference is within tolerance, so approximately equal.
 150       return true;
 151     }
 152 
 153     // sumOfSubPhasesTime is greater than phaseTime and not within tolerance.
 154     return false;
 155   }
 156 
 157   public static BigDecimal checkPhaseTime(String refType) {
 158     BigDecimal phaseTime = getTimeValue(refType, 2);
 159     BigDecimal sumOfSubPhasesTime = BigDecimal.valueOf(0.0);
 160 
 161     if (softReference.equals(refType)) {
 162       sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phase1, 4));
 163     }
 164     sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phase2, 4));
 165     sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phase3, 4));
 166 
 167     // If there are 3 sub-phases, we should allow 0.1 tolerance.
 168     final BigDecimal toleranceFor3SubPhases = BigDecimal.valueOf(0.1);
 169     if (!greaterThanOrApproximatelyEqual(phaseTime, sumOfSubPhasesTime, toleranceFor3SubPhases)) {
 170       throw new RuntimeException(refType +" time(" + phaseTime +
 171                                  "ms) is less than the sum(" + sumOfSubPhasesTime + "ms) of each phases");
 172     }
 173 
 174     return phaseTime;
 175   }
 176 
 177   // Find the first concurrent Reference Processing log and compare phase time vs. sum of sub-phases.
 178   public static void checkLogValue(OutputAnalyzer out) {
 179     output = out.getStdout();
 180 
 181     BigDecimal refProcTime = getTimeValue(referenceProcessing, 0);
 182 
 183     BigDecimal sumOfSubPhasesTime = checkPhaseTime(softReference);
 184     sumOfSubPhasesTime = sumOfSubPhasesTime.add(checkPhaseTime(weakReference));
 185     sumOfSubPhasesTime = sumOfSubPhasesTime.add(checkPhaseTime(finalReference));
 186     sumOfSubPhasesTime = sumOfSubPhasesTime.add(checkPhaseTime(phantomReference));
 187 
 188     // If there are 4 sub-phases, we should allow 0.2 tolerance.
 189     final BigDecimal toleranceFor4SubPhases = BigDecimal.valueOf(0.2);
 190     if (!greaterThanOrApproximatelyEqual(refProcTime, sumOfSubPhasesTime, toleranceFor4SubPhases)) {
 191       throw new RuntimeException("Reference Processing time(" + refProcTime + "ms) is less than the sum("
 192                                  + sumOfSubPhasesTime + "ms) of each phases");
 193     }
 194   }
 195 
 196   static class GCTest {
 197     static final int SIZE = 512 * 1024;
 198     static Object[] dummy = new Object[SIZE];
 199 
 200     public static void main(String [] args) {
 201       for (int i = 0; i < SIZE; i++) {
 202         dummy[i] = new SoftReference<>(new Object());
 203       }
 204     }
 205   }
 206 }