/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.cassandra.utils; import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.text.StrBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility to generate heap dumps. * */ public final class HeapUtils { private static final Logger logger = LoggerFactory.getLogger(HeapUtils.class); /** * Generates a HEAP dump in the directory specified by the <code>HeapDumpPath</code> JVM option * or in the <code>CASSANDRA_HOME</code> directory. */ public static void generateHeapDump() { Long processId = getProcessId(); if (processId == null) { logger.error("The process ID could not be retrieved. Skipping heap dump generation."); return; } String heapDumpPath = getHeapDumpPathOption(); if (heapDumpPath == null) { String cassandraHome = System.getenv("CASSANDRA_HOME"); if (cassandraHome == null) { return; } heapDumpPath = cassandraHome; } Path dumpPath = FileSystems.getDefault().getPath(heapDumpPath); if (Files.isDirectory(dumpPath)) { dumpPath = dumpPath.resolve("java_pid" + processId + ".hprof"); } String jmapPath = getJmapPath(); // The jmap file could not be found. In this case let's default to jmap in the hope that it is in the path. String jmapCommand = jmapPath == null ? "jmap" : jmapPath; String[] dumpCommands = new String[] {jmapCommand, "-dump:format=b,file=" + dumpPath, processId.toString()}; // Lets also log the Heap histogram String[] histoCommands = new String[] {jmapCommand, "-histo", processId.toString()}; try { logProcessOutput(Runtime.getRuntime().exec(dumpCommands)); logProcessOutput(Runtime.getRuntime().exec(histoCommands)); } catch (IOException e) { logger.error("The heap dump could not be generated due to the following error: ", e); } } /** * Retrieve the path to the JMAP executable. * @return the path to the JMAP executable or null if it cannot be found. */ private static String getJmapPath() { // Searching in the JAVA_HOME is safer than searching into System.getProperty("java.home") as the Oracle // JVM might use the JRE which do not contains jmap. String javaHome = System.getenv("JAVA_HOME"); if (javaHome == null) return null; File javaBinDirectory = new File(javaHome, "bin"); File[] files = javaBinDirectory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.startsWith("jmap"); } }); return ArrayUtils.isEmpty(files) ? null : files[0].getPath(); } /** * Logs the output of the specified process. * * @param p the process * @throws IOException if an I/O problem occurs */ private static void logProcessOutput(Process p) throws IOException { BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); StrBuilder builder = new StrBuilder(); String line; while ((line = input.readLine()) != null) { builder.appendln(line); } logger.info(builder.toString()); } /** * Retrieves the value of the <code>HeapDumpPath</code> JVM option. * @return the value of the <code>HeapDumpPath</code> JVM option or <code>null</code> if the value has not been * specified. */ private static String getHeapDumpPathOption() { RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); List<String> inputArguments = runtimeMxBean.getInputArguments(); String heapDumpPathOption = null; for (String argument : inputArguments) { if (argument.startsWith("-XX:HeapDumpPath=")) { heapDumpPathOption = argument; // We do not break in case the option has been specified several times. // In general it seems that JVMs use the right-most argument as the winner. } } if (heapDumpPathOption == null) return null; return heapDumpPathOption.substring(17, heapDumpPathOption.length()); } /** * Retrieves the process ID or <code>null</code> if the process ID cannot be retrieved. * @return the process ID or <code>null</code> if the process ID cannot be retrieved. */ private static Long getProcessId() { // Once Java 9 is ready the process API should provide a better way to get the process ID. long pid = SigarLibrary.instance.getPid(); if (pid >= 0) return Long.valueOf(pid); return getProcessIdFromJvmName(); } /** * Retrieves the process ID from the JVM name. * @return the process ID or <code>null</code> if the process ID cannot be retrieved. */ private static Long getProcessIdFromJvmName() { // the JVM name in Oracle JVMs is: '<pid>@<hostname>' but this might not be the case on all JVMs String jvmName = ManagementFactory.getRuntimeMXBean().getName(); try { return Long.parseLong(jvmName.split("@")[0]); } catch (NumberFormatException e) { // ignore } return null; } /** * The class must not be instantiated. */ private HeapUtils() { } }