• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package shark
2 
3 import com.github.ajalt.clikt.core.CliktCommand
4 import com.github.ajalt.clikt.core.PrintMessage
5 import com.github.ajalt.clikt.core.UsageError
6 import shark.SharkCliCommand.Companion.echo
7 import shark.SharkCliCommand.Companion.retrieveHeapDumpFile
8 import shark.SharkCliCommand.Companion.runCommand
9 import shark.SharkCliCommand.Companion.sharkCliParams
10 import shark.SharkCliCommand.HeapDumpSource.ProcessSource
11 import java.io.File
12 import java.text.SimpleDateFormat
13 import java.util.Date
14 import java.util.Locale
15 
16 class DumpProcessCommand : CliktCommand(
17   name = "dump-process",
18   help = "Dump the heap and pull the hprof file."
19 ) {
20 
runnull21   override fun run() {
22     val params = context.sharkCliParams
23     if (params.source !is ProcessSource) {
24       throw UsageError("dump-process must be used with --process")
25     }
26     val file = retrieveHeapDumpFile(params)
27     echo("Pulled heap dump to $file")
28   }
29 
30   companion object {
31 
32     private val SPACE_PATTERN = Regex("\\s+")
33 
34     @Suppress("ThrowsCount")
CliktCommandnull35     fun CliktCommand.dumpHeap(
36       processNameParam: String,
37       maybeDeviceId: String?
38     ): File {
39       val workingDirectory = File(System.getProperty("user.dir"))
40 
41       val deviceList = runCommand(workingDirectory, "adb", "devices")
42 
43       val connectedDevices = deviceList.lines()
44         .drop(1)
45         .filter { it.isNotBlank() }
46         .map { SPACE_PATTERN.split(it)[0] }
47 
48       val deviceId = if (connectedDevices.isEmpty()) {
49         throw PrintMessage("Error: No device connected to adb")
50       } else if (maybeDeviceId == null) {
51         if (connectedDevices.size == 1) {
52           connectedDevices[0]
53         } else {
54           throw PrintMessage(
55             "Error: more than one device/emulator connected to adb," +
56               " use '--device ID' argument with one of $connectedDevices"
57           )
58         }
59       } else {
60         if (maybeDeviceId in connectedDevices) {
61           maybeDeviceId
62         } else {
63           throw PrintMessage(
64             "Error: device '$maybeDeviceId' not in the list of connected devices $connectedDevices"
65           )
66         }
67       }
68 
69       val processList = runCommand(workingDirectory, "adb", "-s", deviceId, "shell", "ps")
70 
71       val matchingProcesses = processList.lines()
72         .filter { it.contains(processNameParam) }
73         .map {
74           val columns = SPACE_PATTERN.split(it)
75           columns[8] to columns[1]
76         }
77 
78       val (processName, processId) = when {
79         matchingProcesses.size == 1 -> {
80           matchingProcesses[0]
81         }
82         matchingProcesses.isEmpty() -> {
83           throw PrintMessage("Error: No process matching \"$processNameParam\"")
84         }
85         else -> {
86           matchingProcesses.firstOrNull { it.first == processNameParam }
87             ?: throw PrintMessage(
88               "Error: More than one process matches \"$processNameParam\" but none matches exactly: ${matchingProcesses.map { it.first }}"
89             )
90         }
91       }
92 
93       val heapDumpFileName =
94         SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'-$processName.hprof'", Locale.US).format(
95           Date()
96         )
97 
98       val heapDumpDevicePath = "/data/local/tmp/$heapDumpFileName"
99 
100       echo(
101         "Dumping heap on $deviceId for process \"$processName\" with pid $processId to $heapDumpDevicePath"
102       )
103 
104       runCommand(
105         workingDirectory, "adb", "-s", deviceId, "shell", "am", "dumpheap", processId,
106         heapDumpDevicePath
107       )
108 
109       // Dump heap takes time but adb returns immediately.
110       Thread.sleep(5000)
111 
112       SharkLog.d { "Pulling $heapDumpDevicePath" }
113 
114       val pullResult =
115         runCommand(workingDirectory, "adb", "-s", deviceId, "pull", heapDumpDevicePath)
116       SharkLog.d { pullResult }
117       SharkLog.d { "Removing $heapDumpDevicePath" }
118 
119       runCommand(workingDirectory, "adb", "-s", deviceId, "shell", "rm", heapDumpDevicePath)
120 
121       val heapDumpFile = File(workingDirectory, heapDumpFileName)
122       SharkLog.d { "Pulled heap dump to $heapDumpFile" }
123 
124       return heapDumpFile
125     }
126   }
127 }