/* * Copyright (C) 2010 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package lombok.ast.grammar; import java.util.Collection; import java.util.List; import java.util.TreeSet; import lombok.Data; import org.parboiled.BasicParseRunner; import org.parboiled.MatchHandler; import org.parboiled.MatcherContext; import org.parboiled.Rule; import com.google.common.collect.Lists; /** * Like the {@code BasicParseRunner} but will also track statistics on the parse run which you can retrieve by calling {@link #getOverviewReport()} * or {@link #getExtendedReport(int)} after a parse run. */ public class ProfilerParseRunner<V> extends BasicParseRunner<V> { private ReportEntry<V> rootReport; public ProfilerParseRunner(Rule rule, String input) { super(rule, input); } protected boolean runRootContext() { return runRootContext(new Handler()); } protected boolean runRootContext(MatchHandler<V> handler) { createRootContext(handler); return handler.matchRoot(rootContext); } protected void createRootContext(MatchHandler<V> matchHandler) { rootContext = new MatcherContext<V>(inputBuffer, parseErrors, matchHandler, rootMatcher); } /** * Returns a string describing, in order of 'expensiveness', the top-level failed rule chains in the parse run. */ public String getOverviewReport() { TreeSet<ReportEntry<V>> topLevelFailed = new TreeSet<ReportEntry<V>>(); fillReport(topLevelFailed, rootReport); StringBuilder out = new StringBuilder(); for (ReportEntry<V> entry : topLevelFailed) { if (entry.getSubSteps() < 100) break; out.append(formatReport(entry, false)); } return out.toString(); } /** * Lists the work done by the most expensive failed rules. * * First all failed rules are sorted according to how long they took, then, for each such rule, * a string is produced listing it and all its child rules. These are returned. * * @param topEntries Produce reports for the top {@code topEntries} most expensive failed rules. * a negative number means: All of them. */ public List<String> getExtendedReport(int topEntries) { TreeSet<ReportEntry<V>> topLevelFailed = new TreeSet<ReportEntry<V>>(); fillReport(topLevelFailed, rootReport); int count = topEntries; List<String> result = Lists.newArrayList(); StringBuilder out = new StringBuilder(); for (ReportEntry<V> entry : topLevelFailed) { if (count-- == 0) return result; out.setLength(0); fillExtendedReport(out, 0, entry); result.add(out.toString()); } return result; } private static int countInnerNodes(ReportEntry<?> entry) { int count = 1; for (ReportEntry<?> child : entry.getChildren()) count += countInnerNodes(child); return count; } private void fillExtendedReport(StringBuilder out, int spaces, ReportEntry<V> report) { for (int i = 0; i < spaces; i++) out.append(" "); out.append(formatReport(report, true)); for (ReportEntry<V> child : report.getChildren()) { fillExtendedReport(out, spaces + 1, child); } } private static String formatReport(ReportEntry<?> report, boolean withSuccess) { return String.format("%s[%07d] %s\n", withSuccess ? (report.isSucceeded() ? "!" : " ") : "", report.getSubSteps(), report.getPath()); } private void fillReport(Collection<ReportEntry<V>> failed, ReportEntry<V> report) { if (!report.isSucceeded()) { failed.add(report); } else { for (ReportEntry<V> child : report.getChildren()) { fillReport(failed, child); } } } @Data private static class ReportEntry<V> implements Comparable<ReportEntry<V>> { private final String path; private boolean succeeded; private final List<ReportEntry<V>> children = Lists.newArrayList(); private int subSteps = 0; @Override public int compareTo(ReportEntry<V> o) { if (o.getSubSteps() < getSubSteps()) return -1; else if (o.getSubSteps() > getSubSteps()) return +1; if (System.identityHashCode(o) < System.identityHashCode(this)) return -1; if (System.identityHashCode(o) > System.identityHashCode(this)) return +1; return 0; } } public final class Handler implements MatchHandler<V> { private final List<ReportEntry<V>> stack = Lists.newArrayList(); public boolean matchRoot(MatcherContext<V> rootContext) { return rootContext.runMatcher(); } public boolean match(MatcherContext<V> context) { String path = stack.isEmpty() ? "" : stack.get(stack.size() - 1).getPath(); path += String.format("/%s[%d]", context.getMatcher().getLabel(), context.getCurrentIndex()); ReportEntry<V> report = new ReportEntry<V>(path); stack.add(report); boolean result = context.getMatcher().match(context); report.setSucceeded(result); stack.remove(stack.size() -1); if (stack.isEmpty()) rootReport = report; else { ReportEntry<V> parent = stack.get(stack.size() - 1); parent.getChildren().add(report); parent.setSubSteps(parent.getSubSteps() + 1 + report.getSubSteps()); } return result; } } }