001/* 002 * Copyright (C) 2009-2011 Mathias Doenitz 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.parboiled.parserunners; 018 019import org.parboiled.MatchHandler; 020import org.parboiled.MatcherContext; 021import org.parboiled.Rule; 022import org.parboiled.buffers.InputBuffer; 023import org.parboiled.common.Predicate; 024import org.parboiled.common.StringUtils; 025import org.parboiled.matchers.Matcher; 026import org.parboiled.matchervisitors.DoWithMatcherVisitor; 027import org.parboiled.support.ParsingResult; 028import org.parboiled.matchers.Matcher; 029import org.parboiled.support.ParsingResult; 030 031import java.text.DecimalFormat; 032import java.util.ArrayList; 033import java.util.Collections; 034import java.util.Comparator; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038 039import static org.parboiled.common.Preconditions.checkArgNotNull; 040import static org.parboiled.common.Utils.humanize; 041 042/** 043 * <p>The ProfilingParseRunner is a special {@link ParseRunner} implementation that "watches" a parser digest a number 044 * of inputs and collects all sorts of statistical data on the what rules have matched how many times, the number 045 * of reincovations of rules at identical input locations, and so on.</p> 046 * <p>The ProfilingParseRunner is typically used during parser debugging and optimization, not in production.</p> 047 * 048 * @param <V> 049 */ 050public class ProfilingParseRunner<V> extends AbstractParseRunner<V> implements MatchHandler { 051 private final Map<Rule, RuleReport> ruleReports = new HashMap<Rule, RuleReport>(); 052 private int runMatches; 053 private int totalRuns; 054 private int totalMatches; 055 private int totalMismatches; 056 private int totalRematches; 057 private int totalRemismatches; 058 private long totalNanoTime; 059 private long timeCorrection; 060 061 private final DoWithMatcherVisitor.Action updateStatsAction = new DoWithMatcherVisitor.Action() { 062 public void process(Matcher matcher) { 063 RuleStats ruleStats = (RuleStats) matcher.getTag(); 064 int rematches = 0, remismatches = 0; 065 for (Integer i : ruleStats.positionMatches.values()) { 066 if (i > 0) { 067 rematches += i - 1; 068 } else if (i < 0) { 069 remismatches += -(i + 1); 070 } 071 } 072 totalMatches += ruleStats.matches; 073 totalMismatches += ruleStats.mismatches; 074 totalRematches += rematches; 075 totalRemismatches += remismatches; 076 RuleReport ruleReport = ruleReports.get(matcher); 077 if (ruleReport == null) { 078 ruleReport = new RuleReport(matcher); 079 ruleReports.put(matcher, ruleReport); 080 } 081 ruleReport.update(ruleStats.matches, ruleStats.matchSubs, ruleStats.mismatches, ruleStats.mismatchSubs, 082 rematches, ruleStats.rematchSubs, remismatches, ruleStats.remismatchSubs, ruleStats.nanoTime); 083 } 084 }; 085 086 /** 087 * Creates a new ProfilingParseRunner instance for the given rule. 088 * 089 * @param rule the parser rule 090 */ 091 public ProfilingParseRunner(Rule rule) { 092 super(rule); 093 } 094 095 public ParsingResult<V> run(InputBuffer inputBuffer) { 096 checkArgNotNull(inputBuffer, "inputBuffer"); 097 resetValueStack(); 098 totalRuns++; 099 100 MatcherContext<V> rootContext = createRootContext(inputBuffer, this, true); 101 rootContext.getMatcher().accept(new DoWithMatcherVisitor(new DoWithMatcherVisitor.Action() { 102 public void process(Matcher matcher) { 103 RuleStats ruleStats = (RuleStats) matcher.getTag(); 104 if (ruleStats == null) { 105 ruleStats = new RuleStats(); 106 matcher.setTag(ruleStats); 107 } else { 108 ruleStats.clear(); 109 } 110 } 111 })); 112 113 runMatches = 0; 114 long timeStamp = System.nanoTime() - timeCorrection; 115 boolean matched = rootContext.runMatcher(); 116 totalNanoTime += System.nanoTime() - timeCorrection - timeStamp; 117 118 getRootMatcher().accept(new DoWithMatcherVisitor(updateStatsAction)); 119 return createParsingResult(matched, rootContext); 120 } 121 122 public Report getReport() { 123 return new Report(totalRuns, totalMatches, totalMismatches, totalRematches, totalRemismatches, 124 totalNanoTime, new ArrayList<RuleReport>(ruleReports.values())); 125 } 126 127 public boolean match(MatcherContext<?> context) { 128 long timeStamp = System.nanoTime(); 129 Matcher matcher = context.getMatcher(); 130 RuleStats ruleStats = ((RuleStats) matcher.getTag()); 131 int pos = context.getCurrentIndex(); 132 133 int subMatches = -++runMatches; 134 int matchSubs = ruleStats.matchSubs; 135 int rematchSubs = ruleStats.rematchSubs; 136 int mismatchSubs = ruleStats.mismatchSubs; 137 int remismatchSubs = ruleStats.remismatchSubs; 138 139 long time = System.nanoTime(); 140 timeCorrection += time - timeStamp; 141 timeStamp = time - timeCorrection; 142 143 boolean matched = matcher.match(context); 144 145 time = System.nanoTime(); 146 ruleStats.nanoTime += time - timeCorrection - timeStamp; 147 timeStamp = time; 148 149 subMatches += runMatches; 150 151 Integer posMatches = ruleStats.positionMatches.get(pos); 152 if (matched) { 153 ruleStats.matches++; 154 ruleStats.matchSubs = matchSubs + subMatches; 155 if (posMatches == null) { 156 posMatches = 1; 157 } else if (posMatches > 0) { 158 posMatches++; 159 ruleStats.rematchSubs = rematchSubs + subMatches; 160 } else if (posMatches < 0) { 161 posMatches = 0; 162 } 163 } else { 164 ruleStats.mismatches++; 165 ruleStats.mismatchSubs = mismatchSubs + subMatches; 166 if (posMatches == null) { 167 posMatches = -1; 168 } else if (posMatches < 0) { 169 posMatches--; 170 ruleStats.remismatchSubs = remismatchSubs + subMatches; 171 } else if (posMatches > 0) { 172 posMatches = 0; 173 } 174 } 175 ruleStats.positionMatches.put(pos, posMatches); 176 timeCorrection += System.nanoTime() - timeStamp; 177 return matched; 178 } 179 180 private static class RuleStats { 181 private int matches; 182 private int mismatches; 183 private int matchSubs; 184 private int mismatchSubs; 185 private int rematchSubs; 186 private int remismatchSubs; 187 private long nanoTime; 188 189 // map Index -> matches at that position 190 // no entry for a position means that the rule was never tried for that position 191 // an entry n > 0 means that the rule matched n times 192 // an entry n < 0 means that the rule failed n times 193 // an entry of 0 for a position means that the rule matched as well as failed at the position (should happen 194 // only for "strange" action rules) 195 private final Map<Integer, Integer> positionMatches = new HashMap<Integer, Integer>(); 196 197 private void clear() { 198 matches = 0; 199 mismatches = 0; 200 matchSubs = 0; 201 mismatchSubs = 0; 202 rematchSubs = 0; 203 remismatchSubs = 0; 204 nanoTime = 0; 205 positionMatches.clear(); 206 } 207 } 208 209 public static class Report { 210 private final static DecimalFormat fmt = new DecimalFormat("0.###"); 211 212 public static final Predicate<RuleReport> allRules = new Predicate<RuleReport>() { 213 public boolean apply(RuleReport rep) { 214 return true; 215 } 216 }; 217 218 public static final Predicate<RuleReport> namedRules = new Predicate<RuleReport>() { 219 public boolean apply(RuleReport rep) { 220 return rep.getMatcher().hasCustomLabel(); 221 } 222 }; 223 224 public final int totalRuns; 225 public final int totalInvocations; 226 public final int totalMatches; 227 public final int totalMismatches; 228 public final double matchShare; 229 public final int reinvocations; 230 public final int rematches; 231 public final int remismatches; 232 public final double reinvocationShare; 233 public final long totalNanoTime; 234 public final List<RuleReport> ruleReports; 235 236 public Report(int totalRuns, int totalMatches, int totalMismatches, int rematches, int remismatches, 237 long totalNanoTime, List<RuleReport> ruleReports) { 238 this.totalRuns = totalRuns; 239 this.totalInvocations = totalMatches + totalMismatches; 240 this.totalMatches = totalMatches; 241 this.totalMismatches = totalMismatches; 242 this.matchShare = ((double) totalMatches) / totalInvocations; 243 this.reinvocations = rematches + remismatches; 244 this.rematches = rematches; 245 this.remismatches = remismatches; 246 this.reinvocationShare = ((double) reinvocations) / totalInvocations; 247 this.totalNanoTime = totalNanoTime; 248 this.ruleReports = ruleReports; 249 } 250 251 public String print() { 252 StringBuilder sb = new StringBuilder(); 253 sb.append("Profiling Report\n"); 254 sb.append("----------------\n"); 255 sb.append(printBasics()); 256 sb.append("\n"); 257 sb.append("Top 20 named rules by invocations:\n"); 258 sb.append(sortByInvocations().printTopRules(20, namedRules)); 259 sb.append("\n"); 260 sb.append("Top 20 named rules by sub-invocations:\n"); 261 sb.append(sortBySubInvocations().printTopRules(20, namedRules)); 262 sb.append("\n"); 263 sb.append("Top 20 named rules by re-invocations:\n"); 264 sb.append(sortByReinvocations().printTopRules(20, namedRules)); 265 sb.append("\n"); 266 sb.append("Top 20 named rules by re-sub-invocations:\n"); 267 sb.append(sortByResubinvocations().printTopRules(20, namedRules)); 268 sb.append("\n"); 269 sb.append("Top 20 named rules by re-mismatches:\n"); 270 sb.append(sortByRemismatches().printTopRules(20, namedRules)); 271 sb.append("\n"); 272 sb.append("Top 20 named rules by re-sub-mismatches:\n"); 273 sb.append(sortByResubmismatches().printTopRules(20, namedRules)); 274 return sb.toString(); 275 } 276 277 public String printBasics() { 278 StringBuilder sb = new StringBuilder(); 279 sb.append(String.format("Runs : %,15d\n", totalRuns)); 280 sb.append(String.format("Active rules : %,15d\n", ruleReports.size())); 281 sb.append(String.format("Total net rule time : %,15.3f s\n", totalNanoTime / 1000000000.0)); 282 sb.append(String.format("Total rule invocations : %,15d\n", totalInvocations)); 283 sb.append(String.format("Total rule matches : %,15d\n", totalMatches)); 284 sb.append(String.format("Total rule mismatches : %,15d\n", totalMismatches)); 285 sb.append(String.format("Total match share : %15.2f %%\n", 100.0 * matchShare)); 286 sb.append(String.format("Rule re-invocations : %,15d\n", reinvocations)); 287 sb.append(String.format("Rule re-matches : %,15d\n", rematches)); 288 sb.append(String.format("Rule re-mismatches : %,15d\n", remismatches)); 289 sb.append(String.format("Rule re-invocation share : %15.2f %%\n", 100.0 * reinvocationShare)); 290 return sb.toString(); 291 } 292 293 public String printTopRules(int count, Predicate<RuleReport> filter) { 294 checkArgNotNull(filter, "filter"); 295 StringBuilder sb = new StringBuilder(); 296 sb.append( 297 "Rule | Net-Time | Invocations | Matches | Mismatches | Time/Invoc. | Match % | Re-Invocs | Re-Matches | Re-Mismatch | Re-Invoc % \n"); 298 sb.append( 299 "-------------------------------|-----------|-----------------|-----------------|-----------------|-----------------|---------|-----------------|-----------------|-----------------|-------------------\n"); 300 for (int i = 0; i < Math.min(ruleReports.size(), count); i++) { 301 RuleReport rep = ruleReports.get(i); 302 if (!filter.apply(rep)) { 303 count++; 304 continue; 305 } 306 sb.append(String.format( 307 "%-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", 308 StringUtils.left( 309 rep.getMatcher().toString() + ": " + rep.getMatcher().getClass().getSimpleName() 310 .replace("Matcher", ""), 30), 311 rep.getNanoTime() / 1000000.0, 312 humanize(rep.getInvocations()), humanize(rep.getInvocationSubs()), 313 humanize(rep.getMatches()), humanize(rep.getMatchSubs()), 314 humanize(rep.getMismatches()), humanize(rep.getMismatchSubs()), 315 rep.getNanoTime() / (double) rep.getInvocations(), 316 rep.getMatchShare() * 100, 317 humanize(rep.getReinvocations()), humanize(rep.getReinvocationSubs()), 318 humanize(rep.getRematches()), humanize(rep.getRematchSubs()), 319 humanize(rep.getRemismatches()), humanize(rep.getRemismatchSubs()), 320 rep.getReinvocationShare() * 100, rep.getReinvocationShare2() * 100 321 )); 322 } 323 return sb.toString(); 324 } 325 326 public Report sortByInvocations() { 327 Collections.sort(ruleReports, new Comparator<RuleReport>() { 328 public int compare(RuleReport a, RuleReport b) { 329 return intCompare(a.getInvocations(), b.getInvocations()); 330 } 331 }); 332 return this; 333 } 334 335 public Report sortBySubInvocations() { 336 Collections.sort(ruleReports, new Comparator<RuleReport>() { 337 public int compare(RuleReport a, RuleReport b) { 338 return intCompare(a.getInvocationSubs(), b.getInvocationSubs()); 339 } 340 }); 341 return this; 342 } 343 344 public Report sortByTime() { 345 Collections.sort(ruleReports, new Comparator<RuleReport>() { 346 public int compare(RuleReport a, RuleReport b) { 347 return longCompare(a.getNanoTime(), b.getNanoTime()); 348 } 349 }); 350 return this; 351 } 352 353 public Report sortByTimePerInvocation() { 354 Collections.sort(ruleReports, new Comparator<RuleReport>() { 355 public int compare(RuleReport a, RuleReport b) { 356 return doubleCompare(a.getNanoTime() / (double) a.getInvocations(), 357 b.getNanoTime() / (double) b.getInvocations()); 358 } 359 }); 360 return this; 361 } 362 363 public Report sortByMatches() { 364 Collections.sort(ruleReports, new Comparator<RuleReport>() { 365 public int compare(RuleReport a, RuleReport b) { 366 return intCompare(a.getMatches(), b.getMatches()); 367 } 368 }); 369 return this; 370 } 371 372 public Report sortByMismatches() { 373 Collections.sort(ruleReports, new Comparator<RuleReport>() { 374 public int compare(RuleReport a, RuleReport b) { 375 return intCompare(a.getMismatches(), b.getMismatches()); 376 } 377 }); 378 return this; 379 } 380 381 public Report sortByReinvocations() { 382 Collections.sort(ruleReports, new Comparator<RuleReport>() { 383 public int compare(RuleReport a, RuleReport b) { 384 return intCompare(a.getReinvocations(), b.getReinvocations()); 385 } 386 }); 387 return this; 388 } 389 390 public Report sortByResubinvocations() { 391 Collections.sort(ruleReports, new Comparator<RuleReport>() { 392 public int compare(RuleReport a, RuleReport b) { 393 return doubleCompare(a.getReinvocationSubs(), b.getReinvocationSubs()); 394 } 395 }); 396 return this; 397 } 398 399 public Report sortByRematches() { 400 Collections.sort(ruleReports, new Comparator<RuleReport>() { 401 public int compare(RuleReport a, RuleReport b) { 402 return intCompare(a.getRematches(), b.getRematches()); 403 } 404 }); 405 return this; 406 } 407 408 public Report sortByRemismatches() { 409 Collections.sort(ruleReports, new Comparator<RuleReport>() { 410 public int compare(RuleReport a, RuleReport b) { 411 return intCompare(a.getRemismatches(), b.getRemismatches()); 412 } 413 }); 414 return this; 415 } 416 417 public Report sortByResubmismatches() { 418 Collections.sort(ruleReports, new Comparator<RuleReport>() { 419 public int compare(RuleReport a, RuleReport b) { 420 return doubleCompare(a.getRemismatchSubs(), b.getRemismatchSubs()); 421 } 422 }); 423 return this; 424 } 425 426 private int intCompare(int a, int b) { 427 return a < b ? 1 : a > b ? -1 : 0; 428 } 429 430 private int longCompare(long a, long b) { 431 return a < b ? 1 : a > b ? -1 : 0; 432 } 433 434 private int doubleCompare(double a, double b) { 435 return a < b ? 1 : a > b ? -1 : 0; 436 } 437 } 438 439 public static class RuleReport { 440 private final Matcher matcher; 441 private int matches; 442 private int matchSubs; 443 private int mismatches; 444 private int mismatchSubs; 445 private int rematches; 446 private int rematchSubs; 447 private int remismatches; 448 private int remismatchSubs; 449 private long nanoTime; 450 451 public RuleReport(Matcher matcher) { 452 this.matcher = matcher; 453 } 454 455 public Matcher getMatcher() { return matcher; } 456 457 public int getInvocations() { return matches + mismatches; } 458 459 public int getInvocationSubs() { return matchSubs + mismatchSubs; } 460 461 public int getMatches() { return matches; } 462 463 public int getMatchSubs() { return matchSubs; } 464 465 public int getMismatches() { return mismatches; } 466 467 public int getMismatchSubs() { return mismatchSubs; } 468 469 public double getMatchShare() { return ((double) matches) / getInvocations(); } 470 471 public double getMatchShare2() { return ((double) matchSubs) / getInvocationSubs(); } 472 473 public int getReinvocations() { return rematches + remismatches; } 474 475 public int getReinvocationSubs() { return rematchSubs + remismatchSubs; } 476 477 public int getRematches() { return rematches; } 478 479 public int getRematchSubs() { return rematchSubs; } 480 481 public int getRemismatches() { return remismatches; } 482 483 public int getRemismatchSubs() { return remismatchSubs; } 484 485 public double getReinvocationShare() { return ((double) getReinvocations()) / getInvocations(); } 486 487 public double getReinvocationShare2() { return ((double) getReinvocationSubs()) / getInvocationSubs(); } 488 489 public long getNanoTime() { return nanoTime; } 490 491 public void update(int matchesDelta, int matchSubsDelta, 492 int mismatchesDelta, int mismatchSubsDelta, 493 int rematchesDelta, int rematchSubsDelta, 494 int remismatchesDelta, int remismatchSubsDelta, 495 long nanoTimeDelta) { 496 matches += matchesDelta; 497 matchSubs += matchSubsDelta; 498 mismatches += mismatchesDelta; 499 mismatchSubs += mismatchSubsDelta; 500 rematches += rematchesDelta; 501 rematchSubs += rematchSubsDelta; 502 remismatches += remismatchesDelta; 503 remismatchSubs += remismatchSubsDelta; 504 nanoTime += nanoTimeDelta; 505 } 506 } 507}