Package org.parboiled.parserunners

Source Code of org.parboiled.parserunners.ProfilingParseRunner$Report

/*
* Copyright (C) 2009-2010 Mathias Doenitz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.parboiled.parserunners;

import org.jetbrains.annotations.NotNull;
import org.parboiled.MatchHandler;
import org.parboiled.MatcherContext;
import org.parboiled.Rule;
import org.parboiled.common.Predicate;
import org.parboiled.common.StringUtils;
import org.parboiled.matchers.Matcher;
import org.parboiled.matchervisitors.DoWithMatcherVisitor;
import org.parboiled.matchervisitors.HasCustomLabelVisitor;
import org.parboiled.support.ValueStack;

import java.text.DecimalFormat;
import java.util.*;

import static org.parboiled.common.Utils.humanize;

/**
* <p>The ProfilingParseRunner is a special {@link ParseRunner} implementation that "watches" a parser digest a number
* of inputs and collects all sorts of statistical data on the what rules have matched how many times, the number
* of reincovations of rules at identical input locations, and so on.</p>
* <p>The ProfilingParseRunner is typically used during parser debugging and optimization, not in production.</p>
*
* @param <V>
*/
public class ProfilingParseRunner<V> extends BasicParseRunner<V> {

    private final Map<Rule, RuleReport> ruleReports = new HashMap<Rule, RuleReport>();
    private int totalRuns;
    private int totalMatches;
    private int totalMismatches;
    private int totalRematches;
    private int totalRemismatches;
    private long totalNanoTime;

    private final DoWithMatcherVisitor.Action updateStatsAction = new DoWithMatcherVisitor.Action() {
        public void process(Matcher matcher) {
            RuleStats ruleStats = (RuleStats) matcher.getTag();
            int rematches = 0, remismatches = 0;
            for (Integer i : ruleStats.positionMatches.values()) {
                if (i > 0) {
                    rematches += i - 1;
                } else if (i < 0) {
                    remismatches += -(i + 1);
                }
            }
            totalMatches += ruleStats.matches;
            totalMismatches += ruleStats.mismatches;
            totalRematches += rematches;
            totalRemismatches += remismatches;
            RuleReport ruleReport = ruleReports.get(matcher);
            if (ruleReport == null) {
                ruleReport = new RuleReport(matcher);
                ruleReports.put(matcher, ruleReport);
            }
            ruleReport.update(ruleStats.matches, ruleStats.matchSubs, ruleStats.mismatches, ruleStats.mismatchSubs,
                    rematches, ruleStats.rematchSubs, remismatches, ruleStats.remismatchSubs, ruleStats.nanoTime);
        }
    };

    /**
     * Creates a new ProfilingParseRunner instance for the given rule.
     *
     * @param rule the parser rule
     */
    public ProfilingParseRunner(@NotNull Rule rule) {
        super(rule);
    }

    /**
     * Creates a new ProfilingParseRunner instance for the given rule using the given ValueStack instance.
     *
     * @param rule       the parser rule
     * @param valueStack the value stack
     */
    public ProfilingParseRunner(@NotNull Rule rule, @NotNull ValueStack<V> valueStack) {
        super(rule, valueStack);
    }

    @Override
    protected boolean runRootContext() {
        totalRuns++;
        return runRootContext(new Handler(), true);
    }

    public Report getReport() {
        return new Report(totalRuns, totalMatches, totalMismatches, totalRematches, totalRemismatches,
                totalNanoTime, new ArrayList<RuleReport>(ruleReports.values()));
    }

    public class Handler implements MatchHandler {
        private long timeCorrection;
        private int totalMatches;

        public boolean matchRoot(MatcherContext<?> rootContext) {
            rootContext.getMatcher().accept(new DoWithMatcherVisitor(new DoWithMatcherVisitor.Action() {
                public void process(Matcher matcher) {
                    RuleStats ruleStats = (RuleStats) matcher.getTag();
                    if (ruleStats == null) {
                        ruleStats = new RuleStats();
                        matcher.setTag(ruleStats);
                    } else {
                        ruleStats.clear();
                    }
                }
            }));

            totalMatches = 0;
            long timeStamp = System.nanoTime() - timeCorrection;
            boolean matched = rootContext.runMatcher();
            totalNanoTime += System.nanoTime() - timeCorrection - timeStamp;

            rootMatcher.accept(new DoWithMatcherVisitor(updateStatsAction));
            return matched;
        }

        public boolean match(MatcherContext<?> context) {
            long timeStamp = System.nanoTime();
            Matcher matcher = context.getMatcher();
            RuleStats ruleStats = ((RuleStats) matcher.getTag());
            int pos = context.getCurrentIndex();

            int subMatches = -++totalMatches;
            int matchSubs = ruleStats.matchSubs;
            int rematchSubs = ruleStats.rematchSubs;
            int mismatchSubs = ruleStats.mismatchSubs;
            int remismatchSubs = ruleStats.remismatchSubs;

            long time = System.nanoTime();
            timeCorrection += time - timeStamp;
            timeStamp = time - timeCorrection;

            boolean matched = matcher.match(context);

            time = System.nanoTime();
            ruleStats.nanoTime += time - timeCorrection - timeStamp;
            timeStamp = time;

            subMatches += totalMatches;

            Integer posMatches = ruleStats.positionMatches.get(pos);
            if (matched) {
                ruleStats.matches++;
                ruleStats.matchSubs = matchSubs + subMatches;
                if (posMatches == null) {
                    posMatches = 1;
                } else if (posMatches > 0) {
                    posMatches++;
                    ruleStats.rematchSubs = rematchSubs + subMatches;
                } else if (posMatches < 0) {
                    posMatches = 0;
                }
            } else {
                ruleStats.mismatches++;
                ruleStats.mismatchSubs = mismatchSubs + subMatches;
                if (posMatches == null) {
                    posMatches = -1;
                } else if (posMatches < 0) {
                    posMatches--;
                    ruleStats.remismatchSubs = remismatchSubs + subMatches;
                } else if (posMatches > 0) {
                    posMatches = 0;
                }
            }
            ruleStats.positionMatches.put(pos, posMatches);
            timeCorrection += System.nanoTime() - timeStamp;
            return matched;
        }
    }

    private static class RuleStats {
        private int matches;
        private int mismatches;
        private int matchSubs;
        private int mismatchSubs;
        private int rematchSubs;
        private int remismatchSubs;
        private long nanoTime;

        // map Index -> matches at that position
        // no entry for a position means that the rule was never tried for that position
        // an entry n > 0 means that the rule matched n times
        // an entry n < 0 means that the rule failed n times
        // an entry of 0 for a position means that the rule matched as well as failed at the position (should happen
        // only for "strange" action rules)
        private final Map<Integer, Integer> positionMatches = new HashMap<Integer, Integer>();

        private void clear() {
            matches = 0;
            mismatches = 0;
            matchSubs = 0;
            mismatchSubs = 0;
            rematchSubs = 0;
            remismatchSubs = 0;
            nanoTime = 0;
            positionMatches.clear();
        }
    }

    public static class Report {
        private final static DecimalFormat fmt = new DecimalFormat("0.###");

        public static final Predicate<RuleReport> allRules = new Predicate<RuleReport>() {
            public boolean apply(RuleReport rep) {
                return true;
            }
        };

        public static final Predicate<RuleReport> namedRules = new Predicate<RuleReport>() {
            public boolean apply(RuleReport rep) {
                return rep.getMatcher().accept(new HasCustomLabelVisitor());
            }
        };

        public final int totalRuns;
        public final int totalInvocations;
        public final int totalMatches;
        public final int totalMismatches;
        public final double matchShare;
        public final int reinvocations;
        public final int rematches;
        public final int remismatches;
        public final double reinvocationShare;
        public final long totalNanoTime;
        public final List<RuleReport> ruleReports;

        public Report(int totalRuns, int totalMatches, int totalMismatches, int rematches, int remismatches,
                      long totalNanoTime, List<RuleReport> ruleReports) {
            this.totalRuns = totalRuns;
            this.totalInvocations = totalMatches + totalMismatches;
            this.totalMatches = totalMatches;
            this.totalMismatches = totalMismatches;
            this.matchShare = ((double) totalMatches) / totalInvocations;
            this.reinvocations = rematches + remismatches;
            this.rematches = rematches;
            this.remismatches = remismatches;
            this.reinvocationShare = ((double) reinvocations) / totalInvocations;
            this.totalNanoTime = totalNanoTime;
            this.ruleReports = ruleReports;
        }

        public String print() {
            StringBuilder sb = new StringBuilder();
            sb.append("Profiling Report\n");
            sb.append("----------------\n");
            sb.append(printBasics());
            sb.append("\n");
            sb.append("Top 20 named rules by invocations:\n");
            sb.append(sortByInvocations().printTopRules(20, namedRules));
            sb.append("\n");
            sb.append("Top 20 named rules by sub-invocations:\n");
            sb.append(sortBySubInvocations().printTopRules(20, namedRules));
            sb.append("\n");
            sb.append("Top 20 named rules by re-invocations:\n");
            sb.append(sortByReinvocations().printTopRules(20, namedRules));
            sb.append("\n");
            sb.append("Top 20 named rules by re-sub-invocations:\n");
            sb.append(sortByResubinvocations().printTopRules(20, namedRules));
            sb.append("\n");
            sb.append("Top 20 named rules by re-mismatches:\n");
            sb.append(sortByRemismatches().printTopRules(20, namedRules));
            sb.append("\n");
            sb.append("Top 20 named rules by re-sub-mismatches:\n");
            sb.append(sortByResubmismatches().printTopRules(20, namedRules));
            return sb.toString();
        }

        public String printBasics() {
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("Runs                     : %,15d\n", totalRuns));
            sb.append(String.format("Active rules             : %,15d\n", ruleReports.size()));
            sb.append(String.format("Total net rule time      : %,15.3f s\n", totalNanoTime / 1000000000.0));
            sb.append(String.format("Total rule invocations   : %,15d\n", totalInvocations));
            sb.append(String.format("Total rule matches       : %,15d\n", totalMatches));
            sb.append(String.format("Total rule mismatches    : %,15d\n", totalMismatches));
            sb.append(String.format("Total match share        : %15.2f %%\n", 100.0 * matchShare));
            sb.append(String.format("Rule re-invocations      : %,15d\n", reinvocations));
            sb.append(String.format("Rule re-matches          : %,15d\n", rematches));
            sb.append(String.format("Rule re-mismatches       : %,15d\n", remismatches));
            sb.append(String.format("Rule re-invocation share : %15.2f %%\n", 100.0 * reinvocationShare));
            return sb.toString();
        }

        public String printTopRules(int count, @NotNull Predicate<RuleReport> filter) {
            StringBuilder sb = new StringBuilder();
            sb.append(
                    "Rule                           | Net-Time  |   Invocations   |     Matches     |   Mismatches    |   Time/Invoc.   | Match % |    Re-Invocs    |   Re-Matches    |   Re-Mismatch   |     Re-Invoc %    \n");
            sb.append(
                    "-------------------------------|-----------|-----------------|-----------------|-----------------|-----------------|---------|-----------------|-----------------|-----------------|-------------------\n");
            for (int i = 0; i < Math.min(ruleReports.size(), count); i++) {
                RuleReport rep = ruleReports.get(i);
                if (!filter.apply(rep)) {
                    count++;
                    continue;
                }
                sb.append(String.format(
                        "%-30s | %6.0f ms | %6s / %6s | %6s / %6s | %6s / %6s | %,12.0f ns | %6.2f%% | %6s / %6s | %6s / %6s | %6s / %6s | %6.2f%% / %6.2f%%\n",
                        StringUtils.left(
                                rep.getMatcher().toString() + ": " + rep.getMatcher().getClass().getSimpleName()
                                        .replace("Matcher", ""), 30),
                        rep.getNanoTime() / 1000000.0,
                        humanize(rep.getInvocations()), humanize(rep.getInvocationSubs()),
                        humanize(rep.getMatches()), humanize(rep.getMatchSubs()),
                        humanize(rep.getMismatches()), humanize(rep.getMismatchSubs()),
                        rep.getNanoTime() / (double) rep.getInvocations(),
                        rep.getMatchShare() * 100,
                        humanize(rep.getReinvocations()), humanize(rep.getReinvocationSubs()),
                        humanize(rep.getRematches()), humanize(rep.getRematchSubs()),
                        humanize(rep.getRemismatches()), humanize(rep.getRemismatchSubs()),
                        rep.getReinvocationShare() * 100, rep.getReinvocationShare2() * 100
                ));
            }
            return sb.toString();
        }

        public Report sortByInvocations() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getInvocations(), b.getInvocations());
                }
            });
            return this;
        }

        public Report sortBySubInvocations() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getInvocationSubs(), b.getInvocationSubs());
                }
            });
            return this;
        }

        public Report sortByTime() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return longCompare(a.getNanoTime(), b.getNanoTime());
                }
            });
            return this;
        }

        public Report sortByTimePerInvocation() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return doubleCompare(a.getNanoTime() / (double) a.getInvocations(),
                            b.getNanoTime() / (double) b.getInvocations());
                }
            });
            return this;
        }

        public Report sortByMatches() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getMatches(), b.getMatches());
                }
            });
            return this;
        }

        public Report sortByMismatches() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getMismatches(), b.getMismatches());
                }
            });
            return this;
        }

        public Report sortByReinvocations() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getReinvocations(), b.getReinvocations());
                }
            });
            return this;
        }

        public Report sortByResubinvocations() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return doubleCompare(a.getReinvocationSubs(), b.getReinvocationSubs());
                }
            });
            return this;
        }

        public Report sortByRematches() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getRematches(), b.getRematches());
                }
            });
            return this;
        }

        public Report sortByRemismatches() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return intCompare(a.getRemismatches(), b.getRemismatches());
                }
            });
            return this;
        }

        public Report sortByResubmismatches() {
            Collections.sort(ruleReports, new Comparator<RuleReport>() {
                public int compare(RuleReport a, RuleReport b) {
                    return doubleCompare(a.getRemismatchSubs(), b.getRemismatchSubs());
                }
            });
            return this;
        }

        private int intCompare(int a, int b) {
            return a < b ? 1 : a > b ? -1 : 0;
        }

        private int longCompare(long a, long b) {
            return a < b ? 1 : a > b ? -1 : 0;
        }

        private int doubleCompare(double a, double b) {
            return a < b ? 1 : a > b ? -1 : 0;
        }
    }

    public static class RuleReport {
        private final Matcher matcher;
        private int matches;
        private int matchSubs;
        private int mismatches;
        private int mismatchSubs;
        private int rematches;
        private int rematchSubs;
        private int remismatches;
        private int remismatchSubs;
        private long nanoTime;

        public RuleReport(Matcher matcher) {
            this.matcher = matcher;
        }

        public Matcher getMatcher() { return matcher; }

        public int getInvocations() { return matches + mismatches; }

        public int getInvocationSubs() { return matchSubs + mismatchSubs; }

        public int getMatches() { return matches; }

        public int getMatchSubs() { return matchSubs; }

        public int getMismatches() { return mismatches; }

        public int getMismatchSubs() { return mismatchSubs; }

        public double getMatchShare() { return ((double) matches) / getInvocations(); }

        public double getMatchShare2() { return ((double) matchSubs) / getInvocationSubs(); }

        public int getReinvocations() { return rematches + remismatches; }

        public int getReinvocationSubs() { return rematchSubs + remismatchSubs; }

        public int getRematches() { return rematches; }

        public int getRematchSubs() { return rematchSubs; }

        public int getRemismatches() { return remismatches; }

        public int getRemismatchSubs() { return remismatchSubs; }

        public double getReinvocationShare() { return ((double) getReinvocations()) / getInvocations(); }

        public double getReinvocationShare2() { return ((double) getReinvocationSubs()) / getInvocationSubs(); }

        public long getNanoTime() { return nanoTime; }

        public void update(int matchesDelta, int matchSubsDelta,
                           int mismatchesDelta, int mismatchSubsDelta,
                           int rematchesDelta, int rematchSubsDelta,
                           int remismatchesDelta, int remismatchSubsDelta,
                           long nanoTimeDelta) {
            matches += matchesDelta;
            matchSubs += matchSubsDelta;
            mismatches += mismatchesDelta;
            mismatchSubs += mismatchSubsDelta;
            rematches += rematchesDelta;
            rematchSubs += rematchSubsDelta;
            remismatches += remismatchesDelta;
            remismatchSubs += remismatchSubsDelta;
            nanoTime += nanoTimeDelta;
        }
    }
}
TOP

Related Classes of org.parboiled.parserunners.ProfilingParseRunner$Report

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.