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