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.util; 34 35 import java.text.MessageFormat; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Collection; 39 import java.util.Collections; 40 import java.util.Comparator; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.Iterator; 44 import java.util.LinkedHashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Queue; 48 import java.util.Set; 49 import java.util.concurrent.ConcurrentLinkedQueue; 50 import java.util.concurrent.ExecutionException; 51 import java.util.concurrent.Future; 52 import java.util.concurrent.RunnableFuture; 53 import java.util.regex.Matcher; 54 import java.util.regex.Pattern; 55 56 import org.openjdk.jmc.common.IDisplayable; 57 import org.openjdk.jmc.common.IMCThread; 58 import org.openjdk.jmc.common.IPredicate; 59 import org.openjdk.jmc.common.collection.EntryHashMap; 60 import org.openjdk.jmc.common.collection.IteratorToolkit; 61 import org.openjdk.jmc.common.collection.MapToolkit; 62 import org.openjdk.jmc.common.collection.MapToolkit.IntEntry; 63 import org.openjdk.jmc.common.item.Aggregators; 64 import org.openjdk.jmc.common.item.IAccessorFactory; 65 import org.openjdk.jmc.common.item.IAttribute; 66 import org.openjdk.jmc.common.item.IItem; 67 import org.openjdk.jmc.common.item.IItemCollection; 68 import org.openjdk.jmc.common.item.IItemFilter; 69 import org.openjdk.jmc.common.item.IItemIterable; 70 import org.openjdk.jmc.common.item.IMemberAccessor; 71 import org.openjdk.jmc.common.item.IType; 72 import org.openjdk.jmc.common.item.ItemFilters; 73 import org.openjdk.jmc.common.item.ItemToolkit; 74 import org.openjdk.jmc.common.unit.BinaryPrefix; 75 import org.openjdk.jmc.common.unit.IQuantity; 76 import org.openjdk.jmc.common.unit.LinearUnit; 77 import org.openjdk.jmc.common.unit.QuantityConversionException; 78 import org.openjdk.jmc.common.unit.UnitLookup; 79 import org.openjdk.jmc.common.util.IPreferenceValueProvider; 80 import org.openjdk.jmc.common.util.LabeledIdentifier; 81 import org.openjdk.jmc.common.util.PredicateToolkit; 82 import org.openjdk.jmc.common.util.StringToolkit; 83 import org.openjdk.jmc.common.version.JavaVersion; 84 import org.openjdk.jmc.flightrecorder.JfrAttributes; 85 import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators; 86 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 87 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; 88 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 89 import org.openjdk.jmc.flightrecorder.rules.IRule; 90 import org.openjdk.jmc.flightrecorder.rules.Result; 91 import org.openjdk.jmc.flightrecorder.rules.RuleRegistry; 92 import org.openjdk.jmc.flightrecorder.rules.messages.internal.Messages; 93 import org.openjdk.jmc.flightrecorder.rules.tree.Range; 94 import org.openjdk.jmc.flightrecorder.rules.tree.TimeRangeFilter; 95 import org.openjdk.jmc.flightrecorder.rules.tree.TimeRangeThreadFilter; 96 import org.openjdk.jmc.flightrecorder.stacktrace.FrameSeparator; 97 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFormatToolkit; 98 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceFrame; 99 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel; 100 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel.Branch; 101 102 /** 103 * A collection of useful methods when evaluating rules. 104 */ 105 // FIXME: Should probably be broken apart into methods related to rules (kept here) and methods related to JDK events (which should be moved to wherever JfrAttributes etc. are) 106 public class RulesToolkit { 107 108 // FIXME: Quick and dirty inlining of constants constants defined in SettingsTransformer. These should be handled in some other way. 109 private static final String REC_SETTING_NAME_ENABLED = "enabled"; //$NON-NLS-1$ 110 private static final String REC_SETTING_NAME_THRESHOLD = "threshold"; //$NON-NLS-1$ 111 public static final String REC_SETTING_NAME_PERIOD = "period"; //$NON-NLS-1$ 112 public static final String REC_SETTING_PERIOD_EVERY_CHUNK = "everyChunk"; //$NON-NLS-1$ 113 114 private static final IAccessorFactory<String> TYPE_NAME_ACCESSOR_FACTORY = new IAccessorFactory<String>() { 115 116 @Override 117 public <T> IMemberAccessor<String, T> getAccessor(IType<T> type) { 118 final IMemberAccessor<LabeledIdentifier, T> ta = JdkAttributes.REC_SETTING_FOR.getAccessor(type); 119 return new IMemberAccessor<String, T>() { 120 121 @Override 122 public String getMember(T inObject) { 123 LabeledIdentifier eventType = ta.getMember(inObject); 124 return eventType == null ? null : eventType.getName(); 125 } 126 127 }; 128 } 129 }; 130 private final static LinearUnit MEBIBYTES = UnitLookup.MEMORY.getUnit(BinaryPrefix.MEBI); 131 132 /** 133 * Matches strings containing an identifiable version number as presented in a JVM info event. 134 * The minimal matching form is "JRE (" followed by 1 to 4 numbers on the format a.b.c_d or 135 * a.b.c.d. Examples are 1.7.0, 1.8.0_70, 9, 9.1, and 9.1.2.3. Match group 1 will contain the 136 * matched version string. 137 */ 138 private static final Pattern VERSION_PATTERN = Pattern 139 .compile(".*?JRE \\((\\d+(?:\\.\\d+(?:\\.\\d+(?:[\\._]\\d+)?)?)?(?:-ea)?).*"); //$NON-NLS-1$ 140 141 /** 142 * Knowledge about the state of affairs of an event type in an IItemCollection. 143 */ 144 public enum EventAvailability { 145 /** 146 * The type has events available in the collection. 147 */ 148 AVAILABLE, 149 /** 150 * The type was actively enabled in the collection. 151 */ 152 ENABLED, 153 /** 154 * The type was actively disabled in the collection. 155 */ 156 DISABLED, 157 /** 158 * The type is known in the collection, but no events were found. 159 */ 160 NONE, 161 /** 162 * The type is unknown in the collection. 163 */ 164 UNAVAILABLE 165 } 166 167 /** 168 * @return a least squares approximation of the increase in memory over the given time period, 169 * in mebibytes/second 170 */ 171 public static double leastSquareMemory( 172 Iterator<? extends IItem> items, IMemberAccessor<IQuantity, IItem> timeField, 173 IMemberAccessor<IQuantity, IItem> memField) { 174 double sumX = 0; 175 double sumY = 0; 176 double sumX2 = 0; 177 double sumXY = 0; 178 double num = 0; 179 double startTime = 0; 180 181 while (items.hasNext()) { 182 IItem item = items.next(); 183 long time = timeField.getMember(item).clampedLongValueIn(UnitLookup.EPOCH_S); 184 long mem = memField.getMember(item).clampedLongValueIn(MEBIBYTES); 185 if (num == 0) { 186 startTime = time; 187 } 188 time -= startTime; 189 sumX += time; 190 sumY += mem; 191 sumX2 += time * time; 192 sumXY += time * mem; 193 num++; 194 } 195 double value = (num * sumXY - sumX * sumY) / (num * sumX2 - sumX * sumX); 196 return Double.isNaN(value) ? 0 : value; 197 } 198 199 /** 200 * Finds items of a specific type where the given attribute has a value matching that of the 201 * provided match string. 202 * 203 * @param typeId 204 * the event type to find matches in 205 * @param items 206 * the set of items to search 207 * @param attribute 208 * the attribute to match 209 * @param match 210 * the pattern to find 211 * @param ignoreCase 212 * whether or not to ignore case when matching 213 * @return a comma-delimited string with all matching attributes 214 */ 215 public static String findMatches( 216 String typeId, IItemCollection items, IAttribute<String> attribute, String match, boolean ignoreCase) { 217 String regexp = ".*(" + (ignoreCase ? "?i:" : "") + match + ").*"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ 218 return items.getAggregate(Aggregators.filter(Aggregators.distinctAsString(typeId, attribute), 219 ItemFilters.and(ItemFilters.type(typeId), ItemFilters.matches(attribute, regexp)))); 220 } 221 222 /** 223 * Gets the value of a certain attribute for a given item 224 * 225 * @param item 226 * the item to get the attribute from 227 * @param attribute 228 * the attribute to get 229 * @return the value of the specified attribute for the given item 230 */ 231 public static <T> T getValue(IItem item, IAccessorFactory<T> attribute) { 232 IType<IItem> itemType = ItemToolkit.getItemType(item); 233 IMemberAccessor<? extends T, IItem> accessor = attribute.getAccessor(itemType); 234 if (accessor == null) { 235 throw new IllegalArgumentException("The accessor factory could not build accessor for type " //$NON-NLS-1$ 236 + itemType.getIdentifier() 237 + ". This is likely due to an old unsupported recording where an attribute has changed name."); //$NON-NLS-1$ 238 } 239 return accessor.getMember(item); 240 } 241 242 /** 243 * Gets a filter for a specific setting for the provided types. 244 * 245 * @param settingsName 246 * the specific setting to find 247 * @param typeIds 248 * the ids of the types to find the setting for 249 * @return a filter for a specified setting for the provided type ids 250 */ 251 public static IItemFilter getSettingsFilter(String settingsName, String ... typeIds) { 252 final Set<String> types = new HashSet<>(Arrays.asList(typeIds)); 253 IItemFilter typeFilter = new IItemFilter() { 254 @Override 255 public IPredicate<IItem> getPredicate(IType<IItem> type) { 256 final IMemberAccessor<LabeledIdentifier, IItem> ma = JdkAttributes.REC_SETTING_FOR.getAccessor(type); 257 if (ma != null) { 258 return new IPredicate<IItem>() { 259 260 @Override 261 public boolean evaluate(IItem o) { 262 LabeledIdentifier eventType = ma.getMember(o); 263 return eventType != null && types.contains(eventType.getInterfaceId()); 264 } 265 266 }; 267 } 268 return PredicateToolkit.falsePredicate(); 269 } 270 }; 271 return ItemFilters.and(JdkFilters.RECORDING_SETTING, typeFilter, 272 ItemFilters.equals(JdkAttributes.REC_SETTING_NAME, settingsName)); 273 } 274 275 /** 276 * Gets the maximum period setting for the specified event types in the given item collection. 277 * 278 * @param items 279 * the items to find the period setting in 280 * @param typeIds 281 * the event type ids to find settings for 282 * @return the maximum period setting for the specified event types 283 */ 284 public static IQuantity getSettingMaxPeriod(IItemCollection items, String ... typeIds) { 285 Set<String> values = getPeriodSettings(items, typeIds); 286 return values == null || values.isEmpty() ? null : getSettingMaxPeriod(values); 287 } 288 289 /** 290 * If possible, gets the longest period setting that is longer than the specified minimum period 291 * for the given event types. 292 * 293 * @param items 294 * the item collection to search through 295 * @param minPeriod 296 * the minimum period setting 297 * @param typeIds 298 * the event type ids to find the period setting for 299 * @return a string representation of the longest period longer than the {@code minPeriod}, or 300 * {@code null} if all periods are shorter than {@code minPeriod} 301 */ 302 public static String getPeriodIfGreaterThan(IItemCollection items, IQuantity minPeriod, String ... typeIds) { 303 Set<String> values = getPeriodSettings(items, typeIds); 304 if (values != null && !values.isEmpty()) { 305 IQuantity max = getSettingMaxPeriod(values); 306 if (max == null) { 307 return Messages.getString(Messages.RulesToolkit_EVERY_CHUNK); 308 } else if (max.compareTo(minPeriod) > 0) { 309 return max.displayUsing(IDisplayable.AUTO); 310 } 311 } 312 return null; 313 } 314 315 /** 316 * Converts a value persisted as a string by the JVM into an {@link IQuantity}. 317 * 318 * @param persistedValue 319 * the persisted value to convert 320 * @return the resulting {@link IQuantity} 321 */ 322 public static IQuantity parsePersistedJvmTimespan(String persistedValue) throws QuantityConversionException { 323 // FIXME: Copied from CommonConstraints.TimePersisterBrokenSI. Use that when it is exposed. 324 if (persistedValue.endsWith("m")) { //$NON-NLS-1$ 325 persistedValue += "in"; //$NON-NLS-1$ 326 } 327 return UnitLookup.TIMESPAN.parsePersisted(persistedValue); 328 } 329 330 /** 331 * Returns a string describing the subset of event types given which have no duration threshold 332 * set. 333 * 334 * @param items 335 * the item collection to search 336 * @param typeIds 337 * the event type ids to find thresholds for 338 * @return a comma-delimited string describing the event types with no threshold 339 */ 340 public static String getTypesWithZeroThreshold(IItemCollection items, String ... typeIds) { 341 IItemFilter f = new IItemFilter() { 342 343 @Override 344 public IPredicate<IItem> getPredicate(IType<IItem> type) { 345 final IMemberAccessor<String, IItem> accessor = JdkAttributes.REC_SETTING_VALUE.getAccessor(type); 346 return new IPredicate<IItem>() { 347 348 @Override 349 public boolean evaluate(IItem o) { 350 try { 351 String thresholdValue = accessor.getMember(o); 352 return parsePersistedJvmTimespan(thresholdValue).longValue() == 0L; 353 } catch (QuantityConversionException e) { 354 throw new RuntimeException(e); 355 } 356 } 357 }; 358 } 359 }; 360 IItemFilter filter = ItemFilters.and(getSettingsFilter(REC_SETTING_NAME_THRESHOLD, typeIds), f); 361 return getEventTypeNames(items.apply(filter)); 362 } 363 364 /** 365 * This method checks if the provided event types were explicitly enabled by checking the 366 * recording setting events. 367 * 368 * @param items 369 * the collection to check. 370 * @param typeIds 371 * the identifiers for the event types to check. 372 * @return true if all of the required event types were known to be explicitly enabled. 373 */ 374 public static boolean isEventsEnabled(IItemCollection items, String ... typeIds) { 375 IQuantity aggregate = items.apply(createEnablementFilter(true, typeIds)).getAggregate(Aggregators.count()); 376 return aggregate != null && aggregate.longValue() == typeIds.length; 377 } 378 379 /** 380 * This method returns false if any {@link EventAvailability} is disabled or unavailable. 381 * Otherwise true. 382 * 383 * @param eventAvailabilities 384 * the {@link EventAvailability} to check 385 * @return false if any {@link EventAvailability} is disabled or unavailable. Otherwise true. 386 */ 387 public static boolean isEventsEnabled(EventAvailability ... eventAvailabilities) { 388 for (EventAvailability availability : eventAvailabilities) { 389 if (availability == EventAvailability.DISABLED || availability == EventAvailability.UNAVAILABLE) { 390 return false; 391 } 392 } 393 return true; 394 } 395 396 /** 397 * This method checks if the provided event types were explicitly disabled by checking the 398 * recording setting events. 399 * 400 * @param items 401 * the collection to check. 402 * @param typeIds 403 * the identifiers for the event types to check. 404 * @return true if all of the required event types were known to be explicitly enabled. 405 */ 406 private static boolean isEventsDisabled(IItemCollection items, String ... typeIds) { 407 IQuantity aggregate = items.apply(createEnablementFilter(false, typeIds)).getAggregate(Aggregators.count()); 408 return aggregate != null && aggregate.longValue() == typeIds.length; 409 } 410 411 /** 412 * Checks the event availability for the event types. 413 * <p> 414 * Care should be taken when used with multiple typeIds. Use it when all of the provided typeIds 415 * are expected to have the same availability; if mixed, the lowest common availability for all 416 * types will be returned. 417 * 418 * @param items 419 * the collection to check 420 * @param typeIds 421 * the type identifiers to check 422 * @return the availability for the event types 423 */ 424 public static EventAvailability getEventAvailability(IItemCollection items, final String ... typeIds) { 425 // Only AVAILABLE if exactly all types have events 426 if (hasEvents(items, typeIds)) { 427 return EventAvailability.AVAILABLE; 428 } 429 // If enabled at any point, it was indeed enabled, and events could have been recorded 430 if (isEventsEnabled(items, typeIds)) { 431 return EventAvailability.ENABLED; 432 } 433 if (isEventsDisabled(items, typeIds)) { 434 return EventAvailability.DISABLED; 435 } 436 if (isEventsKnown(items, typeIds)) { 437 return EventAvailability.NONE; 438 } 439 return EventAvailability.UNAVAILABLE; 440 } 441 442 /** 443 * Checks if the event types are known in the collection. Note that it does not necessarily mean 444 * that there are events of the event type. 445 * 446 * @param items 447 * the collection to check 448 * @param typeIds 449 * the event types to check 450 * @return true if all the event types exists in the collection. 451 */ 452 private static boolean isEventsKnown(IItemCollection items, String ... typeIds) { 453 Set<String> availableTypes = getAvailableTypeIds(items); 454 if (availableTypes.containsAll(Arrays.asList(typeIds))) { 455 return true; 456 } 457 return false; 458 } 459 460 /** 461 * Returns true if precisely all of the event types have events. 462 * 463 * @param items 464 * the events. 465 * @param typeIds 466 * the identifiers of the event types to check. 467 * @return true if all of the types have events. 468 */ 469 private static boolean hasEvents(IItemCollection items, String ... typeIds) { 470 for (String typeId : typeIds) { 471 if (!internalHasEvents(items, typeId)) { 472 return false; 473 } 474 } 475 return true; 476 } 477 478 /** 479 * Returns a proper result for the availability problem. The result will be a "Not Applicable" 480 * result and the text provided will be based upon the assumption that the provided 481 * EventAvailability is the availability that makes it impossible to evaluate the rule. 482 * 483 * @param rule 484 * the rule for which this result will be generated 485 * @param items 486 * the items for which the availability was tested 487 * @param eventAvailability 488 * the availability making the rule N/A 489 * @param typeIds 490 * the types for which the availability was tested 491 * @return the result for the provided availability problem 492 */ 493 public static Result getEventAvailabilityResult( 494 IRule rule, IItemCollection items, EventAvailability eventAvailability, String ... typeIds) { 495 switch (eventAvailability) { 496 case ENABLED: 497 case NONE: 498 String requiredEventsTypeNames = getEventTypeNames(items, typeIds); 499 return getNotApplicableResult(rule, 500 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_EVENTS), 501 requiredEventsTypeNames), 502 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_EVENTS_LONG), 503 rule.getName(), requiredEventsTypeNames)); 504 case DISABLED: 505 String disabledEventTypeNames = getDisabledEventTypeNames(items, typeIds); 506 return getNotApplicableResult(rule, 507 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_EVENT_TYPE), 508 disabledEventTypeNames), 509 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_EVENT_TYPE_LONG), 510 rule.getName(), disabledEventTypeNames)); 511 case UNAVAILABLE: 512 // Can't get type names if the event type is unavailable 513 List<String> quotedTypeIds = new ArrayList<>(); 514 for (String typeId : typeIds) { 515 quotedTypeIds.add("'" + typeId + "'"); //$NON-NLS-1$ //$NON-NLS-2$ 516 } 517 Collections.sort(quotedTypeIds); 518 String unavailableTypeNames = StringToolkit.join(quotedTypeIds, ", "); //$NON-NLS-1$ 519 return getNotApplicableResult(rule, 520 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_UNAVAILABLE_EVENT_TYPE), 521 rule.getName(), unavailableTypeNames), 522 MessageFormat.format( 523 Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_UNAVAILABLE_EVENT_TYPE_LONG), 524 rule.getName(), unavailableTypeNames)); 525 case AVAILABLE: 526 String availableEventTypeNames = getEventTypeNames(items, typeIds); 527 return getNotApplicableResult(rule, 528 MessageFormat.format( 529 Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_EVENT_TYPE_NOT_AVAILABLE), 530 availableEventTypeNames), 531 MessageFormat.format(Messages.RulesToolkit_RULE_REQUIRES_EVENT_TYPE_NOT_AVAILABLE_LONG, 532 rule.getName(), availableEventTypeNames)); 533 default: 534 throw new IllegalArgumentException("Unsupported event availability: " + eventAvailability); //$NON-NLS-1$ 535 } 536 } 537 538 /** 539 * Creates a {@link Result} object for the given {@link IRule} object representing a result 540 * where there are too few events to properly evaluate a rule. 541 * 542 * @param rule 543 * the rule to create a {@link Result} object for 544 * @return an object describing that the rule could not be evaluated due to there not being 545 * enough data 546 */ 547 public static Result getTooFewEventsResult(IRule rule) { 548 return getNotApplicableResult(rule, Messages.getString(Messages.RulesToolkit_TOO_FEW_EVENTS)); 549 } 550 551 /** 552 * Creates a {@link Result} object with a generic not applicable (N/A) result for a given rule 553 * with a specified message. 554 * 555 * @param rule 556 * the rule to create a {@link Result} object for 557 * @param message 558 * the description of the result 559 * @return an object representing a generic not applicable result for the provided rule 560 */ 561 public static Result getNotApplicableResult(IRule rule, String message) { 562 return getNotApplicableResult(rule, message, null); 563 } 564 565 /** 566 * Creates a {@link Result} object with a generic not applicable (N/A) result for a given rule 567 * with a specified message. 568 * 569 * @param rule 570 * the rule to create a {@link Result} object for 571 * @param shortMessage 572 * the description of the result, as a short description 573 * @param longMessage 574 * a longer version of the description, used to explain in more detail why the rule 575 * could not be evaluated 576 * @return an object representing a generic not applicable result for the provided rule 577 */ 578 private static Result getNotApplicableResult(IRule rule, String shortMessage, String longMessage) { 579 return new Result(rule, Result.NOT_APPLICABLE, shortMessage, longMessage); 580 } 581 582 /** 583 * Creates a {@link Result} object describing that at least one of the specified event types 584 * must be present in the rule's input. 585 * 586 * @param rule 587 * the rule to create a {@link Result} object for 588 * @param typeIds 589 * the ids of the event types required for this rule 590 * @return an object representing a not applicable result due to not missing event types 591 */ 592 public static Result getRuleRequiresAtLeastOneEventTypeResult(IRule rule, String ... typeIds) { 593 return getNotApplicableResult(rule, 594 MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_REQUIRES_SOME_EVENTS), 595 rule.getName(), StringToolkit.join(typeIds, ", "))); //$NON-NLS-1$ 596 } 597 598 /** 599 * Creates a text message informing that event types are recommended 600 * 601 * @param items 602 * the events. 603 * @param typeIds 604 * the identifiers of the event types to check. 605 * @return a text message informing that event types are recommended 606 */ 607 public static String getEnabledEventTypesRecommendation(IItemCollection items, String ... typeIds) { 608 return MessageFormat.format(Messages.getString(Messages.RulesToolkit_RULE_RECOMMENDS_EVENTS), 609 getDisabledEventTypeNames(items, typeIds)); 610 } 611 612 /** 613 * Gets the Java version for the recording the provided {@link IItemCollection} represents. 614 * 615 * @param items 616 * the recording to find the version of 617 * @return an object representing the Java version of the VM the items originate from 618 */ 619 public static JavaVersion getJavaSpecVersion(IItemCollection items) { 620 IItemCollection versionProperties = items.apply(ItemFilters.and(JdkFilters.SYSTEM_PROPERTIES, 621 ItemFilters.equals(JdkAttributes.ENVIRONMENT_KEY, "java.vm.specification.version"))); //$NON-NLS-1$ 622 Set<String> vmSpecificationVersions = versionProperties 623 .getAggregate(Aggregators.distinct(JdkAttributes.ENVIRONMENT_VALUE)); 624 if (vmSpecificationVersions != null && vmSpecificationVersions.size() >= 1) { 625 return new JavaVersion(vmSpecificationVersions.iterator().next()); 626 } 627 // Fall back to using the major version number of the JVM version as Java specification version 628 JavaVersion jvmVersion = getJavaVersion(items); 629 if (jvmVersion != null) { 630 return new JavaVersion(jvmVersion.getMajorVersion()); 631 } 632 return null; 633 } 634 635 /** 636 * @param items 637 * the items to look for the JVM version in. 638 * @return the parsed JavaVersion, or null if no VM information event with the JVM version could 639 * be found. 640 */ 641 public static JavaVersion getJavaVersion(IItemCollection items) { 642 String jvmVersion = items.getAggregate(JdkAggregators.JVM_VERSION); 643 return getJavaVersion(jvmVersion); 644 } 645 646 /** 647 * An exponential mapping from 0/infinity to 0/74 passing through 25 at limit. This approaches 648 * 74 at about 300-400% of the limit. 649 * 650 * @param value 651 * Input value. Negative values will be treated as zero. 652 * @param x1 653 * Return 25 if value is equal to this. Must be more than zero. 654 * @return A value between 0 and 74. 655 */ 656 public static double mapExp74(double value, double x1) { 657 return mapExp(value, 74, x1, 25); 658 } 659 660 /** 661 * An exponential mapping from 0/infinity to 0/100 passing through 75 at limit. This approaches 662 * 100 at about 300-400% of the limit. 663 * 664 * @param value 665 * Input value. Negative values will be treated as zero. 666 * @param x1 667 * Return 75 if value is equal to this. Must be more than zero. 668 * @return A value between 0 and 100. 669 */ 670 public static double mapExp100(double value, double x1) { 671 return mapExp(value, 100, x1, 75); 672 } 673 674 /** 675 * An exponential mapping from 0/infinity to 0/100 passing through y1 at x1. 676 * 677 * @param value 678 * Input value. Negative values will be treated as zero. 679 * @param x1 680 * Return y1 if value is equal to this. Must be more than zero. 681 * @param y1 682 * Return value at x1. Must be more than zero and less than 100. 683 * @return A value between 0 and 100. 684 */ 685 public static double mapExp100Y(double value, double x1, double y1) { 686 return mapExp(value, 100, x1, y1); 687 } 688 689 /** 690 * An exponential mapping from 0/infinity to 0/100 passing through 25 and 75 at limits. This 691 * approaches 100 at about 300-400% of the 75 limit. 692 * 693 * @param value 694 * Input value. Negative values will be treated as zero. 695 * @param x1 696 * Return 25 if value is equal to this. Must be more than zero. 697 * @param x2 698 * Return 75 if value is equal to this. Must be more than x1. 699 * @return A value between 0 and 100. 700 */ 701 public static double mapExp100(double value, double x1, double x2) { 702 return mapExp(value, 100, x1, 25, x2, 75); 703 } 704 705 /** 706 * @param value 707 * Input value. Negative values will be treated as zero. 708 * @param ceiling 709 * Max return value. Must be more than zero. 710 * @param x1 711 * Return y1 if value is equal to this. Must be more than zero. 712 * @param y1 713 * Return value at x1. Must be more than zero and less than ceiling. 714 * @return A value between 0 and ceiling. 715 */ 716 // FIXME: We might want to have a wider input range that produces discernible output values. 717 public static double mapExp(double value, double ceiling, double x1, double y1) { 718 if (value < 0) { 719 return 0; 720 } 721 double k = Math.log(1 - y1 / ceiling) / x1; 722 return ceiling * (1 - Math.exp(k * value)); 723 } 724 725 /** 726 * @param value 727 * Input value. Negative values will be treated as zero. 728 * @param ceiling 729 * Max return value. Must be more than zero. 730 * @param x1 731 * Return y1 if value is equal to this. Must be more than zero. 732 * @param y1 733 * Return y1 at x1. Must be more than zero and less than y2. 734 * @param x2 735 * Return y2 if value is equal to this. Must be more than x1. 736 * @param y2 737 * Return y2 at x2. Must be more than y1 and less than ceiling. 738 * @return A value between 0 and ceiling. 739 */ 740 private static double mapExp(double value, double ceiling, double x1, double y1, double x2, double y2) { 741 if (value < 0) { 742 return 0; 743 } 744 if (value < x1) { 745 return y1 / x1 * value; 746 } 747 return y1 + mapExp(value - x1, ceiling - y1, x2 - x1, y2 - y1); 748 } 749 750 /** 751 * An multi-linear mapping from 0/1 to 0/100 passing through 25 and 75 at limits. 752 * 753 * @param value 754 * Input value. Negative values will be treated as zero. 755 * @param x1 756 * Return 25 if value is equal to this. Must be more than zero. 757 * @param x2 758 * Return 75 if value is equal to this. Must be more than x1. 759 * @return A value between 0 and 100. 760 */ 761 public static double mapLin100(double value, double x1, double x2) { 762 if (value <= 0) { 763 return 0; 764 } 765 if (value >= 1) { 766 return 1; 767 } 768 if (value <= x1) { 769 return value * 25 / x1; 770 } 771 if (value <= x2) { 772 return 25 + (value - x1) * (75 - 25) / (x2 - x1); 773 } 774 return 75 + (value - x2) * (100 - 75) / (1 - x2); 775 } 776 777 /** 778 * Each group is represented by the number of elements that belong in that group, elements are 779 * grouped by accessor value. 780 * <p> 781 * For example, the items {A, B, C, A, B, A, A} will become {1, 2, 4} 782 * 783 * @param items 784 * input items 785 * @param accessorFactory 786 * a factory that provides accessors for the input item types 787 * @return A sorted list of counts, one for each unique value that the accessor computes from 788 * the input items, that tells how many input items gave that accessor value. 789 */ 790 public static <T> List<IntEntry<T>> calculateGroupingScore( 791 IItemCollection items, IAccessorFactory<T> accessorFactory) { 792 EntryHashMap<T, IntEntry<T>> map = MapToolkit.createIntMap(1000, 0.5f); 793 for (IItemIterable ii : items) { 794 IMemberAccessor<? extends T, IItem> accessor = accessorFactory.getAccessor(ii.getType()); 795 if (accessor == null) { 796 continue; 797 } 798 for (IItem item : ii) { 799 T member = accessor.getMember(item); 800 if (member != null) { 801 IntEntry<T> entry = map.get(member, true); 802 entry.setValue(entry.getValue() + 1); 803 } 804 } 805 } 806 List<IntEntry<T>> array = IteratorToolkit.toList(map.iterator(), map.size()); 807 Collections.sort(array); 808 return array; 809 } 810 811 /** 812 * Calculates a balance for entries, where later elements get a higher relevance than earlier 813 * elements. 814 * <p> 815 * For example the values 1, 1, 2, 5 will get the total score 5/9/1 + 2/9/2 + 1/9/3 + 1/9/4 816 * 817 * @param array 818 * input values 819 * @return the balance score 820 */ 821 public static <T> double calculateBalanceScore(List<IntEntry<T>> array) { 822 int totalCount = 0; 823 for (IntEntry<T> e : array) { 824 totalCount += e.getValue(); 825 } 826 double score = 0; 827 for (int i = array.size() - 1; i >= 0; i--) { 828 int index = array.size() - i; 829 score += ((double) array.get(i).getValue()) / totalCount / index; 830 } 831 return score; 832 } 833 834 /** 835 * Get the duration for item within the specified window 836 * 837 * @param windowStart 838 * window start 839 * @param windowEnd 840 * window end 841 * @param item 842 * item to get duration for 843 * @return duration within window 844 */ 845 public static IQuantity getDurationInWindow(IQuantity windowStart, IQuantity windowEnd, IItem item) { 846 IQuantity start = getStartTime(item); 847 IQuantity end = getEndTime(item); 848 IQuantity startCapped = start.compareTo(windowStart) > 0 ? start : windowStart; 849 IQuantity endCapped = end.compareTo(windowEnd) > 0 ? windowEnd : end; 850 IQuantity durationCapped = endCapped.subtract(startCapped); 851 return durationCapped; 852 } 853 854 /** 855 * Maps the input value into a value between the minimum and maximum values (exclusive) using a 856 * sigmoidal curve with the given parameters. Minimum and maximum values are asymptotes, so will 857 * never be mapped to. If you want to map from [0,1] to (0,100) using this you should set a low 858 * inflection point and a single digit high curve fit with a low curve fit around 150. This will 859 * lead to exponential growth until the midway point where it will start growing 860 * logarithmically. 861 * 862 * @param input 863 * the value to map 864 * @param minimum 865 * the maximum value to map to (exclusive) 866 * @param maximum 867 * the minimum value to map to (exclusive) 868 * @param lowCurveFit 869 * fitting parameter for the lower end of the curve 870 * @param inflectionPoint 871 * the inflection point of the curve (where input leads to 1/3 between min and max) 872 * @param highCurveFit 873 * fitting parameter for the higher end of the curve 874 * @return a mapped value in the range (minimum,maximum) 875 */ 876 public static double mapSigmoid( 877 double input, double minimum, double maximum, double lowCurveFit, double inflectionPoint, double highCurveFit) { 878 // https://www.desmos.com/calculator/aylt9wv1x0 to view the curve 879 double g = Math.exp(lowCurveFit * (inflectionPoint - input)); 880 double h = Math.exp(highCurveFit * (inflectionPoint - input)); 881 return minimum + (maximum / (1 + g + h)); 882 } 883 884 private static IQuantity getSettingMaxPeriod(Iterable<String> settingsValues) { 885 IQuantity maxPeriod = null; 886 for (String s : settingsValues) { 887 try { 888 if (REC_SETTING_PERIOD_EVERY_CHUNK.equals(s)) { 889 return null; 890 } 891 IQuantity p = parsePersistedJvmTimespan(s); 892 if (maxPeriod == null || maxPeriod.compareTo(p) < 0) { 893 maxPeriod = p; 894 } 895 } catch (QuantityConversionException e) { 896 throw new RuntimeException(e); 897 } 898 } 899 return maxPeriod; 900 } 901 902 private static Set<String> getPeriodSettings(IItemCollection items, String ... typeIds) { 903 IItemFilter filter = getSettingsFilter(REC_SETTING_NAME_PERIOD, typeIds); 904 return items.apply(filter).getAggregate(Aggregators.distinct(JdkAttributes.REC_SETTING_VALUE)); 905 } 906 907 private static String getDisabledEventTypeNames(IItemCollection items, String ... typeIds) { 908 return getEventTypeNames(items.apply(createEnablementFilter(false, typeIds))); 909 } 910 911 private static String getEventTypeNames(IItemCollection items, String ... typeIds) { 912 return getEventTypeNames(items.apply(getSettingsFilter(REC_SETTING_NAME_ENABLED, typeIds))); 913 } 914 915 private static String getEventTypeNames(IItemCollection items) { 916 Set<String> names = items.getAggregate(Aggregators.distinct("", TYPE_NAME_ACCESSOR_FACTORY)); //$NON-NLS-1$ 917 if (names == null) { 918 return null; 919 } 920 List<String> quotedNames = new ArrayList<>(); 921 for (String name : names) { 922 quotedNames.add("'" + name + "'"); //$NON-NLS-1$ //$NON-NLS-2$ 923 } 924 Collections.sort(quotedNames); 925 return StringToolkit.join(quotedNames, ", "); //$NON-NLS-1$ 926 } 927 928 private static IItemFilter createEnablementFilter(boolean enabled, String ... typeIds) { 929 IItemFilter settingsFilter = getSettingsFilter(REC_SETTING_NAME_ENABLED, typeIds); 930 IItemFilter enabledFilter = ItemFilters.equals(JdkAttributes.REC_SETTING_VALUE, 931 enabled ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); 932 IItemFilter enablementFilter = ItemFilters.and(settingsFilter, enabledFilter); 933 return enablementFilter; 934 } 935 936 private static boolean internalHasEvents(IItemCollection items, String typeId) { 937 return items.apply(ItemFilters.type(typeId)).hasItems(); 938 } 939 940 private static Set<String> getAvailableTypeIds(IItemCollection items) { 941 Set<String> ids = new HashSet<>(); 942 for (IItemIterable iterable : items) { 943 ids.add(iterable.getType().getIdentifier()); 944 } 945 return ids; 946 } 947 948 /** 949 * @param vmInfoVersionString 950 * the JVM version information as presented in the VM information events, containing 951 * both the JVM and JDK version numbers. 952 * @return the parsed JavaVersion. 953 */ 954 public static JavaVersion getJavaVersion(String vmInfoVersionString) { 955 if (vmInfoVersionString != null) { 956 Matcher versionMatcher = VERSION_PATTERN.matcher(vmInfoVersionString); 957 if (versionMatcher.matches()) { 958 String versionString = versionMatcher.group(1); 959 return new JavaVersion(versionString); 960 } 961 } 962 return null; 963 } 964 965 /** 966 * Gets the {@link IType} representation of a specific event type in an {@link IItemCollection}. 967 * 968 * @param items 969 * the items to find the type in 970 * @param typeId 971 * the event type id to find the type object of 972 * @return an object representing the type with the specified id in the provided item collection 973 */ 974 public static IType<IItem> getType(IItemCollection items, String typeId) { 975 for (IItemIterable iter : items) { 976 if (iter.getType().getIdentifier().equals(typeId)) { 977 return iter.getType(); 978 } 979 } 980 return null; 981 } 982 983 /** 984 * Gets a {@link Result} object representing a not applicable result due to a missing attribute. 985 * 986 * @param rule 987 * the rule which could not be evaluated 988 * @param type 989 * the type of the item which is missing a required attribute 990 * @param attribute 991 * the attribute that is missing 992 * @return an object that represents the not applicable result due to the missing attribute 993 */ 994 public static Result getMissingAttributeResult(IRule rule, IType<IItem> type, IAttribute<?> attribute) { 995 return getNotApplicableResult(rule, 996 MessageFormat.format(Messages.getString(Messages.RulesToolkit_ATTRIBUTE_NOT_FOUND), 997 attribute.getIdentifier(), type.getIdentifier()), 998 MessageFormat.format(Messages.getString(Messages.RulesToolkit_ATTRIBUTE_NOT_FOUND_LONG), 999 attribute.getIdentifier(), type.getIdentifier())); 1000 } 1001 1002 /** 1003 * Creates a thread and range filter for a set of related events. It assumes all the events 1004 * provided are related and spans one contiguous time range per thread. The resulting filter 1005 * will include all the original events plus any other occurred in the same thread during the 1006 * time period (per thread) spanned by the events in the collection. Note that this is an 1007 * expensive operation. Use with care. 1008 * 1009 * @param items 1010 * a collection of related items. 1011 * @return a filter for the thread and time range. 1012 */ 1013 public static TimeRangeThreadFilter createThreadsAndRangesFilter(IItemCollection items) { 1014 Map<IMCThread, Range> rangeMap = new HashMap<>(); 1015 1016 for (IItemIterable iter : items) { 1017 for (IItem item : iter) { 1018 IMCThread thread = getThread(item); 1019 if (thread == null) { 1020 continue; 1021 } 1022 if (!rangeMap.containsKey(thread)) { 1023 rangeMap.put(thread, new Range(getStartTime(item), getEndTime(item))); 1024 } else { 1025 Range r = rangeMap.get(thread); 1026 IQuantity startTime = getStartTime(item); 1027 IQuantity endTime = getEndTime(item); 1028 if (startTime.compareTo(r.startTime) < 0 || endTime.compareTo(r.endTime) > 0) { 1029 IQuantity newStartTime = startTime.compareTo(r.startTime) < 0 ? startTime : r.startTime; 1030 IQuantity newEndTime = endTime.compareTo(r.endTime) > 0 ? endTime : r.endTime; 1031 rangeMap.put(thread, new Range(newStartTime, newEndTime)); 1032 } 1033 } 1034 } 1035 } 1036 return new TimeRangeThreadFilter(rangeMap); 1037 } 1038 1039 /** 1040 * Creates a range filter for an event. The range will span the same time as the event. 1041 * 1042 * @param item 1043 * the event for which to create the range filter 1044 * @return a filter for the time range of the event. 1045 */ 1046 public static IItemFilter createRangeFilter(IItem item) { 1047 return new TimeRangeFilter(new Range(getStartTime(item), getEndTime(item))); 1048 } 1049 1050 /** 1051 * Convenience method for getting the start time value from a specific event. 1052 * 1053 * @param item 1054 * the event to get the start time from 1055 * @return the start time of the provided event 1056 */ 1057 public static IQuantity getStartTime(IItem item) { 1058 return RulesToolkit.getValue(item, JfrAttributes.START_TIME); 1059 } 1060 1061 /** 1062 * Convenience method to get the end time value from a specific event. 1063 * 1064 * @param item 1065 * the event to get the end time from 1066 * @return the end time of the provided event 1067 */ 1068 public static IQuantity getEndTime(IItem item) { 1069 return RulesToolkit.getValue(item, JfrAttributes.END_TIME); 1070 } 1071 1072 /** 1073 * Convenience method to get the duration value from a specific event. 1074 * 1075 * @param item 1076 * the event to get the duration from 1077 * @return the duration of the provided event 1078 */ 1079 public static IQuantity getDuration(IItem item) { 1080 return RulesToolkit.getValue(item, JfrAttributes.DURATION); 1081 } 1082 1083 /** 1084 * Convenience method to get the event thread value from a specific event. 1085 * 1086 * @param item 1087 * the event to get the thread value from 1088 * @return the thread the provided event occurred in 1089 */ 1090 public static IMCThread getThread(IItem item) { 1091 return getOptionalValue(item, JfrAttributes.EVENT_THREAD); 1092 } 1093 1094 /** 1095 * Returns the value, or null if no accessor is available. 1096 */ 1097 private static <T> T getOptionalValue(IItem item, IAccessorFactory<T> attribute) { 1098 IType<IItem> itemType = ItemToolkit.getItemType(item); 1099 IMemberAccessor<? extends T, IItem> accessor = attribute.getAccessor(itemType); 1100 if (accessor == null) { 1101 return null; 1102 } 1103 return accessor.getMember(item); 1104 } 1105 1106 /** 1107 * Calculates the ratio between two {@link IQuantity} values of compatible, linear kind and 1108 * returns it represented as a percentage. 1109 * 1110 * @param antecedent 1111 * the antecedent (numerator) value 1112 * @param consequent 1113 * the consequent (denominator) value 1114 * @return the ratio between the two values as a percentage 1115 */ 1116 public static IQuantity toRatioPercent(IQuantity antecedent, IQuantity consequent) { 1117 return UnitLookup.PERCENT.quantity(antecedent.ratioTo(consequent) * 100f); 1118 } 1119 1120 /** 1121 * Same calculation as {@link RulesToolkit#toRatioPercent(IQuantity, IQuantity)} but it returns 1122 * the percentage as a string instead. 1123 * 1124 * @param antecedent 1125 * the antecedent (numerator) value 1126 * @param consequent 1127 * the consequent (denominator) value 1128 * @return the ratio between the two values as a percentage, as a string 1129 */ 1130 public static String toRatioPercentString(IQuantity antecedent, IQuantity consequent) { 1131 return toRatioPercent(antecedent, consequent).displayUsing(IDisplayable.AUTO); 1132 } 1133 1134 /** 1135 * Retrieves all topics that have rules associated with them. 1136 * 1137 * @return all topics associated with any rule 1138 */ 1139 public static Collection<String> getAllTopics() { 1140 Set<String> topics = new HashSet<>(); 1141 for (IRule r : RuleRegistry.getRules()) { 1142 topics.add(r.getTopic()); 1143 } 1144 return topics; 1145 } 1146 1147 /** 1148 * Evaluates a collection of rules in parallel threads. The method returns a map of rules and 1149 * {@link Future future} results that are scheduled to run using the specified number of 1150 * threads. 1151 * <p> 1152 * You can use a single threaded loop over the returned futures to {@link Future#get() get} the 1153 * results. 1154 * <p> 1155 * If evaluation of a rule fails, then the get method of the corresponding future will throw an 1156 * {@link ExecutionException}. 1157 * 1158 * @param rules 1159 * rules to run 1160 * @param items 1161 * items to evaluate 1162 * @param preferences 1163 * See {@link IRule#evaluate(IItemCollection, IPreferenceValueProvider)}. If 1164 * {@code null}, then default values will be used. 1165 * @param nThreads 1166 * The number or parallel threads to use when evaluating. If 0, then the number of 1167 * available processors will be used. 1168 * @return a map from rules to result futures 1169 */ 1170 public static Map<IRule, Future<Result>> evaluateParallel( 1171 Collection<IRule> rules, IItemCollection items, IPreferenceValueProvider preferences, int nThreads) { 1172 if (preferences == null) { 1173 preferences = IPreferenceValueProvider.DEFAULT_VALUES; 1174 } 1175 if (nThreads < 1) { 1176 nThreads = Runtime.getRuntime().availableProcessors(); 1177 } 1178 Map<IRule, Future<Result>> resultFutures = new HashMap<>(); 1179 Queue<RunnableFuture<Result>> futureQueue = new ConcurrentLinkedQueue<>(); 1180 for (IRule rule : rules) { 1181 RunnableFuture<Result> resultFuture = rule.evaluate(items, preferences); 1182 resultFutures.put(rule, resultFuture); 1183 futureQueue.add(resultFuture); 1184 } 1185 for (int i = 0; i < nThreads; i++) { 1186 RuleEvaluator re = new RuleEvaluator(futureQueue); 1187 Thread t = new Thread(re); 1188 t.start(); 1189 } 1190 return resultFutures; 1191 } 1192 1193 private static class RuleEvaluator implements Runnable { 1194 private Queue<RunnableFuture<Result>> futureQueue; 1195 1196 public RuleEvaluator(Queue<RunnableFuture<Result>> futureQueue) { 1197 this.futureQueue = futureQueue; 1198 } 1199 1200 @Override 1201 public void run() { 1202 RunnableFuture<Result> resultFuture; 1203 while ((resultFuture = futureQueue.poll()) != null) { 1204 resultFuture.run(); 1205 } 1206 } 1207 } 1208 1209 /** 1210 * Gets the second frame in the most common stack trace. Useful when showing what called a 1211 * interesting method, like for example java.lang.Integer.valueOf (aka autoboxing) 1212 * 1213 * @param items 1214 * the item collection to build the aggregated stack trace on 1215 * @return a stack trace frame 1216 */ 1217 // FIXME: Generalize this a bit, get the top N frames 1218 public static String getSecondFrameInMostCommonTrace(IItemCollection items) { 1219 FrameSeparator sep = new FrameSeparator(FrameSeparator.FrameCategorization.LINE, false); 1220 StacktraceModel stacktraceModel = new StacktraceModel(false, sep, items); 1221 Branch firstBranch = stacktraceModel.getRootFork().getBranch(0); 1222 StacktraceFrame secondFrame = null; 1223 if (firstBranch.getTailFrames().length > 0) { 1224 secondFrame = firstBranch.getTailFrames()[0]; 1225 } else if (firstBranch.getEndFork().getBranchCount() > 0) { 1226 secondFrame = firstBranch.getEndFork().getBranch(0).getFirstFrame(); 1227 } else { 1228 return null; 1229 } 1230 /* 1231 * FIXME: Consider defining the method formatting based on preferences. 1232 * 1233 * Currently it's a compromise between keeping the length short, but still being able to 1234 * identify the actual method, even if the line number is a bit incorrect. 1235 */ 1236 return StacktraceFormatToolkit.formatFrame(secondFrame.getFrame(), sep, false, false, true, true, true, false); 1237 } 1238 1239 /** 1240 * Convenience method for parsing the -XX:FlightRecorderOptions JVM flag. Since this is one flag 1241 * that contains all flight recorder settings in a comma separated list as a single string it is 1242 * useful to have one place to get the actual setting/value pairs from an 1243 * {@link IItemCollection}. 1244 * 1245 * @param items 1246 * an item collection containing at least one {@link JdkTypeIDs#STRING_FLAG} event 1247 * with the value "FlightRecorderOptions" 1248 * @return a setting/value map for all FlightRecorderOptions 1249 */ 1250 public static Map<String, String> getFlightRecorderOptions(IItemCollection items) { 1251 Map<String, String> options = new HashMap<>(); 1252 IItemFilter stringFlagsFilter = ItemFilters.type(JdkTypeIDs.STRING_FLAG); 1253 IItemFilter optionsFilter = ItemFilters.matches(JdkAttributes.FLAG_NAME, "FlightRecorderOptions"); //$NON-NLS-1$ 1254 IItemCollection optionsFlag = items.apply(ItemFilters.and(stringFlagsFilter, optionsFilter)); 1255 Set<String> optionsValues = optionsFlag.getAggregate(Aggregators.distinct(JdkAttributes.FLAG_VALUE_TEXT)); 1256 if (optionsValues != null && optionsValues.size() > 0) { 1257 String optionsValue = optionsValues.iterator().next(); 1258 String[] allOptions = optionsValue.split(","); //$NON-NLS-1$ 1259 for (String optionAndValue : allOptions) { 1260 String[] optionAndValueSplit = optionAndValue.split("="); //$NON-NLS-1$ 1261 if (optionAndValueSplit.length >= 2) { 1262 options.put(optionAndValueSplit[0], optionAndValueSplit[1]); 1263 } else { 1264 options.put(optionAndValue, ""); //$NON-NLS-1$ 1265 } 1266 } 1267 } 1268 return options; 1269 } 1270 1271 /** 1272 * Checks if the timerange spanned by the items is shorter than the limit, and returns a 1273 * informative text message if that is the case. 1274 * 1275 * @param items 1276 * the item collection to get recording range from 1277 * @param shortRecordingLimit 1278 * limit for a short recording 1279 * @return a text message informing that this is a short recording, or null if recording is not 1280 * short 1281 */ 1282 public static String getShortRecordingInfo(IItemCollection items, IQuantity shortRecordingLimit) { 1283 IQuantity recordingDuration = getItemRange(items); 1284 boolean shortRecording = recordingDuration.compareTo(shortRecordingLimit) < 0; 1285 if (shortRecording) { 1286 return MessageFormat.format(Messages.getString(Messages.Result_SHORT_RECORDING), 1287 recordingDuration.displayUsing(IDisplayable.AUTO), 1288 shortRecordingLimit.displayUsing(IDisplayable.AUTO)); 1289 } 1290 return null; 1291 } 1292 1293 private static IQuantity getItemRange(IItemCollection items) { 1294 IQuantity first = items.getAggregate(JdkAggregators.FIRST_ITEM_START); 1295 IQuantity last = items.getAggregate(JdkAggregators.LAST_ITEM_END); 1296 1297 return last.subtract(first); 1298 } 1299 1300 /** 1301 * Sorts map according to values. 1302 * 1303 * @param map 1304 * the map to sort 1305 * @param sortAscending 1306 * true if the sorting should be from lower to higher, false for higher to lower 1307 * @return sorted map 1308 */ 1309 public static Map<String, Integer> sortMap(final Map<String, Integer> map, final boolean sortAscending) { 1310 List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet()); 1311 Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() { 1312 @Override 1313 public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) { 1314 if (sortAscending) { 1315 return o1.getValue().compareTo(o2.getValue()); 1316 } else { 1317 return o2.getValue().compareTo(o1.getValue()); 1318 } 1319 } 1320 }); 1321 final Map<String, Integer> sortedMap = new LinkedHashMap<>(); 1322 for (Map.Entry<String, Integer> entry : entries) { 1323 sortedMap.put(entry.getKey(), entry.getValue()); 1324 } 1325 return sortedMap; 1326 } 1327 }