Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.projects.isPluginProject
import com.itsaky.androidide.projects.models.assembleTaskOutputListingFile
import com.itsaky.androidide.tooling.api.GradlePluginConfig.PROPERTY_PROFILEABLE_ENABLED
import com.itsaky.androidide.tooling.api.messages.BuildRunType
import com.itsaky.androidide.tooling.api.messages.GradleBuildParams
import com.itsaky.androidide.tooling.api.messages.TaskExecutionMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,14 @@ class ProfilerViewModel(application: Application) : AndroidViewModel(application
}
}
}.onSuccess { profile ->
_state.value = ProfilerUiState.CpuResult(process, profile)
if (profile.totalMicros <= 0L || profile.methods.isEmpty()) {
// simpleperf produced no usable samples (e.g. too short a session, idle/dead
// target, or a failed report) — surface a clear message instead of an empty table.
logger.warn("CPU profiling produced no samples for pid={}", process.pid)
setError(R.string.profiler_cpu_no_samples)
} else {
_state.value = ProfilerUiState.CpuResult(process, profile)
}
}.onFailure { error ->
logger.error("CPU profiling stop/parse failed for pid={}", process.pid, error)
setError(getString(R.string.profiler_cpu_failed, error.message ?: error.javaClass.simpleName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ object SimpleperfReportParser {
private const val MAGIC = "SIMPLEPERF"
private const val MAX_METHOD_ROWS = 100

/** Returned when the report stream is empty/truncated (no usable samples). */
private val EMPTY_PROFILE =
CpuProfile(
root = CpuCallNode(name = "(root)", selfMicros = 0, totalMicros = 0, children = emptyList()),
totalMicros = 0,
methods = emptyList(),
)

private class FileEntry(val path: String, val symbols: List<String>)

/** A buffered sample: [weight] (ns) and its callchain as interleaved (fileId, symbolId) pairs. */
Expand All @@ -51,12 +59,19 @@ object SimpleperfReportParser {
fun parse(input: InputStream): CpuProfile {
val data = DataInputStream(input)

// An empty/too-short stream means simpleperf produced no report (e.g. a failed or aborted
// recording, or a perf.data with no samples that never got written). Treat it as an empty
// profile rather than throwing an EOFException.
val magic = ByteArray(MAGIC.length)
data.readFully(magic)
require(String(magic, Charsets.US_ASCII) == MAGIC) {
"Not a simpleperf report-sample protobuf stream"
try {
data.readFully(magic)
readLe16(data) // format version, not needed
} catch (e: EOFException) {
return EMPTY_PROFILE
}
if (String(magic, Charsets.US_ASCII) != MAGIC) {
throw IllegalArgumentException("Not a simpleperf report-sample protobuf stream")
}
readLe16(data) // format version, not needed

val files = HashMap<Int, FileEntry>()
val samples = ArrayList<RawSample>()
Expand All @@ -66,7 +81,12 @@ object SimpleperfReportParser {
val size = readLe32(data) ?: break
if (size == 0) break
val bytes = ByteArray(size)
data.readFully(bytes)
try {
data.readFully(bytes)
} catch (e: EOFException) {
// Truncated stream (e.g. recording killed mid-flush); use whatever was buffered.
break
}
val record = SimpleperfReportProto.Record.parseFrom(bytes)

when {
Expand Down
1 change: 1 addition & 0 deletions resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,7 @@
<string name="profiler_cpu_processing">Generating CPU report…</string>
<string name="profiler_cpu_failed">CPU profiling failed: %1$s</string>
<string name="profiler_cpu_failed_generic">CPU profiling failed.</string>
<string name="profiler_cpu_no_samples">No CPU samples were captured. Try profiling for a bit longer.</string>
<string name="profiler_col_method">Method</string>
<string name="profiler_col_total_us">Total (µs)</string>
<string name="profiler_col_total_pct">Total %</string>
Expand Down
Loading