DumpProcessCommand.kt
TLDR
This file defines the DumpProcessCommand
class, which is a command-line command that dumps the heap and pulls the hprof file. It also includes a utility method dumpHeap
for dumping the heap of a specified process.
Methods
dumpHeap
This method takes two parameters: processNameParam
(the name of the process to dump the heap for) and maybeDeviceId
(optional device ID to specify the device to dump the heap from). It performs the following steps:
- Retrieves the working directory.
- Runs the
adb devices
command to get the list of connected devices. - Filters and extracts the device IDs from the output of the
adb devices
command. - If no devices are connected, throws a
PrintMessage
error. - If no
maybeDeviceId
is specified and there is only one connected device, selects that device. - If no
maybeDeviceId
is specified and there are multiple connected devices, throws aPrintMessage
error. - If
maybeDeviceId
is specified but it is not in the list of connected devices, throws aPrintMessage
error. - Runs the
adb shell ps
command to get the list of running processes on the specified device. - Filters the processes to find the ones that contain
processNameParam
. - If there is exactly one matching process, selects that process.
- If there are no matching processes, throws a
PrintMessage
error. - If there are multiple matching processes but none matches exactly with
processNameParam
, throws aPrintMessage
error. - Generates a unique heap dump file name based on the current date and time.
- Specifies the path of the heap dump file on the device.
- Prints a message indicating the heap dump process has started.
- Runs the
adb shell am dumpheap
command to dump the heap for the selected process. - Waits for 5 seconds.
- Prints a debug log message indicating that the heap dump is being pulled.
- Runs the
adb pull
command to pull the heap dump file from the device. - Prints the output of the pull command.
- Prints a debug log message indicating that the heap dump file on the device is being removed.
- Runs the
adb shell rm
command to remove the heap dump file from the device. - Creates a
File
object representing the pulled heap dump file. - Prints a debug log message indicating the heap dump file has been pulled.
- Returns the
File
object representing the pulled heap dump file.
Classes
DumpProcessCommand
This class extends the CliktCommand
class and represents a command-line command dump-process
that dumps the heap and pulls the hprof file. It overrides the run
method to perform the following steps:
- Retrieves the
sharkCliParams
from the command context. - Throws a
UsageError
if thesource
insharkCliParams
is not aProcessSource
. - Calls the
retrieveHeapDumpFile
method withsharkCliParams
to retrieve the heap dump file. - Prints a message indicating the heap dump file has been pulled.
That's all! There are no more methods or classes in this file.
package shark
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.PrintMessage
import com.github.ajalt.clikt.core.UsageError
import shark.SharkCliCommand.Companion.echo
import shark.SharkCliCommand.Companion.retrieveHeapDumpFile
import shark.SharkCliCommand.Companion.runCommand
import shark.SharkCliCommand.Companion.sharkCliParams
import shark.SharkCliCommand.HeapDumpSource.ProcessSource
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class DumpProcessCommand : CliktCommand(
name = "dump-process",
help = "Dump the heap and pull the hprof file."
) {
override fun run() {
val params = context.sharkCliParams
if (params.source !is ProcessSource) {
throw UsageError("dump-process must be used with --process")
}
val file = retrieveHeapDumpFile(params)
echo("Pulled heap dump to $file")
}
companion object {
private val SPACE_PATTERN = Regex("\\s+")
@Suppress("ThrowsCount")
fun CliktCommand.dumpHeap(
processNameParam: String,
maybeDeviceId: String?
): File {
val workingDirectory = File(System.getProperty("user.dir"))
val deviceList = runCommand(workingDirectory, "adb", "devices")
val connectedDevices = deviceList.lines()
.drop(1)
.filter { it.isNotBlank() }
.map { SPACE_PATTERN.split(it)[0] }
val deviceId = if (connectedDevices.isEmpty()) {
throw PrintMessage("Error: No device connected to adb")
} else if (maybeDeviceId == null) {
if (connectedDevices.size == 1) {
connectedDevices[0]
} else {
throw PrintMessage(
"Error: more than one device/emulator connected to adb," +
" use '--device ID' argument with one of $connectedDevices"
)
}
} else {
if (maybeDeviceId in connectedDevices) {
maybeDeviceId
} else {
throw PrintMessage(
"Error: device '$maybeDeviceId' not in the list of connected devices $connectedDevices"
)
}
}
val processList = runCommand(workingDirectory, "adb", "-s", deviceId, "shell", "ps")
val matchingProcesses = processList.lines()
.filter { it.contains(processNameParam) }
.map {
val columns = SPACE_PATTERN.split(it)
columns[8] to columns[1]
}
val (processName, processId) = when {
matchingProcesses.size == 1 -> {
matchingProcesses[0]
}
matchingProcesses.isEmpty() -> {
throw PrintMessage("Error: No process matching \"$processNameParam\"")
}
else -> {
matchingProcesses.firstOrNull { it.first == processNameParam }
?: throw PrintMessage(
"Error: More than one process matches \"$processNameParam\" but none matches exactly: ${matchingProcesses.map { it.first }}"
)
}
}
val heapDumpFileName =
SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'-$processName.hprof'", Locale.US).format(
Date()
)
val heapDumpDevicePath = "/data/local/tmp/$heapDumpFileName"
echo(
"Dumping heap on $deviceId for process \"$processName\" with pid $processId to $heapDumpDevicePath"
)
runCommand(
workingDirectory, "adb", "-s", deviceId, "shell", "am", "dumpheap", processId,
heapDumpDevicePath
)
// Dump heap takes time but adb returns immediately.
Thread.sleep(5000)
SharkLog.d { "Pulling $heapDumpDevicePath" }
val pullResult =
runCommand(workingDirectory, "adb", "-s", deviceId, "pull", heapDumpDevicePath)
SharkLog.d { pullResult }
SharkLog.d { "Removing $heapDumpDevicePath" }
runCommand(workingDirectory, "adb", "-s", deviceId, "shell", "rm", heapDumpDevicePath)
val heapDumpFile = File(workingDirectory, heapDumpFileName)
SharkLog.d { "Pulled heap dump to $heapDumpFile" }
return heapDumpFile
}
}
}