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 }