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 }