1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.rules.jdk.latency; 34 35 import static org.openjdk.jmc.common.unit.UnitLookup.NUMBER; 36 import static org.openjdk.jmc.common.unit.UnitLookup.NUMBER_UNITY; 37 38 import java.text.MessageFormat; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collection; 42 import java.util.Collections; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Map.Entry; 48 import java.util.Set; 49 import java.util.concurrent.Callable; 50 import java.util.concurrent.FutureTask; 51 import java.util.concurrent.RunnableFuture; 52 import java.util.logging.Level; 53 import java.util.logging.Logger; 54 55 import org.openjdk.jmc.common.IMCType; 56 import org.openjdk.jmc.common.item.Aggregators; 57 import org.openjdk.jmc.common.item.IAttribute; 58 import org.openjdk.jmc.common.item.IItemCollection; 59 import org.openjdk.jmc.common.item.IItemFilter; 60 import org.openjdk.jmc.common.item.IItemQuery; 61 import org.openjdk.jmc.common.item.ItemFilters; 62 import org.openjdk.jmc.common.item.ItemQueryBuilder; 63 import org.openjdk.jmc.common.unit.IQuantity; 64 import org.openjdk.jmc.common.unit.UnitLookup; 65 import org.openjdk.jmc.common.util.IPreferenceValueProvider; 66 import org.openjdk.jmc.common.util.TypedPreference; 67 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 68 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; 69 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 70 import org.openjdk.jmc.flightrecorder.rules.IRule; 71 import org.openjdk.jmc.flightrecorder.rules.Result; 72 import org.openjdk.jmc.flightrecorder.rules.jdk.messages.internal.Messages; 73 import org.openjdk.jmc.flightrecorder.rules.jdk.util.ClassEntry; 74 import org.openjdk.jmc.flightrecorder.rules.jdk.util.ColumnInfo; 75 import org.openjdk.jmc.flightrecorder.rules.jdk.util.IItemResultSet; 76 import org.openjdk.jmc.flightrecorder.rules.jdk.util.ItemResultSetException; 77 import org.openjdk.jmc.flightrecorder.rules.jdk.util.ItemResultSetFactory; 78 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics; 79 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit; 80 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit.EventAvailability; 81 82 /** 83 * This rule is making use of the new dedicated biased locking revocation events available in JDK 84 * 10/18.3. It will fire whenever a class is excluded from biased lockings, or whenever there have 85 * been more than 15 revocations (can be configured) for a particular class. 86 */ 87 public final class BiasedLockingRevocationRule implements IRule { 88 public static final TypedPreference<IQuantity> WARNING_LIMIT = new TypedPreference<>( 89 "biasedRevocation.warning.limit", //$NON-NLS-1$ 90 Messages.getString(Messages.BiasedLockingRevocationRule_CONFIG_WARNING_LIMIT), 91 Messages.getString(Messages.BiasedLockingRevocationRule_CONFIG_WARNING_LIMIT_LONG), NUMBER, 92 NUMBER_UNITY.quantity(15)); 93 94 public static final TypedPreference<IQuantity> MAX_NUMBER_OF_CLASSES_TO_REPORT = new TypedPreference<>( 95 "biasedRevocation.classesToReport.limit", //$NON-NLS-1$ 96 Messages.getString(Messages.General_CONFIG_CLASS_LIMIT), 97 Messages.getString(Messages.General_CONFIG_CLASS_LIMIT_LONG), NUMBER, NUMBER_UNITY.quantity(5)); 98 99 private static final TypedPreference<String> FILTERED_CLASSES = new TypedPreference<>( 100 "biasedRevocation.filtered.classes", //$NON-NLS-1$ 101 Messages.getString(Messages.BiasedLockingRevocationRule_CONFIG_FILTERED_CLASSES), 102 Messages.getString(Messages.BiasedLockingRevocationRule_CONFIG_FILTERED_CLASSES_LONG), 103 UnitLookup.PLAIN_TEXT.getPersister(), "java.lang.ref.ReferenceQueue$Lock"); //$NON-NLS-1$ 104 105 private static final List<TypedPreference<?>> CONFIG_ATTRIBUTES = Arrays.<TypedPreference<?>> asList(WARNING_LIMIT, 106 MAX_NUMBER_OF_CLASSES_TO_REPORT, FILTERED_CLASSES); 107 108 private Result getResult(IItemCollection items, IPreferenceValueProvider valueProvider) { 109 EventAvailability eventAvailability = RulesToolkit.getEventAvailability(items, 110 JdkTypeIDs.BIASED_LOCK_CLASS_REVOCATION); 111 if (eventAvailability == EventAvailability.UNKNOWN || eventAvailability == EventAvailability.DISABLED) { 112 return RulesToolkit.getEventAvailabilityResult(this, items, eventAvailability, 113 JdkTypeIDs.BIASED_LOCK_CLASS_REVOCATION); 114 } 115 116 IItemCollection revokationEvents = items.apply(JdkFilters.BIASED_LOCKING_REVOCATIONS); // $NON-NLS-1$ 117 if (!revokationEvents.hasItems()) { 118 return new Result(this, 0, Messages.getString(Messages.BiasedLockingRevocationPauseRule_TEXT_OK)); 119 } 120 121 Set<String> filteredTypes = getFilteredTypes(valueProvider.getPreferenceValue(FILTERED_CLASSES)); 122 123 IItemCollection revokedClassesEvents = revokationEvents 124 .apply(ItemFilters.and(ItemFilters.hasAttribute(JdkAttributes.BIASED_REVOCATION_CLASS), 125 ItemFilters.equals(JdkAttributes.BIASED_REVOCATION_DISABLE_BIASING, Boolean.TRUE))); 126 Set<IMCType> revokedTypes = filter(filteredTypes, 127 revokedClassesEvents.getAggregate(Aggregators.distinct(JdkAttributes.BIASED_REVOCATION_CLASS))); 128 129 StringBuilder shortMessage = new StringBuilder(); 130 StringBuilder longMessage = new StringBuilder(); 131 132 float totalScore = 0; 133 134 if (!revokedTypes.isEmpty()) { 135 totalScore = 25; // Base penalty for having fully revoked types not filtered out. 136 totalScore += RulesToolkit.mapExp(revokedTypes.size(), 25, 7, 20); // Up to 25 more points if you have plenty of revoked types. 137 shortMessage.append(Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_REVOKED_CLASSES_FOUND)); 138 shortMessage.append(" "); //$NON-NLS-1$ 139 longMessage 140 .append(Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_REVOKED_CLASSES_FOUND_LONG)); 141 longMessage.append("<p><ul>"); //$NON-NLS-1$ 142 for (IMCType offender : revokedTypes) { 143 longMessage.append("<li>"); //$NON-NLS-1$ 144 longMessage.append(offender.toString()); 145 longMessage.append("</li>"); //$NON-NLS-1$ 146 } 147 longMessage.append("</ul></p>"); //$NON-NLS-1$ 148 } 149 int warningLimit = (int) valueProvider.getPreferenceValue(WARNING_LIMIT).longValue(); 150 151 Map<IMCType, ClassEntry> revocationMap = extractRevocations(revokationEvents, 152 ItemFilters.or(ItemFilters.type(JdkTypeIDs.BIASED_LOCK_REVOCATION), 153 ItemFilters.type(JdkTypeIDs.BIASED_LOCK_SELF_REVOCATION)), 154 JdkAttributes.BIASED_REVOCATION_LOCK_CLASS); 155 Map<IMCType, ClassEntry> classRevocationMap = extractRevocations(revokationEvents, 156 ItemFilters.type(JdkTypeIDs.BIASED_LOCK_CLASS_REVOCATION), JdkAttributes.BIASED_REVOCATION_CLASS); 157 158 List<ClassEntry> revocationClasses = filteredMerge(filteredTypes, revokedTypes, classRevocationMap, 159 revocationMap); 160 totalScore += calculateRevocationCountScore(revocationClasses); 161 162 Collections.sort(revocationClasses); 163 164 if (revocationClasses.size() > 0) { 165 int maxClasses = (int) valueProvider.getPreferenceValue(MAX_NUMBER_OF_CLASSES_TO_REPORT).longValue(); 166 shortMessage 167 .append(Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_REVOKE_LIMIT_CLASSES_FOUND)); 168 longMessage.append(MessageFormat.format( 169 Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_REVOKE_LIMIT_CLASSES_FOUND_LONG), 170 warningLimit)); 171 longMessage.append("<p><ul>"); //$NON-NLS-1$ 172 int classLimit = Math.min(revocationClasses.size(), maxClasses); 173 for (int i = 0; i < classLimit; i++) { 174 ClassEntry classEntry = revocationClasses.get(i); 175 if (classEntry.getCount() < warningLimit) { 176 break; 177 } 178 longMessage.append("<li>"); //$NON-NLS-1$ 179 longMessage.append(classEntry); 180 longMessage.append("</li>"); //$NON-NLS-1$ 181 } 182 longMessage.append("</ul></p>"); //$NON-NLS-1$ 183 } 184 if (totalScore == 0) { 185 return new Result(this, 0, Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_OK)); 186 } else { 187 longMessage 188 .append(MessageFormat.format(Messages.getString(Messages.BiasedLockingRevocationRule_TEXT_EPILOGUE), 189 String.valueOf(filteredTypes))); 190 } 191 return new Result(this, totalScore, shortMessage.toString(), longMessage.toString()); 192 } 193 194 private int calculateRevocationCountScore(List<ClassEntry> offendingClasses) { 195 int score = 0; 196 for (ClassEntry entry : offendingClasses) { 197 // Can get maximum the base score for a full revocation if there are plenty of 198 // revocation events for a single class. 199 score = Math.max(Math.min(entry.getCount() / 2, 25), score); 200 } 201 return score; 202 } 203 204 /** 205 * @param filteredTypes 206 * user filtered types 207 * @param revokedTypes 208 * the types that were revoked during this recording. 209 * @param offendingClassRevocations 210 * @param offendingRevocations 211 * @return 212 */ 213 private List<ClassEntry> filteredMerge( 214 Set<String> filteredTypes, Set<IMCType> revokedTypes, Map<IMCType, ClassEntry> offendingClassRevocations, 215 Map<IMCType, ClassEntry> offendingRevocations) { 216 Map<IMCType, ClassEntry> merged = new HashMap<>(); 217 218 for (Entry<IMCType, ClassEntry> entry : offendingRevocations.entrySet()) { 219 putIfNotInFiltered(filteredTypes, revokedTypes, merged, entry); 220 } 221 222 // Likely far fewer class revocations 223 for (Entry<IMCType, ClassEntry> entry : offendingClassRevocations.entrySet()) { 224 ClassEntry mergedEntry = merged.get(entry.getKey()); 225 if (mergedEntry != null) { 226 merged.put(entry.getKey(), 227 new ClassEntry(entry.getKey(), entry.getValue().getCount() + mergedEntry.getCount())); 228 } else { 229 putIfNotInFiltered(filteredTypes, revokedTypes, merged, entry); 230 } 231 } 232 return new ArrayList<>(merged.values()); 233 } 234 235 private static void putIfNotInFiltered( 236 Set<String> filteredTypes, Set<IMCType> revokedTypes, Map<IMCType, ClassEntry> merged, 237 Entry<IMCType, ClassEntry> entry) { 238 IMCType type = entry.getKey(); 239 if (type != null && !filteredTypes.contains(type.getFullName()) && !revokedTypes.contains(type)) { 240 merged.put(entry.getKey(), entry.getValue()); 241 } 242 } 243 244 private Map<IMCType, ClassEntry> extractRevocations( 245 IItemCollection revokationEvents, IItemFilter filter, IAttribute<IMCType> classAttribute) { 246 ItemQueryBuilder itemQueryBuilder = ItemQueryBuilder.fromWhere(filter); 247 itemQueryBuilder.groupBy(classAttribute); 248 itemQueryBuilder.select(classAttribute); 249 itemQueryBuilder.select(Aggregators.count()); 250 IItemQuery query = itemQueryBuilder.build(); 251 252 IItemResultSet resultSet = new ItemResultSetFactory().createResultSet(revokationEvents, query); 253 ColumnInfo countColumn = resultSet.getColumnMetadata().get(Aggregators.count().getName()); 254 ColumnInfo classColumn = resultSet.getColumnMetadata().get(classAttribute.getIdentifier()); 255 256 Map<IMCType, ClassEntry> offendingClasses = new HashMap<>(); 257 while (resultSet.next()) { 258 try { 259 IQuantity countObject = (IQuantity) resultSet.getValue(countColumn.getColumn()); 260 IMCType type = (IMCType) resultSet.getValue(classColumn.getColumn()); 261 if (countObject != null && type != null) { 262 offendingClasses.put(type, new ClassEntry(type, (int) countObject.longValue())); 263 264 } 265 } catch (ItemResultSetException e) { 266 Logger.getLogger(getClass().getName()).log(Level.WARNING, 267 "Unexpected problem looking at biased revocation events.", e); //$NON-NLS-1$ 268 } 269 } 270 return offendingClasses; 271 } 272 273 private Set<IMCType> filter(Set<String> filteredTypes, Set<IMCType> types) { 274 Set<IMCType> result = new HashSet<>(); 275 for (IMCType type : types) { 276 if (!filteredTypes.contains(type.getFullName())) { 277 result.add(type); 278 } 279 } 280 return result; 281 } 282 283 private static Set<String> getFilteredTypes(String preferenceValue) { 284 Set<String> acceptedOptionNames = new HashSet<>(); 285 if (preferenceValue != null) { 286 String[] optionNames = preferenceValue.split("[, ]+"); //$NON-NLS-1$ 287 for (String optionName : optionNames) { 288 acceptedOptionNames.add(optionName); 289 } 290 } 291 return acceptedOptionNames; 292 } 293 294 @Override 295 public RunnableFuture<Result> evaluate(final IItemCollection items, final IPreferenceValueProvider valueProvider) { 296 FutureTask<Result> evaluationTask = new FutureTask<>(new Callable<Result>() { 297 @Override 298 public Result call() throws Exception { 299 return getResult(items, valueProvider); 300 } 301 }); 302 return evaluationTask; 303 } 304 305 @Override 306 public Collection<TypedPreference<?>> getConfigurationAttributes() { 307 return CONFIG_ATTRIBUTES; 308 } 309 310 @Override 311 public String getId() { 312 return "biasedLockingRevocation"; //$NON-NLS-1$ 313 } 314 315 @Override 316 public String getName() { 317 return Messages.getString(Messages.BiasedLockingRevocationRule_NAME); 318 } 319 320 @Override 321 public String getTopic() { 322 return JfrRuleTopics.BIASED_LOCKING; 323 } 324 325 }