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.
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) {
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 {
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 }
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 }
|
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.
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) {
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 {
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 }
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 }
|