From f77bf84cb290cbc2b1d522b51c726658eaa193bc Mon Sep 17 00:00:00 2001 From: cfig Date: Sun, 14 Sep 2025 00:08:55 +0800 Subject: [PATCH] lazybox: support compile_commands in AOSP C++ --- lazybox/src/main/kotlin/cfig/lazybox/App.kt | 12 +- .../cfig/lazybox/staging/AospCompiledb.kt | 180 ++++++++++++++++++ .../kotlin/cfig/lazybox/sysinfo/BootChart.kt | 7 +- .../cfig/lazybox/sysinfo/ThermalInfo.kt | 73 +++++++ 4 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 lazybox/src/main/kotlin/cfig/lazybox/staging/AospCompiledb.kt create mode 100644 lazybox/src/main/kotlin/cfig/lazybox/sysinfo/ThermalInfo.kt diff --git a/lazybox/src/main/kotlin/cfig/lazybox/App.kt b/lazybox/src/main/kotlin/cfig/lazybox/App.kt index 740e052..0ad5b53 100644 --- a/lazybox/src/main/kotlin/cfig/lazybox/App.kt +++ b/lazybox/src/main/kotlin/cfig/lazybox/App.kt @@ -1,5 +1,6 @@ package cfig.lazybox +import cfig.lazybox.staging.AospCompiledb import cfig.lazybox.staging.DiffCI import cfig.lazybox.staging.Perfetto import cfig.lazybox.staging.RepoWorker @@ -7,6 +8,7 @@ import cfig.lazybox.sysinfo.BootChart import cfig.lazybox.sysinfo.CpuInfo import cfig.lazybox.sysinfo.Pidstat import cfig.lazybox.sysinfo.SysInfo +import cfig.lazybox.sysinfo.ThermalInfo import com.fasterxml.jackson.databind.ObjectMapper import org.slf4j.LoggerFactory import java.io.File @@ -20,15 +22,17 @@ fun main(args: Array) { println("Usage: args: (Array) ...") println(" or: function [arguments]...") println("\nCurrently defined functions:") - println("\tcpuinfo sysinfo sysstat pidstat bootchart") + println("\tcpuinfo sysinfo sysstat pidstat bootchart thermal_info compiledb") println("\nCommand Usage:") println("bootchart: generate Android bootchart") println("pidstat : given a pid, profile its CPU usage") println("tracecmd : analyze trace-cmd report") println("cpuinfo : get cpu info from /sys/devices/system/cpu/") println("sysinfo : get overall system info from Android") + println("thermal_info : get thermal info from /sys/class/thermal/") println("\nIncubating usage:") println("apps : get apk file list from Android") + println("compiledb : generate compilation database for AOSP") println("dmainfo : parse /d/dma_buf/bufinfo") println("diffci : find changelist files from CI server based on date and time ranges") println("repo_lfs : pull LFS files from Git repositories managed by 'repo'") @@ -48,6 +52,9 @@ fun main(args: Array) { if (args[0] == "sysinfo") { SysInfo().run() } + if (args[0] == "thermal_info") { + ThermalInfo().run() + } if (args[0] == "sysstat") { println("adb shell /data/vendor/sadc -F -L -S ALL 2 20 /data/vendor") } @@ -116,4 +123,7 @@ fun main(args: Array) { if (args[0] == "perfetto") { Perfetto().run(args.drop(1).toTypedArray()) } + if (args[0] == "compiledb") { + AospCompiledb().run() + } } diff --git a/lazybox/src/main/kotlin/cfig/lazybox/staging/AospCompiledb.kt b/lazybox/src/main/kotlin/cfig/lazybox/staging/AospCompiledb.kt new file mode 100644 index 0000000..9c4dd84 --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/staging/AospCompiledb.kt @@ -0,0 +1,180 @@ +package cfig.lazybox.staging + +import com.fasterxml.jackson.databind.ObjectMapper +import java.io.File +import java.io.FileInputStream +import java.util.regex.Pattern +import java.util.zip.GZIPInputStream + +class AospCompiledb { + data class CompileCommand( + val directory: String, + val command: String, + val file: String + ) + + fun findAndroidRoot(logFile: File): String { + // Get absolute path of the log file + val logAbsPath = logFile.absoluteFile + + // The log is in out/verbose.log.gz, so Android root is the parent of the "out" directory + var currentDir = logAbsPath.parentFile // This should be "out" directory + while (currentDir != null && currentDir.name != "out") { + currentDir = currentDir.parentFile + } + + return if (currentDir != null) { + // Go up one more level to get the Android root (parent of "out") + currentDir.parentFile?.absolutePath ?: System.getProperty("user.dir") + } else { + // Fallback: try to find Android root by looking for typical Android files + var dir = logAbsPath.parentFile + while (dir != null) { + if (File(dir, "build/make").exists() || File(dir, "Makefile").exists() || File(dir, "build.gradle").exists()) { + return dir.absolutePath + } + dir = dir.parentFile + } + System.getProperty("user.dir") + } + } + + fun parseVerboseLog(gzFile: File, androidRoot: String): List { + val compileCommands = mutableListOf() + val ninjaCommandPattern = Pattern.compile("""^\[(\d+)/(\d+)\]\s+(.+)$""") + + GZIPInputStream(FileInputStream(gzFile)).bufferedReader().use { reader -> + reader.lineSequence().forEach { line -> + val matcher = ninjaCommandPattern.matcher(line) + if (matcher.matches()) { + val command = matcher.group(3) + + // Only process compilation commands (with -c flag), not linking commands + if (command.contains(" -c ") && command.contains("clang")) { + val compileCommand = parseCompilationCommand(command, androidRoot) + if (compileCommand != null) { + compileCommands.add(compileCommand) + } + } + } + } + } + + return compileCommands + } + + fun parseCompilationCommand(commandLine: String, androidRoot: String): CompileCommand? { + try { + // Parse the command to extract compiler path, flags, and source file + val parts = splitCommandLine(commandLine) + if (parts.isEmpty()) return null + + // Find the compiler executable + val compilerIndex = parts.indexOfFirst { it.contains("clang") } + if (compilerIndex == -1) return null + + // Find source files (typically .c, .cpp, .cc files that are not output files) + val sourceFiles = findSourceFiles(parts) + if (sourceFiles.isEmpty()) return null + + // Build the clean command (without PWD prefix) + val cleanCommand = buildCleanCommand(parts, compilerIndex) + + // Create compile command for each source file + val sourceFile = sourceFiles.first() // Take the first source file + + return CompileCommand( + directory = androidRoot, + command = cleanCommand, + file = sourceFile + ) + } catch (e: Exception) { + println("Warning: Failed to parse command: ${e.message}") + return null + } + } + + + fun splitCommandLine(commandLine: String): List { + // Remove PWD= prefix if present + val cleanCommand = commandLine.replace(Regex("""PWD=[^\s]+\s*"""), "") + + // Simple command line splitting (handles basic quoting) + val parts = mutableListOf() + var current = StringBuilder() + var inQuotes = false + var escapeNext = false + + for (char in cleanCommand) { + when { + escapeNext -> { + current.append(char) + escapeNext = false + } + char == '\\' -> { + escapeNext = true + } + char == '"' -> { + inQuotes = !inQuotes + } + char == ' ' && !inQuotes -> { + if (current.isNotEmpty()) { + parts.add(current.toString()) + current = StringBuilder() + } + } + else -> { + current.append(char) + } + } + } + + if (current.isNotEmpty()) { + parts.add(current.toString()) + } + + return parts + } + + fun findSourceFiles(parts: List): List { + val sourceExtensions = setOf(".c", ".cpp", ".cc", ".cxx", ".c++") + return parts.filter { part -> + sourceExtensions.any { ext -> part.endsWith(ext) } && + !part.startsWith("-") && // Not a flag + !part.contains("crtbegin") && // Not a crt file + !part.contains("crtend") && + File(part).extension in sourceExtensions.map { it.substring(1) } + } + } + + fun buildCleanCommand(parts: List, compilerIndex: Int): String { + // Join all parts starting from the compiler + return parts.drop(compilerIndex).joinToString(" ") + } + + fun run() { + val logFile = File("out/verbose.log.gz") + val outputFile = File("compile_commands.json") + + if (!logFile.exists()) { + println("Error: verbose.log.gz not found in out/ directory") + return + } + + // Find Android root directory from verbose log location + val androidRoot = findAndroidRoot(logFile) + println("Android root directory: $androidRoot") + + println("Parsing verbose build log...") + val compileCommands = parseVerboseLog(logFile, androidRoot) + + println("Found ${compileCommands.size} compilation commands") + + // Generate JSON + val json = ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(compileCommands) + + outputFile.writeText(json) + println("Generated compile_commands.json with ${compileCommands.size} entries") + + } +} diff --git a/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/BootChart.kt b/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/BootChart.kt index 281229d..e9379d1 100644 --- a/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/BootChart.kt +++ b/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/BootChart.kt @@ -9,11 +9,13 @@ class BootChart { companion object { private val log = LoggerFactory.getLogger(BootChart::class.java) fun run() { + /* "adb wait-for-device".check_call() "adb root".check_call() "adb wait-for-device".check_call() "adb shell touch /data/bootchart/enabled".check_call() - Helper.adbCmd("reboot") + "adb reboot".check_call() + //Helper.adbCmd("reboot") "adb wait-for-device".check_call() "adb root".check_call() "adb wait-for-device".check_call() @@ -29,6 +31,7 @@ class BootChart { TimeUnit.SECONDS.sleep(1) } } + */ "header proc_stat.log proc_ps.log proc_diskstats.log".split("\\s".toRegex()).forEach { val LOGROOT = "/data/bootchart/" "adb pull ${LOGROOT}$it".check_call() @@ -38,4 +41,4 @@ class BootChart { "xdg-open bootchart.png".check_call() } } -} \ No newline at end of file +} diff --git a/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/ThermalInfo.kt b/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/ThermalInfo.kt new file mode 100644 index 0000000..99cf8b2 --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/sysinfo/ThermalInfo.kt @@ -0,0 +1,73 @@ +package cfig.lazybox.sysinfo + +import cfig.helper.Helper +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileOutputStream +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.io.path.deleteIfExists +import kotlin.io.path.writeText + +class ThermalInfo { + private val log = LoggerFactory.getLogger(ThermalInfo::class.java) + + fun makeTar(tarFile: String, srcDir: String) { + val pyScript = + """ +import os, sys, subprocess, gzip, logging, shutil, tarfile, os.path +def makeTar(output_filename, source_dir): + with tarfile.open(output_filename, "w:xz") as tar: + tar.add(source_dir, arcname=os.path.basename(source_dir)) +makeTar("%s", "%s") +""".trim() + val tmp = Files.createTempFile(Paths.get("."), "xx.", ".yy") + tmp.writeText(String.format(pyScript, tarFile, srcDir)) + ("python " + tmp.fileName).check_call() + tmp.deleteIfExists() + } + + fun run() { + "adb wait-for-device".check_call() + "adb root".check_call() + val prefix = "thermal_info" + File(prefix).let { + if (it.exists()) { + log.info("purging directory $prefix/ ...") + it.deleteRecursively() + } + } + File(prefix).mkdir() + + val thermalZonesOutput = Helper.powerRun("adb shell ls /sys/class/thermal", null) + val thermalZones = String(thermalZonesOutput.get(0)).split("\\s+".toRegex()).filter { it.startsWith("thermal_zone") } + + if (thermalZones.isEmpty()) { + log.warn("No thermal zones found.") + } else { + thermalZones.forEach { zone -> + log.info("pulling info from $zone") + val zoneDir = File("$prefix/$zone") + zoneDir.mkdir() + FileOutputStream("${zoneDir.path}/type").use { + SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/type", it, false) + } + FileOutputStream("${zoneDir.path}/temp").use { + SysInfo.runAndWrite("adb shell cat /sys/class/thermal/$zone/temp", it, false) + } + } + } + + makeTar("$prefix.tar.xz", prefix) + File(prefix).deleteRecursively() + log.info("$prefix.tar.xz is ready") + } + + private fun String.check_call() { + val ret = Helper.powerRun(this, null) + val ret2 = String(ret.get(0)).trim() + if (ret2.isNotEmpty()) { + log.info(ret2) + } + } +}