ml.shifu.guagua.util.JMap Maven / Gradle / Ivy
The newest version!
/*
* Copyright [2013-2014] PayPal Software Foundation
*
* 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 ml.shifu.guagua.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import ml.shifu.guagua.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper to run jmap and print the output. Copy from Apache Giraph.
*/
public class JMap {
private static final Logger LOG = LoggerFactory.getLogger(JMap.class);
/** The command to run */
public static final String CMD = "jmap";
/** Arguments to pass in to command */
public static final String ARGS = "-histo";
private static int staticProcessId = -1;
private static final String USER_DIR = "user.dir";
/** Do not construct */
protected JMap() {
}
/**
* Get the process ID of the current running process
*
* @return Integer process ID
*/
public synchronized static int getProcessId() {
if(staticProcessId == -1) {
String processId = ManagementFactory.getRuntimeMXBean().getName();
if(processId.contains("@")) {
processId = processId.substring(0, processId.indexOf("@"));
}
staticProcessId = Integer.parseInt(processId);
}
return staticProcessId;
}
/**
* Run jmap, print numLines of output from it to stderr.
*
* @param numLines
* Number of lines to print
*/
public static void heapHistogramDump(int numLines) {
heapHistogramDump(numLines, System.err);
}
/**
* Run jmap, print numLines of output from it to stream passed in.
*
* @param numLines
* Number of lines to print
* @param printStream
* Stream to print to
*/
public static void heapHistogramDump(int numLines, PrintStream printStream) {
BufferedReader in = null;
try {
String JAVA_HOME = System.getProperty("java.home");
if(JAVA_HOME == null) {
throw new IllegalArgumentException("java.home is not set!");
}
List commandList = new ArrayList();
commandList.add(JAVA_HOME + File.separator + ".." + File.separator + "bin" + File.separator + CMD);
commandList.add(ARGS);
commandList.add(getProcessId() + "");
String workingDir = System.getProperty(USER_DIR, ".");
ProcessBuilder pb = new ProcessBuilder();
File execDir = new File(workingDir);
pb.command(commandList);
pb.directory(execDir);
pb.redirectErrorStream(true);
Process jmapProcess = null;
StreamCollector jmapStreamCollector;
synchronized(StreamCollector.class) {
jmapProcess = pb.start();
jmapStreamCollector = new StreamCollector(jmapProcess.getInputStream(), numLines);
jmapStreamCollector.start();
}
Runtime.getRuntime()
.addShutdownHook(new Thread(new JMapShutdownHook(jmapProcess, jmapStreamCollector, workingDir)));
} catch (IOException e) {
LOG.error("IOException in dump heap", e);
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
LOG.error("Error in closing input stream", e);
}
}
}
}
private static class JMapShutdownHook implements Runnable {
private Process process;
private StreamCollector collector;
private String exeDir;
public JMapShutdownHook(Process process, StreamCollector collector, String exeDir) {
this.process = process;
this.collector = collector;
this.exeDir = exeDir;
}
@Override
public void run() {
try {
LOG.info("start run shutdown hook");
synchronized(this) {
if(process != null) {
LOG.warn("foeced a shutdown hook to kill process");
process.destroy();
int returnCode = -1;
try {
returnCode = process.waitFor();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
LOG.info("Current process exited with {} (note that 143 typically means killed).", returnCode);
}
}
} finally {
if(this.collector != null) {
this.collector.close();
}
}
FileUtils.deleteQuietly(new File(exeDir));
}
}
private static class StreamCollector extends Thread {
/** Number of last lines to keep */
private static final int LAST_LINES_COUNT = 100;
/** Class logger */
private static final Logger LOG = LoggerFactory.getLogger(StreamCollector.class);
/** Buffered reader of input stream */
private final BufferedReader bufferedReader;
/** Last lines (help to debug failures) */
private final LinkedList lastLines = new LinkedList();
private final int maxLines;
/**
* Constructor.
*
* @param is
* InputStream to dump to LOG.info
*/
public StreamCollector(final InputStream is, int maxLines) {
super(StreamCollector.class.getName());
this.maxLines = maxLines;
setDaemon(true);
InputStreamReader streamReader = new InputStreamReader(is, Charset.defaultCharset());
bufferedReader = new BufferedReader(streamReader);
}
@Override
public void run() {
readLines();
}
/**
* Read all the lines from the bufferedReader.
*/
private synchronized void readLines() {
String line;
try {
int lineIndex = 0;
while((line = bufferedReader.readLine()) != null && lineIndex < this.maxLines) {
if(lastLines.size() > LAST_LINES_COUNT) {
lastLines.removeFirst();
}
lastLines.add(line);
lineIndex += 1;
LOG.info("readLines: {}.", line);
}
} catch (IOException e) {
LOG.error("readLines: Ignoring IOException", e);
}
}
/**
* Dump the last n lines of the collector. Likely used in the case of failure.
*
* @param level
* Log level to dump with
*/
@SuppressWarnings("unused")
public synchronized void dumpLastLines() {
// Get any remaining lines
readLines();
// Dump the lines to the screen
for(String line: lastLines) {
LOG.info(line);
}
}
public void close() {
try {
this.bufferedReader.close();
} catch (IOException ignore) {
}
}
}
}