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