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