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