From ce0533262dbe682a33fdabedd74c025347c00f4c Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 18 Jun 2026 23:20:20 +0530 Subject: [PATCH] fix: EOFException is reported when no samples are recorded Signed-off-by: Akash Yadav --- .../androidide/viewmodel/BuildViewModel.kt | 1 - .../cotg/profiler/ProfilerViewModel.kt | 9 +++++- .../profiler/cpu/SimpleperfReportParser.kt | 30 +++++++++++++++---- resources/src/main/res/values/strings.xml | 1 + 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt index 5f002f8110..790276a4de 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/BuildViewModel.kt @@ -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 diff --git a/profiler/src/main/java/org/appdevforall/cotg/profiler/ProfilerViewModel.kt b/profiler/src/main/java/org/appdevforall/cotg/profiler/ProfilerViewModel.kt index 24451a14a2..23819f718a 100644 --- a/profiler/src/main/java/org/appdevforall/cotg/profiler/ProfilerViewModel.kt +++ b/profiler/src/main/java/org/appdevforall/cotg/profiler/ProfilerViewModel.kt @@ -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)) diff --git a/profiler/src/main/java/org/appdevforall/cotg/profiler/cpu/SimpleperfReportParser.kt b/profiler/src/main/java/org/appdevforall/cotg/profiler/cpu/SimpleperfReportParser.kt index 73d071c446..b599e4fc62 100644 --- a/profiler/src/main/java/org/appdevforall/cotg/profiler/cpu/SimpleperfReportParser.kt +++ b/profiler/src/main/java/org/appdevforall/cotg/profiler/cpu/SimpleperfReportParser.kt @@ -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) /** A buffered sample: [weight] (ns) and its callchain as interleaved (fileId, symbolId) pairs. */ @@ -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() val samples = ArrayList() @@ -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 { diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index c398a3d8d6..9d8eeef7c4 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -1353,6 +1353,7 @@ Generating CPU report… CPU profiling failed: %1$s CPU profiling failed. + No CPU samples were captured. Try profiling for a bit longer. Method Total (µs) Total %