summaryrefslogtreecommitdiff
path: root/adblib-tools/src/com/android/adblib/tools/debugging/impl/JdwpProcessProfilerImpl.kt
blob: 492a16601d1478cbea624a32ca1d977cf62c09eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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 com.android.adblib.tools.debugging.impl

import com.android.adblib.AdbInputChannel
import com.android.adblib.AdbSession
import com.android.adblib.thisLogger
import com.android.adblib.tools.debugging.DdmsCommandException
import com.android.adblib.tools.debugging.JdwpCommandProgress
import com.android.adblib.tools.debugging.JdwpProcess
import com.android.adblib.tools.debugging.JdwpProcessProfiler
import com.android.adblib.tools.debugging.ProfilerStatus
import com.android.adblib.tools.debugging.SharedJdwpSession
import com.android.adblib.tools.debugging.Signal
import com.android.adblib.tools.debugging.createDdmsMPSE
import com.android.adblib.tools.debugging.createDdmsMPSS
import com.android.adblib.tools.debugging.createDdmsSPSE
import com.android.adblib.tools.debugging.createDdmsSPSS
import com.android.adblib.tools.debugging.handleDdmsCommandAndReplyProtocol
import com.android.adblib.tools.debugging.handleDdmsCommandWithEmptyReply
import com.android.adblib.tools.debugging.handleDdmsMPRQ
import com.android.adblib.tools.debugging.packets.JdwpPacketView
import com.android.adblib.tools.debugging.packets.ddms.DdmsChunkTypes
import com.android.adblib.tools.debugging.packets.ddms.ddmsChunks
import com.android.adblib.tools.debugging.packets.ddms.isDdmsCommand
import com.android.adblib.tools.debugging.processEmptyDdmsReplyPacket
import kotlinx.coroutines.flow.first
import java.util.concurrent.TimeUnit

internal class JdwpProcessProfilerImpl(
    override val process: JdwpProcess
) : JdwpProcessProfiler {

    private val session: AdbSession
        get() = process.device.session

    private val logger = thisLogger(session)

    override suspend fun queryStatus(progress: JdwpCommandProgress?): ProfilerStatus {
        return process.withJdwpSession {
            when (handleDdmsMPRQ(progress)) {
                0 -> ProfilerStatus.Off
                1 -> ProfilerStatus.InstrumentationProfilerRunning
                2 -> ProfilerStatus.SamplingProfilerRunning
                else -> throw DdmsCommandException("Unknown profiler status value")
            }
        }
    }

    override suspend fun startInstrumentationProfiling(
        bufferSize: Int,
        progress: JdwpCommandProgress?
    ) {
        process.withJdwpSession {
            val requestPacket = createDdmsMPSS(bufferSize)
            handleDdmsCommandWithEmptyReply(requestPacket, DdmsChunkTypes.MPSS, progress)
        }
    }

    override suspend fun <R> stopInstrumentationProfiling(
        progress: JdwpCommandProgress?,
        block: suspend (data: AdbInputChannel, dataLength: Int) -> R
    ): R {
        return process.withJdwpSession {
            logger.debug { "Stopping method profiling session" }
            val requestPacket = createDdmsMPSE()
            handleMPSEReply(requestPacket, DdmsChunkTypes.MPSE, progress, block)
        }
    }

    override suspend fun startSampleProfiling(
        interval: Long,
        intervalUnit: TimeUnit,
        bufferSize: Int,
        progress: JdwpCommandProgress?,
    ) {
        process.withJdwpSession {
            logger.debug { "Starting sampling profiling session" }
            val requestPacket = createDdmsSPSS(bufferSize, interval, intervalUnit)
            handleDdmsCommandWithEmptyReply(requestPacket, DdmsChunkTypes.SPSS, progress)
        }
    }

    override suspend fun <R> stopSampleProfiling(
        progress: JdwpCommandProgress?,
        block: suspend (data: AdbInputChannel, dataLength: Int) -> R
    ): R {
        return process.withJdwpSession {
            logger.debug { "Stopping sampling profiling session" }
            val requestPacket = createDdmsSPSE()
            handleMPSEReply(requestPacket, DdmsChunkTypes.SPSE, progress, block)
        }
    }

    private suspend fun <R> SharedJdwpSession.handleMPSEReply(
        requestPacket: JdwpPacketView,
        chunkType: DdmsChunkTypes,
        progress: JdwpCommandProgress?,
        block: suspend (data: AdbInputChannel, dataLength: Int) -> R
    ): R {
        return handleDdmsCommandAndReplyProtocol(progress) { signal ->
            handleMPSEReplyImpl(requestPacket, chunkType, progress, signal, block)
        }
    }

    private suspend fun <R> SharedJdwpSession.handleMPSEReplyImpl(
        requestPacket: JdwpPacketView,
        chunkType: DdmsChunkTypes,
        progress: JdwpCommandProgress?,
        signal: Signal<R>,
        block: suspend (data: AdbInputChannel, dataLength: Int) -> R
    ) {
        assert(chunkType == DdmsChunkTypes.MPSE || chunkType == DdmsChunkTypes.SPSE)

        // Send an "MPSE" (or "SPSE") DDMS command to the VM. Note that the AndroidVM treats "MPSE"
        // in a special way: the profiling data comes back as an "MPSE" command (with the
        // profiling data as the payload), then we get back an empty reply packet to the "MPSE"
        // command we sent.
        //   Debugger        |  AndroidVM
        //   ----------------+-----------------------------------------
        //   MPSE command => |
        //                   | (collects data)
        //                   | <=  MPSE command (with data as payload)
        //                   | <=  MPSE reply (empty payload)
        //                   | OR
        //                   | <=  FAIL command (with error code and message)

        this.newPacketReceiver()
            .withName("handleMPSEReply")
            .onActivation {
                progress?.beforeSend(requestPacket)
                sendPacket(requestPacket)
                progress?.afterSend(requestPacket)
            }.flow()
            .first { packet ->
                logger.verbose { "Receiving packet: $packet" }

                if (packet.isDdmsCommand) {
                    packet.ddmsChunks().collect { chunk ->
                        if (chunk.type == DdmsChunkTypes.MPSE) {
                            val blockResult = block(chunk.payload, chunk.length)

                            // We got the result we want, signal so that the timeout waiting for
                            // reply packet starts now.
                            signal.complete(blockResult)
                        }
                    }
                }

                // Stop when we receive the reply to our MPSE command
                val isReply = (packet.isReply && packet.id == requestPacket.id)
                if (isReply) {
                    progress?.onReply(packet)
                    processEmptyDdmsReplyPacket(packet, chunkType)
                }
                isReply
            }
    }
}