package jenkins.diagnosis; import com.sun.akuma.JavaVMArguments; import hudson.Extension; import hudson.Functions; import hudson.Util; import hudson.model.AdministrativeMonitor; import hudson.util.jna.Kernel32Utils; import jenkins.model.Jenkins; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import org.jenkinsci.Symbol; /** * Finds crash dump reports and show them in the UI. * * @author Kohsuke Kawaguchi */ @Extension(optional=true) @Symbol("hsErrPid") // TODO why would an extension using a built-in extension point need to be marked optional? public class HsErrPidList extends AdministrativeMonitor { /** * hs_err_pid files that we think belong to us. */ /*package*/ final List<HsErrPidFile> files = new ArrayList<HsErrPidFile>(); /** * Used to keep a marker file memory-mapped, so that we can find hs_err_pid files that belong to us. */ private MappedByteBuffer map; public HsErrPidList() { if (Functions.getIsUnitTest()) { return; } try { FileChannel ch = null; try { ch = new FileInputStream(getSecretKeyFile()).getChannel(); map = ch.map(MapMode.READ_ONLY,0,1); } finally { if (ch != null) { ch.close(); } } scan("./hs_err_pid%p.log"); if (Functions.isWindows()) { File dir = Kernel32Utils.getTempDir(); if (dir!=null) { scan(dir.getPath() + "\\hs_err_pid%p.log"); } } else { scan("/tmp/hs_err_pid%p.log"); } // on different platforms, rules about the default locations are a lot more subtle. // check our arguments in the very end since this might fail on some platforms JavaVMArguments args = JavaVMArguments.current(); for (String a : args) { // see http://www.oracle.com/technetwork/java/javase/felog-138657.html if (a.startsWith(ERROR_FILE_OPTION)) { scan(a.substring(ERROR_FILE_OPTION.length())); } } } catch (UnsupportedOperationException e) { // ignore } catch (Throwable e) { LOGGER.log(Level.WARNING, "Failed to list up hs_err_pid files", e); } } @Override public String getDisplayName() { return "JVM Crash Reports"; } /** * Expose files to the URL. */ public List<HsErrPidFile> getFiles() { return files; } private void scan(String pattern) { LOGGER.fine("Scanning "+pattern+" for hs_err_pid files"); pattern = pattern.replace("%p","*").replace("%%","%"); File f = new File(pattern).getAbsoluteFile(); if (!pattern.contains("*")) scanFile(f); else {// GLOB File commonParent = f; while (commonParent!=null && commonParent.getPath().contains("*")) { commonParent = commonParent.getParentFile(); } if (commonParent==null) { LOGGER.warning("Failed to process "+f); return; // huh? } FileSet fs = Util.createFileSet(commonParent, f.getPath().substring(commonParent.getPath().length()+1), null); DirectoryScanner ds = fs.getDirectoryScanner(new Project()); for (String child : ds.getIncludedFiles()) { scanFile(new File(commonParent,child)); } } } private void scanFile(File log) { LOGGER.fine("Scanning "+log); BufferedReader r=null; try { r = new BufferedReader(new FileReader(log)); if (!findHeader(r)) return; // we should find a memory mapped file for secret.key String secretKey = getSecretKeyFile().getAbsolutePath(); String line; while ((line=r.readLine())!=null) { if (line.contains(secretKey)) { files.add(new HsErrPidFile(this,log)); return; } } } catch (IOException e) { // not a big enough deal. LOGGER.log(Level.FINE, "Failed to parse hs_err_pid file: " + log, e); } finally { IOUtils.closeQuietly(r); } } private File getSecretKeyFile() { return new File(Jenkins.getInstance().getRootDir(),"secret.key"); } private boolean findHeader(BufferedReader r) throws IOException { for (int i=0; i<5; i++) { String line = r.readLine(); if (line==null) return false; if (line.startsWith("# A fatal error has been detected by the Java Runtime Environment:")) return true; } return false; } @Override public boolean isActivated() { return !files.isEmpty(); } private static final String ERROR_FILE_OPTION = "-XX:ErrorFile="; private static final Logger LOGGER = Logger.getLogger(HsErrPidList.class.getName()); }