/** * Copyright 2014 Alexey Ragozin * * 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.gridkit.jvmtool.cmd; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.management.ThreadMXBean; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import javax.management.MBeanServerConnection; import org.gridkit.jvmtool.GlobHelper; import org.gridkit.jvmtool.JmxConnectionInfo; import org.gridkit.jvmtool.cli.CommandLauncher; import org.gridkit.jvmtool.cli.TimeIntervalConverter; import org.gridkit.jvmtool.cli.CommandLauncher.CmdRef; import org.gridkit.jvmtool.stacktrace.StackFrame; import org.gridkit.jvmtool.stacktrace.StackTraceCodec; import org.gridkit.jvmtool.stacktrace.StackTraceWriter; import org.gridkit.jvmtool.stacktrace.ThreadDumpSampler; import org.gridkit.jvmtool.stacktrace.ThreadMXBeanEx; import org.gridkit.jvmtool.stacktrace.ThreadSnapshot; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.beust.jcommander.ParametersDelegate; /** * Stack capture command. * * @author Alexey Ragozin (alexey.ragozin@gmail.com) */ public class StackCaptureCmd implements CmdRef { @Override public String getCommandName() { return "stcap"; } @Override public Runnable newCommand(CommandLauncher host) { return new StCap(host); } @Parameters(commandDescription = "[Stack Capture] Dumps stack traces to file for further processing") public static class StCap implements Runnable { @ParametersDelegate private CommandLauncher host; @Parameter(names = {"-i", "--sampler-interval"}, converter = TimeIntervalConverter.class, description = "Interval between polling MBeans") private long samplerIntervalMS = 0; @Parameter(names = {"-f", "--filter"}, description = "Filter threads by name (Java RegEx syntax)") private String threadFilter = ".*"; @Parameter(names = {"-e", "--empty"}, description = "Retain threads without stack trace in dump (ignored by default)") private boolean retainEmptyTraces = false; @Parameter(names = {"-m", "--match-frame"}, variableArity = true, description = "Frame filter, only traces conatining this string will be included to dump") private List<String> frameFilter; @Parameter(names = {"-o", "--output"}, required = true, description = "Name of file to write thread dump to") private String outputFile; @Parameter(names = {"-l", "--limit"}, description = "Target number of traces to collect, once reached command will terminate (0 - unlimited)") private long limit = 0; @Parameter(names = {"-t", "--timeout"}, converter = TimeIntervalConverter.class, description = "Time until command terminate even without enough traces collected") private long timeoutMS = TimeUnit.SECONDS.toMillis(30); @Parameter(names = {"-r", "--rotate"}, description = "If specified output file would be rotated every N traces (0 - do not rotate)") private long fileLimit = 0; @ParametersDelegate private JmxConnectionInfo connInfo; private ThreadDumpSampler sampler; private long traceCounter = 0; private long lastRotate = 0; private int rotSeg = 0; private StackTraceWriter writer; public StCap(CommandLauncher host) { this.host = host; this.connInfo = new JmxConnectionInfo(host); } @Override public void run() { try { MBeanServerConnection mserver = connInfo.getMServer(); ThreadMXBean bean = ThreadMXBeanEx.BeanHelper.connectThreadMXBean(mserver); sampler = new ThreadDumpSampler(); sampler.setThreadFilter(threadFilter); sampler.connect(bean); if (limit == 0) { limit = Long.MAX_VALUE; } StackTraceWriter proxy = new StackWriterProxy(); openWriter(); long deadline = System.currentTimeMillis() + timeoutMS; long nextReport = 500; while(System.currentTimeMillis() < deadline && traceCounter < limit) { long nextsample = System.currentTimeMillis() + samplerIntervalMS; sampler.collect(proxy); if (traceCounter >= nextReport) { System.out.println("Collected " +traceCounter); while(traceCounter >= nextReport) { nextReport += 500; } checkRotate(); } // delay while(nextsample > System.currentTimeMillis()) { long st = nextsample - System.currentTimeMillis(); if (st > 0) { Thread.sleep(st); } } } writer.close(); System.out.println("Trace dumped: " + traceCounter); } catch (Exception e) { host.fail("Unexpected error: " + e.toString(), e); } } private void checkRotate() throws FileNotFoundException, IOException { if (fileLimit > 0) { if (traceCounter - lastRotate > fileLimit) { writer.close(); ++rotSeg; lastRotate = traceCounter; openWriter(); } } } private class StackWriterProxy implements StackTraceWriter { private Map<StackFrame, Boolean> elementCache = new HashMap<StackFrame, Boolean>(); private Matcher[] matchers; public StackWriterProxy() { if (frameFilter != null) { matchers = new Matcher[frameFilter.size()]; for(int i = 0; i != frameFilter.size(); ++i) { matchers[i] = GlobHelper.translate(frameFilter.get(i), ".").matcher(""); } } } @Override public void write(ThreadSnapshot snap) throws IOException { if (snap.stackTrace().isEmpty() && !retainEmptyTraces) { return; } // test filter if (frameFilter != null) { boolean match = false; for(StackFrame e: snap.stackTrace()) { if (match(e)) { match = true; break; } } if (!match) { return; } } ++traceCounter; writer.write(snap); } private boolean match(StackFrame e) { Boolean cached = elementCache.get(e); if (cached == null) { if (elementCache.size() > 4 << 10) { elementCache.clear(); } boolean matched = false; for(Matcher m: matchers) { m.reset(e.toString()); if (m.lookingAt()) { matched = true; break; } } elementCache.put(e, matched); return matched; } return cached; } @Override public void close() { writer.close(); } } private void openWriter() throws FileNotFoundException, IOException { if (fileLimit < 1) { File file = new File(outputFile); if (file.getParentFile() != null) { file.getParentFile().mkdirs(); } writer = StackTraceCodec.newWriter(new FileOutputStream(file)); System.out.println("Writing to " + file.getAbsolutePath()); } else { int c = outputFile.lastIndexOf('.'); String pref = c < 0 ? outputFile : outputFile.substring(0, c); String suf = c < 0 ? "" : outputFile.substring(c); String name = pref + String.format("-%02d", rotSeg) + suf; File file = new File(name); if (file.getParentFile() != null) { file.getParentFile().mkdirs(); } writer = StackTraceCodec.newWriter(new FileOutputStream(file)); System.out.println("Writing to " + file.getAbsolutePath()); } } } }