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 import java.util.regex.Matcher;
  51 import java.util.regex.Pattern;
  52 
  53 import org.openjdk.jmc.common.IDisplayable;
  54 import org.openjdk.jmc.common.IMCFrame;
  55 import org.openjdk.jmc.common.IMCMethod;
  56 import org.openjdk.jmc.common.IMCStackTrace;
  57 import org.openjdk.jmc.common.item.Aggregators;
  58 import org.openjdk.jmc.common.item.Aggregators.CountConsumer;
  59 import org.openjdk.jmc.common.item.GroupingAggregator;
  60 import org.openjdk.jmc.common.item.GroupingAggregator.GroupEntry;
  61 import org.openjdk.jmc.common.item.IAggregator;
  62 import org.openjdk.jmc.common.item.IItem;
  63 import org.openjdk.jmc.common.item.IItemCollection;
  64 import org.openjdk.jmc.common.item.IItemFilter;
  65 import org.openjdk.jmc.common.item.IItemIterable;
  66 import org.openjdk.jmc.common.item.IMemberAccessor;
  67 import org.openjdk.jmc.common.item.IType;
  68 import org.openjdk.jmc.common.item.ItemFilters;
  69 import org.openjdk.jmc.common.unit.IQuantity;
  70 import org.openjdk.jmc.common.unit.IRange;
  71 import org.openjdk.jmc.common.unit.QuantityConversionException;
  72 import org.openjdk.jmc.common.unit.QuantityRange;
  73 import org.openjdk.jmc.common.unit.UnitLookup;
  74 import org.openjdk.jmc.common.util.FormatToolkit;
  75 import org.openjdk.jmc.common.util.IPreferenceValueProvider;
  76 import org.openjdk.jmc.common.util.MCStackTrace;
  77 import org.openjdk.jmc.common.util.Pair;
  78 import org.openjdk.jmc.common.util.TypedPreference;
  79 import org.openjdk.jmc.flightrecorder.JfrAttributes;
  80 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes;
  81 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters;
  82 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs;
  83 import org.openjdk.jmc.flightrecorder.rules.IRule;
  84 import org.openjdk.jmc.flightrecorder.rules.Result;
  85 import org.openjdk.jmc.flightrecorder.rules.jdk.dataproviders.MethodProfilingDataProvider;
  86 import org.openjdk.jmc.flightrecorder.rules.jdk.messages.internal.Messages;
  87 import org.openjdk.jmc.flightrecorder.rules.util.JfrRuleTopics;
  88 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit;
  89 import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit.EventAvailability;
  90 import org.openjdk.jmc.flightrecorder.rules.util.SlidingWindowToolkit;
  91 import org.openjdk.jmc.flightrecorder.rules.util.SlidingWindowToolkit.IUnorderedWindowVisitor;
  92 
  93 /**
  94  * Rule that calculates the top method balance in a sliding window throughout the recording with a
  95  * relevance calculated by the ratio of samples to maximum samples for that period.
  96  */
  97 public class MethodProfilingRule implements IRule {
  98 
  99         /**
 100          * Constant value of the maximum number of samples the JVM attempts per sampling period.
 101          */
 102         private static final double SAMPLES_PER_PERIOD = 5;
 103 
 104         /**
 105          * Constant value of the maximum number of stack frames to display for the hottest path.
 106          */
 107         private static final int MAX_STACK_DEPTH = 10;
 108 
 109         /**
 110          * A simple class for storing execution sample period settings, allowing the sliding window to
 111          * get the correct samples for each time slice.
 112          */
 113         private static class PeriodRangeMap {
 114                 private List<Pair<IQuantity, IQuantity>> settingPairs = new ArrayList<>();
 115 
 116                 void addSetting(IQuantity settingTime, IQuantity setting) {
 117                         settingPairs.add(new Pair<>(settingTime, setting));
 118                 }
 119 
 120                 /**
 121                  * Gets the execution sample period that is in effect for the given timestamp.
 122                  *
 123                  * @param timestamp
 124                  *            the timestamp for which to find the given period setting
 125                  * @return an IQuantity representing the period setting for the period given
 126                  */
 127                 IQuantity getSetting(IQuantity timestamp) {
 128                         for (Pair<IQuantity, IQuantity> settingPair : settingPairs) {
 129                                 boolean isAfterOrAtSettingTime = settingPair.left.compareTo(timestamp) <= 0;
 130                                 if (isAfterOrAtSettingTime) {
 131                                         return settingPair.right;
 132                                 }
 133                         }
 134                         return null; // before first period setting event in recording, i.e. we should ignore any profiling events that get this result
 135                 }
 136 
 137                 void sort() {
 138                         Collections.sort(settingPairs, new Comparator<Pair<IQuantity, IQuantity>>() {
 139                                 @Override
 140                                 public int compare(Pair<IQuantity, IQuantity> p1, Pair<IQuantity, IQuantity> p2) {
 141                                         return p1.left.compareTo(p2.left); // sorting according to time of setting event
 142                                 }
 143                         });
 144                 }
 145         }
 146 
 147         private static class MethodProfilingWindowResult {
 148                 IMCMethod method;
 149                 IMCStackTrace path;
 150                 IQuantity ratioOfAllPossibleSamples;
 151                 IRange<IQuantity> window;
 152 
 153                 public MethodProfilingWindowResult(IMCMethod method, IMCStackTrace path, IQuantity ratio, IRange<IQuantity> window) {
 154                         this.method = method;
 155                         this.path = path;
 156                         this.ratioOfAllPossibleSamples = ratio;
 157                         this.window = window;
 158                 }
 159 
 160                 @Override
 161                 public String toString() {
 162                         return FormatToolkit.getHumanReadable(method, false, false, true, true, true, false) + " (" //$NON-NLS-1$
 163                                         + ratioOfAllPossibleSamples.displayUsing(IDisplayable.AUTO) + ") " //$NON-NLS-1$
 164                                         + window.displayUsing(IDisplayable.AUTO);
 165                 }
 166         }
 167 
 168         private static final String RESULT_ID = "MethodProfiling"; //$NON-NLS-1$
 169         public static final TypedPreference<IQuantity> WINDOW_SIZE = new TypedPreference<>(
 170                         "method.profiling.evaluation.window.size", //$NON-NLS-1$
 171                         Messages.getString(Messages.MethodProfilingRule_WINDOW_SIZE),
 172                         Messages.getString(Messages.MethodProfilingRule_WINDOW_SIZE_DESC), UnitLookup.TIMESPAN,
 173                         UnitLookup.SECOND.quantity(30));
 174         public static final TypedPreference<String> EXCLUDED_PACKAGE_REGEXP = new TypedPreference<>(
 175                         "method.profiling.evaluation.excluded.package", //$NON-NLS-1$
 176                         Messages.getString(Messages.MethodProfilingRule_EXCLUDED_PACKAGES),
 177                         Messages.getString(Messages.MethodProfilingRule_EXCLUDED_PACKAGES_DESC),
 178                         UnitLookup.PLAIN_TEXT.getPersister(), "");
 179         private static final List<TypedPreference<?>> CONFIG_ATTRIBUTES = Arrays.<TypedPreference<?>> asList(WINDOW_SIZE, EXCLUDED_PACKAGE_REGEXP);
 180 
 181         /**
 182          * Private Callable implementation specifically used to avoid storing the FutureTask as a field.
 183          */
 184         private class MethodProfilingCallable implements Callable<Result> {
 185                 private FutureTask<Result> evaluationTask = null;
 186                 private IItemCollection items;
 187                 private IPreferenceValueProvider valueProvider;
 188 
 189                 private MethodProfilingCallable(IItemCollection items, IPreferenceValueProvider valueProvider) {
 190                         this.items = items;
 191                         this.valueProvider = valueProvider;
 192                 }
 193 
 194                 @Override
 195                 public Result call() throws Exception {
 196                         return getResult(items, valueProvider, evaluationTask);
 197                 }
 198 
 199                 void setTask(FutureTask<Result> task) {
 200                         evaluationTask = task;
 201                 }
 202         }
 203 
 204         @Override
 205         public RunnableFuture<Result> evaluate(final IItemCollection items, final IPreferenceValueProvider valueProvider) {
 206                 MethodProfilingCallable callable = new MethodProfilingCallable(items, valueProvider);
 207                 FutureTask<Result> evaluationTask = new FutureTask<>(callable);
 208                 callable.setTask(evaluationTask);
 209                 return evaluationTask;
 210         }
 211 
 212         private Result getResult(
 213                 IItemCollection items, IPreferenceValueProvider valueProvider, FutureTask<Result> evaluationTask) {
 214                 EventAvailability eventAvailability = RulesToolkit.getEventAvailability(items, JdkTypeIDs.EXECUTION_SAMPLE,
 215                                 JdkTypeIDs.RECORDING_SETTING);
 216                 if (eventAvailability != EventAvailability.AVAILABLE) {
 217                         return RulesToolkit.getEventAvailabilityResult(this, items, eventAvailability, JdkTypeIDs.EXECUTION_SAMPLE,
 218                                         JdkTypeIDs.RECORDING_SETTING);
 219                 }
 220 
 221                 PeriodRangeMap settings = new PeriodRangeMap();
 222                 IItemFilter settingsFilter = RulesToolkit.getSettingsFilter(RulesToolkit.REC_SETTING_NAME_PERIOD,
 223                                 JdkTypeIDs.EXECUTION_SAMPLE);
 224                 populateSettingsMap(items.apply(settingsFilter), settings);
 225 
 226                 IQuantity windowSize = valueProvider.getPreferenceValue(WINDOW_SIZE);
 227                 IQuantity slideSize = UnitLookup.SECOND.quantity(windowSize.ratioTo(UnitLookup.SECOND.quantity(2)));
 228                 String excludedPattern = valueProvider.getPreferenceValue(EXCLUDED_PACKAGE_REGEXP);
 229                 Pattern excludes;
 230                 try {
 231                         excludes = Pattern.compile(excludedPattern);
 232                 } catch (Exception e) {
 233                         // Make sure we don't blow up on an invalid pattern.
 234                         excludes = Pattern.compile("");
 235                 }
 236                 List<MethodProfilingWindowResult> windowResults = new ArrayList<>();
 237                 IUnorderedWindowVisitor visitor = createWindowVisitor(settings, settingsFilter, windowSize, windowResults,
 238                                 evaluationTask, excludes);
 239                 SlidingWindowToolkit.slidingWindowUnordered(visitor, items, windowSize, slideSize);
 240                 // 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.
 241                 if (windowResults.isEmpty()) {
 242                         return RulesToolkit.getNotApplicableResult(this,
 243                                         Messages.getString(Messages.HotMethodsRuleFactory_NOT_ENOUGH_SAMPLES));
 244                 }
 245                 Pair<MethodProfilingWindowResult, Map<IMCStackTrace, MethodProfilingWindowResult>> interestingMethods = getInterestingMethods(
 246                                 windowResults);
 247                 Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod = interestingMethods.right;
 248                 MethodProfilingWindowResult mostInterestingResult = interestingMethods.left;
 249                 if (mostInterestingResult == null) { // Couldn't find any interesting methods
 250                         return new Result(this, 0, Messages.getString(Messages.HotMethodsRuleFactory_TEXT_OK));
 251                 }
 252                 double mappedScore = performSigmoidMap(
 253                                 mostInterestingResult.ratioOfAllPossibleSamples.doubleValueIn(UnitLookup.PERCENT_UNITY));
 254 
 255                 Result result = null;
 256                 if (mappedScore < 25) {
 257                         result = new Result(this, mappedScore, Messages.getString(Messages.HotMethodsRuleFactory_TEXT_OK));
 258                 } else {
 259                         String shortDescription = MessageFormat.format(Messages.getString(Messages.HotMethodsRuleFactory_TEXT_INFO),
 260                                         FormatToolkit.getHumanReadable(mostInterestingResult.method, false, false, true, false, true,
 261                                                         false),
 262                                         mostInterestingResult.ratioOfAllPossibleSamples.displayUsing(IDisplayable.AUTO),
 263                                         windowSize.displayUsing(IDisplayable.AUTO));
 264                         String formattedPath = "<ul>" + //$NON-NLS-1$
 265                                         FormatToolkit.getHumanReadable(mostInterestingResult.path, false, false, true, true, true, false,
 266                                                         MAX_STACK_DEPTH, null, "<li>", //$NON-NLS-1$
 267                                                         "</li>" //$NON-NLS-1$
 268                                                         ) + "</ul>"; //$NON-NLS-1$
 269                         String longDescription = MessageFormat.format(
 270                                         Messages.getString(Messages.HotMethodsRuleFactory_TEXT_INFO_LONG),
 271                                         buildResultList(percentByMethod),
 272                                         formattedPath
 273                                         );
 274                         result = new Result(this, mappedScore, shortDescription, shortDescription + "<p>" + longDescription); //$NON-NLS-1$
 275                 }
 276                 return result;
 277         }
 278 
 279         private String buildResultList(Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod) {
 280                 StringBuilder longList = new StringBuilder();
 281                 longList.append("<ul>"); //$NON-NLS-1$
 282                 for (Entry<IMCStackTrace, MethodProfilingWindowResult> entry : percentByMethod.entrySet()) {
 283                         longList.append("<li>"); //$NON-NLS-1$
 284                         longList.append(entry.getValue());
 285                         longList.append("</li>"); //$NON-NLS-1$
 286                 }
 287                 longList.append("</ul>"); //$NON-NLS-1$
 288                 return longList.toString();
 289         }
 290 
 291         private Pair<MethodProfilingWindowResult, Map<IMCStackTrace, MethodProfilingWindowResult>> getInterestingMethods(
 292                 List<MethodProfilingWindowResult> windowResults) {
 293                 Map<IMCStackTrace, MethodProfilingWindowResult> percentByMethod = new HashMap<>();
 294                 IQuantity maxRawScore = UnitLookup.PERCENT_UNITY.quantity(0);
 295                 MethodProfilingWindowResult mostInterestingResult = null;
 296                 for (MethodProfilingWindowResult result : windowResults) {
 297                         if (result != null) {
 298                                 if (result.ratioOfAllPossibleSamples.compareTo(maxRawScore) > 0) {
 299                                         mostInterestingResult = result;
 300                                         maxRawScore = result.ratioOfAllPossibleSamples;
 301                                 }
 302                                 if (result.path != null && performSigmoidMap(
 303                                                 result.ratioOfAllPossibleSamples.doubleValueIn(UnitLookup.PERCENT_UNITY)) >= 25) {
 304                                         MethodProfilingWindowResult r = percentByMethod.get(result.path);
 305                                         if (r == null || result.ratioOfAllPossibleSamples.compareTo(r.ratioOfAllPossibleSamples) > 0) {
 306                                                 percentByMethod.put(result.path, result);
 307                                         }
 308                                 }
 309                         }
 310                 }
 311                 return new Pair<>(mostInterestingResult, percentByMethod);
 312         }
 313 
 314         private double performSigmoidMap(double input) {
 315                 return RulesToolkit.mapSigmoid(input, 0, 100, 150, 0.03333, 7);
 316         }
 317 
 318         /**
 319          * Creates an IUnorderedWindowVisitor that is called on each slice in the recording and
 320          * generates the scores for each slice and places them in the rawScores list. The given
 321          * parameters that are also given to the slidingWindowUnordered call must be the same as in this
 322          * call.
 323          *
 324          * @param settings
 325          *            the settings map with all the times the execution sample event has a change of
 326          *            periodicity
 327          * @param settingsFilter
 328          *            the filter used to select the recording setting for the execution sample event
 329          * @param windowSize
 330          *            the size of the sliding window
 331          * @param rawScores
 332          *            the list of raw scores that will be populated by this visitor
 333          * @return an IUnorderedWindowVisitor implementation that will populate the rawScores list with
 334          *         raw score values
 335          */
 336         private IUnorderedWindowVisitor createWindowVisitor(
 337                 final PeriodRangeMap settings, final IItemFilter settingsFilter, final IQuantity windowSize,
 338                 final List<MethodProfilingWindowResult> rawScores, final FutureTask<Result> evaluationTask, final Pattern excludes) {
 339                 return new IUnorderedWindowVisitor() {
 340                         @Override
 341                         public void visitWindow(IItemCollection items, IQuantity startTime, IQuantity endTime) {
 342                                 IRange<IQuantity> windowRange = QuantityRange.createWithEnd(startTime, endTime);
 343                                 if (RulesToolkit.getSettingMaxPeriod(items, JdkTypeIDs.EXECUTION_SAMPLE) == null) {
 344                                         Pair<IQuantity, IMCStackTrace> resultPair = performCalculation(items, settings.getSetting(startTime));
 345                                         if (resultPair != null) {
 346                                                 rawScores.add(new MethodProfilingWindowResult(resultPair.right.getFrames().get(0).getMethod(), resultPair.right, resultPair.left, windowRange));
 347                                         }
 348                                 } else {
 349                                         Set<IQuantity> settingTimes = items.apply(settingsFilter)
 350                                                         .getAggregate(Aggregators.distinct(JfrAttributes.START_TIME));
 351                                         IQuantity start = startTime;
 352                                         List<Pair<IQuantity, IMCStackTrace>> scores = new ArrayList<>(settingTimes.size());
 353                                         for (IQuantity settingTime : settingTimes) {
 354                                                 IItemFilter window = ItemFilters.interval(JfrAttributes.END_TIME, start, true, settingTime,
 355                                                                 true);
 356                                                 scores.add(performCalculation(items.apply(window), settings.getSetting(start)));
 357                                                 start = settingTime;
 358                                         }
 359                                         Map<IMCStackTrace, IQuantity> scoresByMethod = new HashMap<>();
 360                                         for (Pair<IQuantity, IMCStackTrace> score : scores) {
 361                                                 if (score != null) {
 362                                                         if (scoresByMethod.get(score.right) == null) {
 363                                                                 scoresByMethod.put(score.right, score.left);
 364                                                         } else {
 365                                                                 scoresByMethod.put(score.right, score.left.add(scoresByMethod.get(score.right)));
 366                                                         }
 367                                                 }
 368                                         }
 369                                         IQuantity sumScore = UnitLookup.PERCENT_UNITY.quantity(0);
 370                                         IMCStackTrace hottestPath = null;
 371                                         for (Entry<IMCStackTrace, IQuantity> entry : scoresByMethod.entrySet()) {
 372                                                 if (entry.getValue().compareTo(sumScore) > 0) {
 373                                                         hottestPath = entry.getKey();
 374                                                         sumScore = sumScore.add(entry.getValue());
 375                                                 }
 376                                         }
 377                                         IQuantity averageOfAllPossibleSamples = sumScore.multiply(1d / scores.size());
 378                                         IMCMethod hottestMethod = (hottestPath == null ? null : hottestPath.getFrames().get(0).getMethod());
 379                                         rawScores.add(new MethodProfilingWindowResult(hottestMethod, hottestPath, averageOfAllPossibleSamples, windowRange));
 380                                 }
 381                         }
 382 
 383                         @Override
 384                         public boolean shouldContinue() {
 385                                 return evaluationTask != null && !evaluationTask.isCancelled();
 386                         }
 387 
 388                         /**
 389                          * Performs the actual calculation of the score for the given period of the recording.
 390                          *
 391                          * @param items
 392                          *            the items to base the score on
 393                          * @param period
 394                          *            the periodicity to base the relevancy calculation on
 395                          * @return a double value in the interval [0,1] with 1 being a system in completely
 396                          *         saturated load with only one method called
 397                          */
 398                         private Pair<IQuantity, IMCStackTrace> performCalculation(IItemCollection items, IQuantity period) {
 399                                 IItemCollection filteredItems = items.apply(JdkFilters.EXECUTION_SAMPLE);
 400                                 final IMCMethod[] maxMethod = new IMCMethod[1];
 401                                 final IMCStackTrace[] maxPath = new IMCStackTrace[1];
 402                                 // Using this GroupingAggregator because it's the only way to extract the keys from the aggregation along with values
 403                                 IAggregator<IQuantity, ?> aggregator = GroupingAggregator.build("", "", //$NON-NLS-1$ //$NON_NLS_2$
 404                                                 MethodProfilingDataProvider.PATH_ACCESSOR_FACTORY, Aggregators.count(),
 405                                                 new GroupingAggregator.IGroupsFinisher<IQuantity, IMCStackTrace, CountConsumer>() {
 406 
 407                                                         @Override
 408                                                         public IType<IQuantity> getValueType() {
 409                                                                 return UnitLookup.NUMBER;
 410                                                         }
 411 
 412                                                         @Override
 413                                                         public IQuantity getValue(Iterable<? extends GroupEntry<IMCStackTrace, CountConsumer>> groupEntries) {
 414                                                                 HashMap<IMCMethod, IQuantity> map = new HashMap<>();
 415                                                                 HashMap<IMCMethod, IMCStackTrace> pathMap = new HashMap<>();
 416                                                                 int total = 0;
 417                                                                 // When we group by stack trace we can run into situations where the top frames are otherwise the same
 418                                                                 // for our purposes (finding the hottest method), but they differ by BCI, throwing off the count.
 419                                                                 // so we should collect further on the method for the top frame.
 420                                                                 for (GroupEntry<IMCStackTrace, CountConsumer> group : groupEntries) {
 421                                                                         IMCStackTrace trace = processPath(group.getKey());
 422                                                                         total += group.getConsumer().getCount();
 423                                                                         if (!trace.getFrames().isEmpty()) {
 424                                                                                 IMCMethod topFrameMethod = trace.getFrames().get(0).getMethod();
 425                                                                                 if (map.get(topFrameMethod) == null) {
 426                                                                                         map.put(topFrameMethod, UnitLookup.NUMBER_UNITY.quantity(group.getConsumer().getCount()));
 427                                                                                         pathMap.put(topFrameMethod, trace);
 428                                                                                 } else {
 429                                                                                         IQuantity old = map.get(topFrameMethod);
 430                                                                                         map.put(topFrameMethod, old.add(UnitLookup.NUMBER_UNITY.quantity(group.getConsumer().getCount())));
 431                                                                                 }
 432                                                                         }
 433                                                                 }
 434                                                                 if (!pathMap.isEmpty() && !map.isEmpty()) {
 435                                                                         Entry<IMCMethod, IQuantity> topEntry = Collections.max(map.entrySet(), new Comparator<Entry<IMCMethod, IQuantity>>() {
 436                                                                                 @Override
 437                                                                                 public int compare(Entry<IMCMethod, IQuantity> arg0,
 438                                                                                                 Entry<IMCMethod, IQuantity> arg1) {
 439                                                                                         return arg0.getValue().compareTo(arg1.getValue());
 440                                                                                 }
 441                                                                         });
 442                                                                         maxPath[0] = pathMap.get(topEntry.getKey());
 443                                                                         maxMethod[0] = topEntry.getKey();
 444                                                                         return topEntry.getValue().multiply(1d/total);
 445                                                                 }
 446                                                                 return UnitLookup.NUMBER_UNITY.quantity(0);
 447                                                         }
 448 
 449                                                         private IMCStackTrace processPath(IMCStackTrace path) {
 450                                                                 List<IMCFrame> frames = new ArrayList<>(path.getFrames());
 451                                                                 List<IMCFrame> framesToDrop = new ArrayList<IMCFrame>();
 452                                                                 // Drop any frames that match the excluded pattern, thereby treating the first non-matching frame that we encounter as the hot one.
 453                                                                 for (IMCFrame frame : frames) {
 454                                                                         Matcher m = excludes.matcher(FormatToolkit.getHumanReadable(frame.getMethod(), false, false, true, true, false, false));
 455                                                                         if (m.matches()) {
 456                                                                                 framesToDrop.add(frame);
 457                                                                         } else {
 458                                                                                 break;
 459                                                                         }
 460                                                                 }
 461                                                                 frames.removeAll(framesToDrop);
 462                                                                 return new MCStackTrace(frames, path.getTruncationState());
 463                                                         }
 464                                 });
 465 
 466                                 IQuantity maxRatio = filteredItems.getAggregate(aggregator);
 467                                 Pair<IQuantity, IMCStackTrace> result = null;
 468                                 if (maxMethod[0] != null && maxRatio != null && period != null) { // ignoring if there are no samples or if we don't yet know the periodicity
 469                                         double periodsPerSecond = 1 / period.doubleValueIn(UnitLookup.SECOND);
 470                                         double maxSamplesPerSecond = SAMPLES_PER_PERIOD * periodsPerSecond;
 471                                         double samplesInPeriod = items
 472                                                         .getAggregate(Aggregators.count(ItemFilters.type(JdkTypeIDs.EXECUTION_SAMPLE)))
 473                                                         .doubleValueIn(UnitLookup.NUMBER_UNITY);
 474                                         double maxSamplesInPeriod = maxSamplesPerSecond * windowSize.doubleValueIn(UnitLookup.SECOND);
 475                                         double relevancy = samplesInPeriod / maxSamplesInPeriod;
 476                                         double highestRatioOfSamples = maxRatio.doubleValueIn(UnitLookup.NUMBER_UNITY);
 477                                         IQuantity percentOfAllPossibleSamples = UnitLookup.PERCENT_UNITY
 478                                                         .quantity(highestRatioOfSamples * relevancy);
 479                                         result = new Pair<>(percentOfAllPossibleSamples, maxPath[0]);
 480                                 }
 481                                 return result;
 482                         }
 483                 };
 484         }
 485 
 486         /**
 487          * Populates the settings map with all the period settings for the execution sample event found
 488          * in this recording.
 489          *
 490          * @param items
 491          *            the items to search for execution sample period events
 492          * @param settings
 493          *            the map to populate with the events
 494          */
 495         private void populateSettingsMap(IItemCollection items, final PeriodRangeMap settings) {
 496                 Iterator<IItemIterable> itemIterableIterator = items.iterator();
 497                 while (itemIterableIterator.hasNext()) {
 498                         IItemIterable itemIterable = itemIterableIterator.next();
 499                         IMemberAccessor<IQuantity, IItem> startTimeAccessor = JfrAttributes.START_TIME
 500                                         .getAccessor(itemIterable.getType());
 501                         IMemberAccessor<String, IItem> settingValueAccessor = JdkAttributes.REC_SETTING_VALUE
 502                                         .getAccessor(itemIterable.getType());
 503 
 504                         Iterator<IItem> itemIterator = itemIterable.iterator();
 505                         while (itemIterator.hasNext()) {
 506                                 IItem item = itemIterator.next();
 507                                 settings.addSetting(startTimeAccessor.getMember(item),
 508                                                 getValueQuantity(settingValueAccessor.getMember(item)));
 509                         }
 510                 }
 511                 settings.sort();
 512         }
 513 
 514         /**
 515          * Used to parse the value of a Recording Setting Period attribute
 516          *
 517          * @param settingValue
 518          *            the value to parse
 519          * @return an IQuantity representation of the passed String object
 520          */
 521         private IQuantity getValueQuantity(String settingValue) {
 522                 try {
 523                         if (RulesToolkit.REC_SETTING_PERIOD_EVERY_CHUNK.equals(settingValue)) {
 524                                 return null;
 525                         }
 526                         return RulesToolkit.parsePersistedJvmTimespan(settingValue);
 527                 } catch (QuantityConversionException e) {
 528                         throw new RuntimeException(e);
 529                 }
 530         }
 531 
 532         @Override
 533         public Collection<TypedPreference<?>> getConfigurationAttributes() {
 534                 return CONFIG_ATTRIBUTES;
 535         }
 536 
 537         @Override
 538         public String getId() {
 539                 return RESULT_ID;
 540         }
 541 
 542         @Override
 543         public String getName() {
 544                 return Messages.getString(Messages.MethodProfilingRule_RULE_NAME);
 545         }
 546 
 547         @Override
 548         public String getTopic() {
 549                 return JfrRuleTopics.METHOD_PROFILING_TOPIC;
 550         }
 551 
 552 }