package org.gridkit.jvmtool; import java.io.FileInputStream; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gridkit.jvmtool.cli.CommandLauncher; import org.gridkit.jvmtool.codec.stacktrace.ThreadSnapshotEvent; import org.gridkit.jvmtool.codec.stacktrace.ThreadSnapshotEventPojo; import org.gridkit.jvmtool.event.ChainedEventReader; import org.gridkit.jvmtool.event.ErrorHandler; import org.gridkit.jvmtool.event.Event; import org.gridkit.jvmtool.event.EventMorpher; import org.gridkit.jvmtool.event.EventReader; import org.gridkit.jvmtool.event.ShieldedEventReader; import org.gridkit.jvmtool.event.SimpleErrorEvent; import org.gridkit.jvmtool.event.SingleEventReader; import org.gridkit.jvmtool.stacktrace.ThreadEventCodec; import org.gridkit.jvmtool.stacktrace.analytics.CachingFilterFactory; import org.gridkit.jvmtool.stacktrace.analytics.ParserException; import org.gridkit.jvmtool.stacktrace.analytics.PositionalStackMatcher; import org.gridkit.jvmtool.stacktrace.analytics.ThreadEventFilter; import org.gridkit.jvmtool.stacktrace.analytics.ThreadSnapshotFilter; import org.gridkit.jvmtool.stacktrace.analytics.TimeRangeChecker; import org.gridkit.jvmtool.stacktrace.analytics.TraceFilterPredicateParser; import com.beust.jcommander.Parameter; public class ThreadDumpSource { private CommandLauncher host; @Parameter(names={"-f", "--file"}, required = false, variableArity=true, description="Path to stack dump file") private List<String> files; @Parameter(names={"-tf", "--trace-filter"}, required = false, description="Apply filter to traces before processing. Use --ssa-help for more details about filter notation") private String traceFilter = null; @Parameter(names={"-tt", "--trace-trim"}, required = false, description="Positional filter trim frames to process. Use --ssa-help for more details about filter notation") private String traceTrim = null; @Parameter(names={"-tn", "--thread-name"}, required = false, description="Thread name filter (Java RegEx syntax)") private String threadName = null; @Parameter(names={"-tr", "--time-range"}, required = false, description="Time range filter") private String timeRange = null; private TimeZone timeZone; public ThreadDumpSource(CommandLauncher host) { this.host = host; } public void setTimeZone(TimeZone tz) { this.timeZone = tz; } public EventReader<ThreadSnapshotEvent> getFilteredReader() { if (traceFilter == null && traceTrim == null && threadName == null && timeRange == null) { return getUnclassifiedReader(); } else { EventReader<ThreadSnapshotEvent> reader = getUnclassifiedReader(); if (threadName != null) { reader = reader.morph(new ThreadNameFilter(threadName)); } if (timeRange != null) { String[] lh = timeRange.split("[-]"); if (lh.length != 2) { host.fail("Invalid time range '" + timeRange + "'", "Valid format yyyy.MM.dd_HH:mm:ss-yyyy.MM.dd_HH:mm:ss hours and higher parts can be ommited"); } TimeRangeChecker checker = new TimeRangeChecker(lh[0], lh[1], timeZone); reader = reader.morph(new TimeFilter(checker)); } try { CachingFilterFactory factory = new CachingFilterFactory(); if (traceFilter != null) { ThreadSnapshotFilter ts = TraceFilterPredicateParser.parseFilter(traceFilter, factory); reader = reader.morph(new ThreadEventFilter(ts)); } if (traceTrim != null) { final PositionalStackMatcher mt = TraceFilterPredicateParser.parsePositionMatcher(traceTrim, factory); reader = reader.morph(new TrimProxy() { @Override public ThreadSnapshotEvent morph(ThreadSnapshotEvent event) { int n = mt.matchNext(event, 0); if (n >= 0) { trimPoint = n; return super.morph(event); } else { return null; } } }); } return reader; } catch(ParserException e) { throw host.fail("Failed to parse trace filter - " + e.getMessage() + " at " + e.getOffset() + " [" + e.getParseText() + "]"); } } } public EventReader<ThreadSnapshotEvent> getUnclassifiedReader() { if (files == null) { host.fail("No input files provided, used -f option"); } final Iterator<String> it = files.iterator(); ChainedEventReader<Event> reader = new ChainedEventReader<Event>() { @Override protected EventReader<Event> produceNext() { return it.hasNext() ? open(it.next()) : null; } private EventReader<Event> open(String next) { try { return ThreadEventCodec.createEventReader(new FileInputStream(next)); } catch (IOException e) { return new SingleEventReader<Event>(new SimpleErrorEvent(e)); } } }; ShieldedEventReader<ThreadSnapshotEvent> shielderReader = new ShieldedEventReader<ThreadSnapshotEvent>(reader, ThreadSnapshotEvent.class, new ErrorHandler() { @Override public void onException(Exception e) { System.err.println("Stream reader error: " + e); } }); return shielderReader; } static class TimeFilter implements EventMorpher<ThreadSnapshotEvent, ThreadSnapshotEvent> { TimeRangeChecker checker; public TimeFilter(TimeRangeChecker checker) { this.checker = checker; } @Override public ThreadSnapshotEvent morph(ThreadSnapshotEvent event) { return checker.evaluate(event.timestamp()) ? event : null; } } static class ThreadNameFilter implements EventMorpher<ThreadSnapshotEvent, ThreadSnapshotEvent> { Matcher matcher; public ThreadNameFilter(String regex) { this.matcher = Pattern.compile(regex).matcher(""); } @Override public ThreadSnapshotEvent morph(ThreadSnapshotEvent event) { return evaluate(event) ? event : null; } protected boolean evaluate(ThreadSnapshotEvent event) { if (event.threadName() != null) { matcher.reset(event.threadName()); return matcher.matches(); } else { return false; } } } static class TrimProxy implements EventMorpher<ThreadSnapshotEvent, ThreadSnapshotEvent> { protected ThreadSnapshotEventPojo snap = new ThreadSnapshotEventPojo(); protected int trimPoint = 0; public TrimProxy() { } @Override public ThreadSnapshotEvent morph(ThreadSnapshotEvent event) { snap.loadFrom(event); snap.stackTrace(event.stackTrace().fragment(0, trimPoint)); return snap; } } }