1 /*
  2  * Copyright (c) 2015, 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 package gc.logging;
 25 
 26 /*
 27  * @test TestPrintReferences
 28  * @bug 8136991 8186402 8186465 8188245
 29  * @summary Validate the reference processing logging
 30  * @key gc
 31  * @library /test/lib
 32  * @modules java.base/jdk.internal.misc
 33  *          java.management
 34  * @run main gc.logging.TestPrintReferences
 35  */
 36 
 37 import java.lang.ref.SoftReference;
 38 import java.math.BigDecimal;
 39 import jdk.test.lib.process.OutputAnalyzer;
 40 import jdk.test.lib.process.ProcessTools;
 41 import java.util.regex.Pattern;
 42 import java.util.regex.Matcher;
 43 
 44 public class TestPrintReferences {
 45     static String output;
 46     static final String doubleRegex = "[0-9]+[.,][0-9]+";
 47     static final String referenceProcessing = "Reference Processing";
 48     static final String softReference = "SoftReference";
 49     static final String weakReference = "WeakReference";
 50     static final String finalReference = "FinalReference";
 51     static final String phantomReference = "PhantomReference";
 52 
 53     static final String phaseReconsiderSoftReferences = "Reconsider SoftReferences";
 54     static final String phaseNotifySoftWeakReferences = "Notify Soft/WeakReferences";
 55     static final String phaseNotifyKeepAliveFinalizer = "Notify and keep alive finalizable";
 56     static final String phaseNotifyPhantomReferences  = "Notify PhantomReferences";
 57 
 58     static final String phase1 = "Phase1";
 59     static final String phase2 = "Phase2";
 60     static final String phase3 = "Phase3";
 61     static final String gcLogTimeRegex = ".* GC\\([0-9]+\\) ";
 62 
 63     public static void main(String[] args) throws Exception {
 64         testPhases(true);
 65         testPhases(false);
 66         testRefs();
 67     }
 68 
 69     static String indent(int count) {
 70         return " {" + count + "}";
 71     }
 72 
 73     public static void testRefs() throws Exception {
 74         ProcessBuilder pb_enabled = ProcessTools.createJavaProcessBuilder("-Xlog:gc+ref+phases=debug",
 75                                                                           "-XX:+UseG1GC",
 76                                                                           "-Xmx32M",
 77                                                                           GCTest.class.getName());
 78         OutputAnalyzer output = new OutputAnalyzer(pb_enabled.start());
 79 
 80         checkRefsLogFormat(output);
 81 
 82         output.shouldHaveExitValue(0);
 83     }
 84 
 85     private static String refRegex(String reftype) {
 86         String countRegex = "[0-9]+";
 87         return gcLogTimeRegex + indent(6) + reftype + ":\n" +
 88                gcLogTimeRegex + indent(8) + "Discovered: " + countRegex + "\n" +
 89                gcLogTimeRegex + indent(8) + "Cleared: " + countRegex + "\n";
 90     }
 91 
 92     private static void checkRefsLogFormat(OutputAnalyzer output) {
 93         output.shouldMatch(refRegex("SoftReference") +
 94                            refRegex("WeakReference") +
 95                            refRegex("FinalReference") +
 96                            refRegex("PhantomReference"));
 97     }
 98 
 99     public static void testPhases(boolean parallelRefProcEnabled) throws Exception {
100         ProcessBuilder pb_enabled = ProcessTools.createJavaProcessBuilder("-Xlog:gc+phases+ref=debug",
101                                                                           "-XX:+UseG1GC",
102                                                                           "-Xmx32M",
103                                                                           "-XX:" + (parallelRefProcEnabled ? "+" : "-") + "ParallelRefProcEnabled",
104                                                                           "-XX:-UseDynamicNumberOfGCThreads",
105                                                                           "-XX:ParallelGCThreads=2",
106                                                                           GCTest.class.getName());
107         OutputAnalyzer output = new OutputAnalyzer(pb_enabled.start());
108 
109         checkLogFormat(output, parallelRefProcEnabled);
110         checkLogValue(output);
111 
112         output.shouldHaveExitValue(0);
113     }
114 
115     private static String phaseRegex(String phaseName) {
116         final String timeRegex = doubleRegex + "ms";
117         return indent(6) + phaseName + ": " + timeRegex + "\n";
118     }
119 
120     private static String subphaseRegex(String subphaseName, boolean parallelRefProcEnabled) {
121         final String timeRegex = "\\s+" + doubleRegex;
122         if (parallelRefProcEnabled) {
123             final String timeInParRegex = timeRegex +",\\s";
124             return gcLogTimeRegex + indent(8) + subphaseName +
125                    " \\(ms\\):\\s+(Min:" + timeInParRegex + "Avg:" + timeInParRegex + "Max:" + timeInParRegex + "Diff:" + timeInParRegex + "Sum:" + timeInParRegex +
126                    "Workers: [0-9]+|skipped)" + "\n";
127         } else {
128             return gcLogTimeRegex + indent(8) + subphaseName + ":(" + timeRegex + "ms|\\s+skipped)\n";
129         }
130     }
131 
132     // Find the first Reference Processing log and check its format.
133     private static void checkLogFormat(OutputAnalyzer output, boolean parallelRefProcEnabled) {
134         String timeRegex = doubleRegex + "ms";
135 
136         /* Total Reference processing time */
137         String totalRegex = gcLogTimeRegex + indent(4) + referenceProcessing + ": " + timeRegex + "\n";
138 
139         String balanceRegex = parallelRefProcEnabled ? "(" + gcLogTimeRegex + indent(8) + "Balance queues: " + timeRegex + "\n)??" : "";
140 
141         final boolean p = parallelRefProcEnabled;
142 
143         String phase1Regex = gcLogTimeRegex + phaseRegex(phaseReconsiderSoftReferences) + balanceRegex + subphaseRegex("SoftRef", p);
144         String phase2Regex = gcLogTimeRegex + phaseRegex(phaseNotifySoftWeakReferences) +
145                              balanceRegex +
146                              subphaseRegex("SoftRef", p) +
147                              subphaseRegex("WeakRef", p) +
148                              subphaseRegex("FinalRef", p) +
149                              subphaseRegex("Total", p);
150         String phase3Regex = gcLogTimeRegex + phaseRegex(phaseNotifyKeepAliveFinalizer) + balanceRegex + subphaseRegex("FinalRef", p);
151         String phase4Regex = gcLogTimeRegex + phaseRegex(phaseNotifyPhantomReferences) + balanceRegex + subphaseRegex("PhantomRef", p);
152 
153         output.shouldMatch(totalRegex +
154                            phase1Regex +
155                            phase2Regex +
156                            phase3Regex +
157                            phase4Regex);
158     }
159 
160     // After getting time value, update 'output' for next use.
161     private static BigDecimal getTimeValue(String name, int indentCount) {
162         // Pattern of 'name', 'value' and some extra strings.
163         String patternString = gcLogTimeRegex + indent(indentCount) + name + ": " + "(" + doubleRegex + ")";
164         Matcher m = Pattern.compile(patternString).matcher(output);
165         if (!m.find()) {
166             throw new RuntimeException("Could not find time log for " + patternString);
167         }
168 
169         String match = m.group();
170         String value = m.group(1);
171 
172         double result = Double.parseDouble(value);
173 
174         int index = output.indexOf(match);
175         if (index != -1) {
176             output = output.substring(index, output.length());
177         }
178 
179         // Convert to BigDecimal to control the precision of floating point arithmetic.
180         return BigDecimal.valueOf(result);
181    }
182 
183     // Reference log is printing 1 decimal place of elapsed time.
184     // So sum of each sub-phases could be slightly larger than the enclosing phase in some cases.
185     // Because of this we need method to verify that our measurements and calculations are valid.
186     private static boolean greaterThanOrApproximatelyEqual(BigDecimal phaseTime, BigDecimal sumOfSubPhasesTime, BigDecimal tolerance) {
187         if (phaseTime.compareTo(sumOfSubPhasesTime) >= 0) {
188             // phaseTime is greater than or equal.
189             return true;
190         }
191 
192         BigDecimal diff = sumOfSubPhasesTime.subtract(phaseTime);
193         if (diff.compareTo(tolerance) <= 0) {
194             // Difference is within tolerance, so approximately equal.
195             return true;
196         }
197 
198         // sumOfSubPhasesTime is greater than phaseTime and not within tolerance.
199         return false;
200     }
201 
202     // Find the first concurrent Reference Processing log and compare phase time vs. sum of sub-phases.
203     public static void checkLogValue(OutputAnalyzer out) {
204         output = out.getStdout();
205 
206         String patternString = gcLogTimeRegex + indent(0) +
207                                referenceProcessing + ": " + "[0-9]+[.,][0-9]+";
208         Matcher m = Pattern.compile(patternString).matcher(output);
209         if (m.find()) {
210             int start = m.start();
211             int end = output.length();
212             // If there's another concurrent Reference Processing log, ignore it.
213             if (m.find()) {
214                 end = m.start();
215             }
216             if (start != -1) {
217                 output = output.substring(start, end);
218                 checkTrimmedLogValue();
219             }
220         }
221     }
222 
223     private static void checkTrimmedLogValue() {
224         BigDecimal refProcTime = getTimeValue(referenceProcessing, 0);
225 
226         BigDecimal sumOfSubPhasesTime = getTimeValue(phaseReconsiderSoftReferences, 2);
227         sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phaseNotifySoftWeakReferences, 2));
228         sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phaseNotifyKeepAliveFinalizer, 2));
229         sumOfSubPhasesTime = sumOfSubPhasesTime.add(getTimeValue(phaseNotifyPhantomReferences, 2));
230 
231         // If there are 4 phases, we should allow 0.2 tolerance.
232         final BigDecimal toleranceFor4SubPhases = BigDecimal.valueOf(0.2);
233         if (!greaterThanOrApproximatelyEqual(refProcTime, sumOfSubPhasesTime, toleranceFor4SubPhases)) {
234             throw new RuntimeException("Reference Processing time(" + refProcTime + "ms) is less than the sum("
235                                        + sumOfSubPhasesTime + "ms) of each phases");
236         }
237     }
238 
239     static class GCTest {
240         static final int SIZE = 512 * 1024;
241         static Object[] dummy = new Object[SIZE];
242 
243         public static void main(String [] args) {
244              for (int i = 0; i < SIZE; i++) {
245                   dummy[i] = new SoftReference<>(new Object());
246              }
247         }
248     }
249 }