1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  *
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  *
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  *
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  *
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.rules.jdk.latency;
  34 
  35 import 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.Iterator;
  43 import java.util.List;
  44 import java.util.Map;
  45 import java.util.Map.Entry;
  46 import java.util.Set;
  47 import java.util.concurrent.Callable;
  48 import java.util.concurrent.FutureTask;
  49 import java.util.concurrent.RunnableFuture;
  50 
  51 import org.openjdk.jmc.common.IDisplayable;
  52 import org.openjdk.jmc.common.IMCMethod;
  53 import org.openjdk.jmc.common.IMCStackTrace;
  54 import org.openjdk.jmc.common.item.Aggregators;
  55 import org.openjdk.jmc.common.item.Aggregators.CountConsumer;
  56 import org.openjdk.jmc.common.item.GroupingAggregator;
  57 import org.openjdk.jmc.common.item.GroupingAggregator.GroupEntry;
  58 import org.openjdk.jmc.common.item.IAggregator;
  59 import org.openjdk.jmc.common.item.IItem;
  60 import org.openjdk.jmc.common.item.IItemCollection;
  61 import org.openjdk.jmc.common.item.IItemFilter;
  62 import org.openjdk.jmc.common.item.IItemIterable;
  63 import org.openjdk.jmc.common.item.IMemberAccessor;
  64 import org.openjdk.jmc.common.item.IType;
  65 import org.openjdk.jmc.common.item.ItemFilters;
  66 import org.openjdk.jmc.common.unit.IQuantity;
  67 import org.openjdk.jmc.common.unit.IRange;
  68 import org.openjdk.jmc.common.unit.QuantityConversionException;
  69 import org.openjdk.jmc.common.unit.QuantityRange;
  70 import org.openjdk.jmc.common.unit.UnitLookup;
  71 import org.openjdk.jmc.common.util.FormatToolkit;
  72 import org.openjdk.jmc.common.util.IPreferenceValueProvider;
  73 import org.openjdk.jmc.common.util.Pair;
  74 import org.openjdk.jmc.common.util.TypedPreference;
  75 import org.openjdk.jmc.flightrecorder.JfrAttributes;
  76 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes;
  77 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters;
  78 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs;
  79 import org.openjdk.jmc.flightrecorder.rules.IRule;
  80 import org.openjdk.jmc.flightrecorder.rules.Result;
  81 import org.openjdk.jmc.flightrecorder.rules.jdk.dataproviders.MethodProfilingDataProvider;
  82 import org.openjdk.jmc.flightrecorder.rules.jdk.messages.internal.Messages;
  83 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics;
  84 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit;
  85 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit.EventAvailability;
  86 import org.openjdk.jmc.flightrecorder.rules.util.SlidingWindowToolkit;
  87 import org.openjdk.jmc.flightrecorder.rules.util.SlidingWindowToolkit.IUnorderedWindowVisitor;
  88 import org.openjdk.jmc.flightrecorder.stacktrace.StacktraceModel;
  89 
  90 /**
  91  * Rule that calculates the top method balance in a sliding window throughout the recording with a
  92  * relevance calculated by the ratio of samples to maximum samples for that period.
  93  */
  94 public class MethodProfilingRule implements IRule {
  95 
  96         /**
  97          * Constant value of the maximum number of samples the JVM attempts per sampling period.
  98          */
  99         private static final double SAMPLES_PER_PERIOD = 5;
 100 
 101         /**
 102          * Constant value of the maximum number of stack frames to display for the hottest path.
 103          */
 104         private static final int MAX_STACK_DEPTH = 10;
 105 
 106         /**
 107          * A simple class for storing execution sample period settings, allowing the sliding window to
 108          * get the correct samples for each time slice.
 109          */
 110         private static class PeriodRangeMap {
 111                 private List<Pair<IQuantity, IQuantity>> settingPairs = new ArrayList<>();
 112 
 113                 void addSetting(IQuantity settingTime, IQuantity setting) {
 114                         settingPairs.add(new Pair<>(settingTime, setting));
 115                 }
 116 
 117                 /**
 118                  * Gets the execution sample period that is in effect for the given timestamp.
 119                  *
 120                  * @param timestamp
 121                  *            the timestamp for which to find the given period setting
 122                  * @return an IQuantity representing the period setting for the period given
 123                  */
 124                 IQuantity getSetting(IQuantity timestamp) {
 125                         for (Pair<IQuantity, IQuantity> settingPair : settingPairs) {
 126                                 boolean isAfterOrAtSettingTime = settingPair.left.compareTo(timestamp) <= 0;
 127                                 if (isAfterOrAtSettingTime) {
 128                                         return settingPair.right;
 129                                 }
 130                         }
 131                         return null; // before first period setting event in recording, i.e. we should ignore any profiling events that get this result
 132                 }
 133 
 134                 void sort() {
 135                         Collections.sort(settingPairs, new Comparator<Pair<IQuantity, IQuantity>>() {
 136                                 @Override
 137                                 public int compare(Pair<IQuantity, IQuantity> p1, Pair<IQuantity, IQuantity> p2) {
 138                                         return p1.left.compareTo(p2.left); // sorting according to time of setting event
 139                                 }
 140                         });
 141                 }
 142         }
 143 
 144         private static class MethodProfilingWindowResult {
 145                 IMCMethod method;
 146                 IMCStackTrace path;
 147                 IQuantity ratioOfAllPossibleSamples;
 148                 IRange<IQuantity> window;
 149 
 150                 public MethodProfilingWindowResult(IMCMethod method, IMCStackTrace path, IQuantity ratio, IRange<IQuantity> window) {
 151                         this.method = method;
 152                         this.path = path;
 153                         this.ratioOfAllPossibleSamples = ratio;
 154                         this.window = window;
 155                 }
 156 
 157                 @Override
 158                 public String toString() {
 159                         return FormatToolkit.getHumanReadable(method, false, false, true, true, true, false) + " (" //$NON-NLS-1$
 160                                         + ratioOfAllPossibleSamples.displayUsing(IDisplayable.AUTO) + ") " //$NON-NLS-1$
 161                                         + window.displayUsing(IDisplayable.AUTO);
 162                 }
 163         }
 164 
 165         private static final String RESULT_ID = "MethodProfiling"; //$NON-NLS-1$
 166         public static final TypedPreference<IQuantity> WINDOW_SIZE = new TypedPreference<>(
 167                         "method.profiling.evaluation.window.size", //$NON-NLS-1$
 168                         Messages.getString(Messages.MethodProfilingRule_WINDOW_SIZE),
 169                         Messages.getString(Messages.MethodProfilingRule_WINDOW_SIZE_DESC), UnitLookup.TIMESPAN,
 170                         UnitLookup.SECOND.quantity(30));
 171         private static final List<TypedPreference<?>> CONFIG_ATTRIBUTES = Arrays.<TypedPreference<?>> asList(WINDOW_SIZE);
 172 
 173         /**
 174          * Private Callable implementation specifically used to avoid storing the FutureTask as a field.
 175          */
 176         private class MethodProfilingCallable implements Callable<Result> {
 177                 private FutureTask<Result> evaluationTask = null;
 178                 private IItemCollection items;
 179                 private IPreferenceValueProvider valueProvider;
 180 
 181                 private MethodProfilingCallable(IItemCollection items, IPreferenceValueProvider valueProvider) {
 182                         this.items = items;
 183                         this.valueProvider = valueProvider;
 184                 }
 185 
 186                 @Override
 187                 public Result call() throws Exception {
 188                         return getResult(items, valueProvider, evaluationTask);
 189                 }
 190 
 191                 void setTask(FutureTask<Result> task) {
 192                         evaluationTask = task;
 193                 }
 194         }
 195 
 196         @Override
 197         public RunnableFuture<Result> evaluate(final IItemCollection items, final IPreferenceValueProvider valueProvider) {
 198                 MethodProfilingCallable callable = new MethodProfilingCallable(items, valueProvider);
 199                 FutureTask<Result> evaluationTask = new FutureTask<>(callable);
 200                 callable.setTask(evaluationTask);
 201                 return evaluationTask;
 202         }
 203 
 204         private Result getResult(
 205                 IItemCollection items, IPreferenceValueProvider valueProvider, FutureTask<Result> evaluationTask) {
 206                 EventAvailability eventAvailability = RulesToolkit.getEventAvailability(items, JdkTypeIDs.EXECUTION_SAMPLE,
 207                                 JdkTypeIDs.RECORDING_SETTING);
 208                 if (eventAvailability != EventAvailability.AVAILABLE) {
 209                         return RulesToolkit.getEventAvailabilityResult(this, items, eventAvailability, JdkTypeIDs.EXECUTION_SAMPLE,
 210                                         JdkTypeIDs.RECORDING_SETTING);
 211                 }
 212 
 213                 PeriodRangeMap settings = new PeriodRangeMap();
 214                 IItemFilter settingsFilter = RulesToolkit.getSettingsFilter(RulesToolkit.REC_SETTING_NAME_PERIOD,
 215                                 JdkTypeIDs.EXECUTION_SAMPLE);
 216                 populateSettingsMap(items.apply(settingsFilter), settings);
 217 
 218                 IQuantity windowSize = valueProvider.getPreferenceValue(WINDOW_SIZE);
 219                 IQuantity slideSize = UnitLookup.SECOND.quantity(windowSize.ratioTo(UnitLookup.SECOND.quantity(2)));
 220 
 221                 List<MethodProfilingWindowResult> windowResults = new ArrayList<>();
 222                 IUnorderedWindowVisitor visitor = createWindowVisitor(settings, settingsFilter, windowSize, windowResults,
 223                                 evaluationTask);
 224                 SlidingWindowToolkit.slidingWindowUnordered(visitor, items, windowSize, slideSize);
 225                 // If a window visitor over a non empty quantity of events is guaranteed to always generate at minimum one raw score, this can be removed.
 226                 if (windowResults.isEmpty()) {
 227                         return RulesToolkit.getNotApplicableResult(this,
 228                                         Messages.getString(Messages.HotMethodsRuleFactory_NOT_ENOUGH_SAMPLES));
 229                 }
 230                 Pair<MethodProfilingWindowResult, Map<IMCStackTrace, MethodProfilingWindowResult>> interestingMethods = getInterestingMethods(
 231                                 windowResults);
 232                 Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod = interestingMethods.right;
 233                 MethodProfilingWindowResult mostInterestingResult = interestingMethods.left;
 234                 if (mostInterestingResult == null) { // Couldn't find any interesting methods
 235                         return new Result(this, 0, Messages.getString(Messages.HotMethodsRuleFactory_TEXT_OK));
 236                 }
 237                 double mappedScore = performSigmoidMap(
 238                                 mostInterestingResult.ratioOfAllPossibleSamples.doubleValueIn(UnitLookup.PERCENT_UNITY));
 239 
 240                 Result result = null;
 241                 if (mappedScore < 25) {
 242                         result = new Result(this, mappedScore, Messages.getString(Messages.HotMethodsRuleFactory_TEXT_OK));
 243                 } else {
 244                         String shortDescription = MessageFormat.format(Messages.getString(Messages.HotMethodsRuleFactory_TEXT_INFO),
 245                                         FormatToolkit.getHumanReadable(mostInterestingResult.method, false, false, true, false, true,
 246                                                         false),
 247                                         mostInterestingResult.ratioOfAllPossibleSamples.displayUsing(IDisplayable.AUTO),
 248                                         windowSize.displayUsing(IDisplayable.AUTO));
 249                         String formattedPath = "<ul>" + //$NON-NLS-1$
 250                                         FormatToolkit.getHumanReadable(mostInterestingResult.path, false, false, true, true, true, false,
 251                                                         MAX_STACK_DEPTH, null, "<li>", //$NON-NLS-1$
 252                                                         "</li>" //$NON-NLS-1$
 253                                                         ) + "</ul>"; //$NON-NLS-1$
 254                         String longDescription = MessageFormat.format(
 255                                         Messages.getString(Messages.HotMethodsRuleFactory_TEXT_INFO_LONG),
 256                                         buildResultList(percentByMethod),
 257                                         formattedPath
 258                                         );
 259                         result = new Result(this, mappedScore, shortDescription, shortDescription + "<p>" + longDescription); //$NON-NLS-1$
 260                 }
 261                 return result;
 262         }
 263 
 264         private String buildResultList(Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod) {
 265                 StringBuilder longList = new StringBuilder();
 266                 longList.append("<ul>"); //$NON-NLS-1$
 267                 for (Entry<IMCStackTrace, MethodProfilingWindowResult> entry : percentByMethod.entrySet()) {
 268                         longList.append("<li>"); //$NON-NLS-1$
 269                         longList.append(entry.getValue());
 270                         longList.append("</li>"); //$NON-NLS-1$
 271                 }
 272                 longList.append("</ul>"); //$NON-NLS-1$
 273                 return longList.toString();
 274         }
 275 
 276         private Pair<MethodProfilingWindowResult, Map<IMCStackTrace, MethodProfilingWindowResult>> getInterestingMethods(
 277                 List<MethodProfilingWindowResult> windowResults) {
 278                 Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod = new HashMap<>();
 279                 IQuantity maxRawScore = UnitLookup.PERCENT_UNITY.quantity(0);
 280                 MethodProfilingWindowResult mostInterestingResult = null;
 281                 for (MethodProfilingWindowResult result : windowResults) {
 282                         if (result != null) {
 283                                 if (result.ratioOfAllPossibleSamples.compareTo(maxRawScore) > 0) {
 284                                         mostInterestingResult = result;
 285                                         maxRawScore = result.ratioOfAllPossibleSamples;
 286                                 }
 287                                 if (result.path != null && performSigmoidMap(
 288                                                 result.ratioOfAllPossibleSamples.doubleValueIn(UnitLookup.PERCENT_UNITY)) >= 25) {
 289                                         MethodProfilingWindowResult r = percentByMethod.get(result.path);
 290                                         if (r == null || result.ratioOfAllPossibleSamples.compareTo(r.ratioOfAllPossibleSamples) > 0) {
 291                                                 percentByMethod.put(result.path, result);
 292                                         }
 293                                 }
 294                         }
 295                 }
 296                 return new Pair<>(mostInterestingResult, percentByMethod);
 297         }
 298 
 299         private double performSigmoidMap(double input) {
 300                 return RulesToolkit.mapSigmoid(input, 0, 100, 150, 0.03333, 7);
 301         }
 302 
 303         /**
 304          * Creates an IUnorderedWindowVisitor that is called on each slice in the recording and
 305          * generates the scores for each slice and places them in the rawScores list. The given
 306          * parameters that are also given to the slidingWindowUnordered call must be the same as in this
 307          * call.
 308          *
 309          * @param settings
 310          *            the settings map with all the times the execution sample event has a change of
 311          *            periodicity
 312          * @param settingsFilter
 313          *            the filter used to select the recording setting for the execution sample event
 314          * @param windowSize
 315          *            the size of the sliding window
 316          * @param rawScores
 317          *            the list of raw scores that will be populated by this visitor
 318          * @return an IUnorderedWindowVisitor implementation that will populate the rawScores list with
 319          *         raw score values
 320          */
 321         private IUnorderedWindowVisitor createWindowVisitor(
 322                 final PeriodRangeMap settings, final IItemFilter settingsFilter, final IQuantity windowSize,
 323                 final List<MethodProfilingWindowResult> rawScores, final FutureTask<Result> evaluationTask) {
 324                 return new IUnorderedWindowVisitor() {
 325                         @Override
 326                         public void visitWindow(IItemCollection items, IQuantity startTime, IQuantity endTime) {
 327                                 IRange<IQuantity> windowRange = QuantityRange.createWithEnd(startTime, endTime);
 328                                 if (RulesToolkit.getSettingMaxPeriod(items, JdkTypeIDs.EXECUTION_SAMPLE) == null) {
 329                                         Pair<IQuantity, IMCStackTrace> resultPair = performCalculation(items, settings.getSetting(startTime));
 330                                         if (resultPair != null) {
 331                                                 rawScores.add(new MethodProfilingWindowResult(resultPair.right.getFrames().get(0).getMethod(), resultPair.right, resultPair.left, windowRange));
 332                                         }
 333                                 } else {
 334                                         Set<IQuantity> settingTimes = items.apply(settingsFilter)
 335                                                         .getAggregate(Aggregators.distinct(JfrAttributes.START_TIME));
 336                                         IQuantity start = startTime;
 337                                         List<Pair<IQuantity, IMCStackTrace>> scores = new ArrayList<>(settingTimes.size());
 338                                         for (IQuantity settingTime : settingTimes) {
 339                                                 IItemFilter window = ItemFilters.interval(JfrAttributes.END_TIME, start, true, settingTime,
 340                                                                 true);
 341                                                 scores.add(performCalculation(items.apply(window), settings.getSetting(start)));
 342                                                 start = settingTime;
 343                                         }
 344                                         Map<IMCStackTrace, IQuantity> scoresByMethod = new HashMap<>();
 345                                         for (Pair<IQuantity, IMCStackTrace> score : scores) {
 346                                                 if (score != null) {
 347                                                         if (scoresByMethod.get(score.right) == null) {
 348                                                                 scoresByMethod.put(score.right, score.left);
 349                                                         } else {
 350                                                                 scoresByMethod.put(score.right, score.left.add(scoresByMethod.get(score.right)));
 351                                                         }
 352                                                 }
 353                                         }
 354                                         IQuantity sumScore = UnitLookup.PERCENT_UNITY.quantity(0);
 355                                         IMCStackTrace hottestPath = null;
 356                                         for (Entry<IMCStackTrace, IQuantity> entry : scoresByMethod.entrySet()) {
 357                                                 if (entry.getValue().compareTo(sumScore) > 0) {
 358                                                         hottestPath = entry.getKey();
 359                                                         sumScore = sumScore.add(entry.getValue());
 360                                                 }
 361                                         }
 362                                         IQuantity averageOfAllPossibleSamples = sumScore.multiply(1d / scores.size());
 363                                         IMCMethod hottestMethod = (hottestPath == null ? null : hottestPath.getFrames().get(0).getMethod());
 364                                         rawScores.add(new MethodProfilingWindowResult(hottestMethod, hottestPath, averageOfAllPossibleSamples, windowRange));
 365                                 }
 366                         }
 367 
 368                         @Override
 369                         public boolean shouldContinue() {
 370                                 return evaluationTask != null && !evaluationTask.isCancelled();
 371                         }
 372 
 373                         /**
 374                          * Performs the actual calculation of the score for the given period of the recording.
 375                          *
 376                          * @param items
 377                          *            the items to base the score on
 378                          * @param period
 379                          *            the periodicity to base the relevancy calculation on
 380                          * @return a double value in the interval [0,1] with 1 being a system in completely
 381                          *         saturated load with only one method called
 382                          */
 383                         private Pair<IQuantity, IMCStackTrace> performCalculation(IItemCollection items, IQuantity period) {
 384                                 IItemCollection filteredItems = items.apply(JdkFilters.EXECUTION_SAMPLE);
 385                                 final IMCMethod[] maxMethod = new IMCMethod[1];
 386                                 final IMCStackTrace[] maxPath = new IMCStackTrace[1];
 387                                 // Using this GroupingAggregator because it's the only way to extract the keys from the aggregation along with values
 388                                 IAggregator<IQuantity, ?> aggregator = GroupingAggregator.build("", "", //$NON-NLS-1$ //$NON_NLS_2$
 389                                                 MethodProfilingDataProvider.PATH_ACCESSOR_FACTORY, Aggregators.count(),
 390                                                 new GroupingAggregator.IGroupsFinisher<IQuantity, IMCStackTrace, CountConsumer>() {
 391 
 392                                                         @Override
 393                                                         public IType<IQuantity> getValueType() {
 394                                                                 return UnitLookup.NUMBER;
 395                                                         }
 396 
 397                                                         @Override
 398                                                         public IQuantity getValue(Iterable<? extends GroupEntry<IMCStackTrace, CountConsumer>> groupEntries) {
 399                                                                 HashMap<IMCMethod, IQuantity> map = new HashMap<>();
 400                                                                 HashMap<IMCMethod, IMCStackTrace> pathMap = new HashMap<>();
 401                                                                 int total = 0;
 402                                                                 // When we group by stack trace we can run into situations where the top frames are otherwise the same
 403                                                                 // for our purposes (finding the hottest method), but they differ by BCI, throwing off the count.
 404                                                                 // so we should collect further on the method for the top frame.
 405                                                                 for (GroupEntry<IMCStackTrace, CountConsumer> group : groupEntries) {
 406                                                                         total += group.getConsumer().getCount();
 407                                                                         IMCMethod topFrameMethod = group.getKey().getFrames().get(0).getMethod();
 408                                                                         if (map.get(topFrameMethod) == null) {
 409                                                                                 map.put(topFrameMethod, UnitLookup.NUMBER_UNITY.quantity(group.getConsumer().getCount()));
 410                                                                                 pathMap.put(topFrameMethod, group.getKey());
 411                                                                         } else {
 412                                                                                 IQuantity old = map.get(topFrameMethod);
 413                                                                                 map.put(topFrameMethod, old.add(UnitLookup.NUMBER_UNITY.quantity(group.getConsumer().getCount())));
 414                                                                         }
 415                                                                 }
 416                                                                 if (!pathMap.isEmpty() && !map.isEmpty()) {
 417                                                                         Entry<IMCMethod, IQuantity> topEntry = Collections.max(map.entrySet(), new Comparator<Entry<IMCMethod, IQuantity>>() {
 418                                                                                 @Override
 419                                                                                 public int compare(Entry<IMCMethod, IQuantity> arg0,
 420                                                                                                 Entry<IMCMethod, IQuantity> arg1) {
 421                                                                                         return arg0.getValue().compareTo(arg1.getValue());
 422                                                                                 }
 423                                                                         });
 424                                                                         maxPath[0] = pathMap.get(topEntry.getKey());
 425                                                                         maxMethod[0] = topEntry.getKey();
 426                                                                         return topEntry.getValue().multiply(1d/total);
 427                                                                 }
 428                                                                 return UnitLookup.NUMBER_UNITY.quantity(0);
 429                                                         }
 430                                 });
 431 
 432                                 IQuantity maxRatio = filteredItems.getAggregate(aggregator);
 433                                 Pair<IQuantity, IMCStackTrace> result = null;
 434                                 if (maxMethod[0] != null && maxRatio != null && period != null) { // ignoring if there are no samples or if we don't yet know the periodicity
 435                                         double periodsPerSecond = 1 / period.doubleValueIn(UnitLookup.SECOND);
 436                                         double maxSamplesPerSecond = SAMPLES_PER_PERIOD * periodsPerSecond;
 437                                         double samplesInPeriod = items
 438                                                         .getAggregate(Aggregators.count(ItemFilters.type(JdkTypeIDs.EXECUTION_SAMPLE)))
 439                                                         .doubleValueIn(UnitLookup.NUMBER_UNITY);
 440                                         double maxSamplesInPeriod = maxSamplesPerSecond * windowSize.doubleValueIn(UnitLookup.SECOND);
 441                                         double relevancy = samplesInPeriod / maxSamplesInPeriod;
 442                                         double highestRatioOfSamples = maxRatio.doubleValueIn(UnitLookup.NUMBER_UNITY);
 443                                         IQuantity percentOfAllPossibleSamples = UnitLookup.PERCENT_UNITY
 444                                                         .quantity(highestRatioOfSamples * relevancy);
 445                                         result = new Pair<>(percentOfAllPossibleSamples, maxPath[0]);
 446                                 }
 447                                 return result;
 448                         }
 449                 };
 450         }
 451 
 452         /**
 453          * Populates the settings map with all the period settings for the execution sample event found
 454          * in this recording.
 455          *
 456          * @param items
 457          *            the items to search for execution sample period events
 458          * @param settings
 459          *            the map to populate with the events
 460          */
 461         private void populateSettingsMap(IItemCollection items, final PeriodRangeMap settings) {
 462                 Iterator<IItemIterable> itemIterableIterator = items.iterator();
 463                 while (itemIterableIterator.hasNext()) {
 464                         IItemIterable itemIterable = itemIterableIterator.next();
 465                         IMemberAccessor<IQuantity, IItem> startTimeAccessor = JfrAttributes.START_TIME
 466                                         .getAccessor(itemIterable.getType());
 467                         IMemberAccessor<String, IItem> settingValueAccessor = JdkAttributes.REC_SETTING_VALUE
 468                                         .getAccessor(itemIterable.getType());
 469 
 470                         Iterator<IItem> itemIterator = itemIterable.iterator();
 471                         while (itemIterator.hasNext()) {
 472                                 IItem item = itemIterator.next();
 473                                 settings.addSetting(startTimeAccessor.getMember(item),
 474                                                 getValueQuantity(settingValueAccessor.getMember(item)));
 475                         }
 476                 }
 477                 settings.sort();
 478         }
 479 
 480         /**
 481          * Used to parse the value of a Recording Setting Period attribute
 482          *
 483          * @param settingValue
 484          *            the value to parse
 485          * @return an IQuantity representation of the passed String object
 486          */
 487         private IQuantity getValueQuantity(String settingValue) {
 488                 try {
 489                         if (RulesToolkit.REC_SETTING_PERIOD_EVERY_CHUNK.equals(settingValue)) {
 490                                 return null;
 491                         }
 492                         return RulesToolkit.parsePersistedJvmTimespan(settingValue);
 493                 } catch (QuantityConversionException e) {
 494                         throw new RuntimeException(e);
 495                 }
 496         }
 497 
 498         @Override
 499         public Collection<TypedPreference<?>> getConfigurationAttributes() {
 500                 return CONFIG_ATTRIBUTES;
 501         }
 502 
 503         @Override
 504         public String getId() {
 505                 return RESULT_ID;
 506         }
 507 
 508         @Override
 509         public String getName() {
 510                 return Messages.getString(Messages.MethodProfilingRule_RULE_NAME);
 511         }
 512 
 513         @Override
 514         public String getTopic() {
 515                 return JfrRuleTopics.METHOD_PROFILING_TOPIC;
 516         }
 517 
 518 }