diff --git a/bbootimg/build.gradle.kts b/bbootimg/build.gradle.kts index 44678a7..ae21894 100644 --- a/bbootimg/build.gradle.kts +++ b/bbootimg/build.gradle.kts @@ -34,12 +34,12 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("cc.cfig:io:0.2") - implementation("ch.qos.logback:logback-classic:1.4.5") + implementation("ch.qos.logback:logback-classic:1.4.14") implementation("com.fasterxml.jackson.core:jackson-annotations:2.14.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") - implementation("com.google.guava:guava:31.1-jre") + implementation("com.google.guava:guava:33.0.0-jre") implementation("org.apache.commons:commons-exec:1.3") - implementation("org.apache.commons:commons-compress:1.21") + implementation("org.apache.commons:commons-compress:1.26.0") implementation("org.tukaani:xz:1.9") implementation("commons-codec:commons-codec:1.15") implementation("junit:junit:4.13.2") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 84a0b92..48c0a02 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts index ef87027..e684345 100644 --- a/helper/build.gradle.kts +++ b/helper/build.gradle.kts @@ -34,12 +34,12 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("cc.cfig:io:0.2") - implementation("com.google.guava:guava:31.1-jre") - implementation("ch.qos.logback:logback-classic:1.2.11") + implementation("com.google.guava:guava:33.0.0-jre") + implementation("ch.qos.logback:logback-classic:1.4.14") implementation("org.apache.commons:commons-exec:1.3") implementation("org.bouncycastle:bcprov-jdk15on:1.70") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") //org.bouncycastle.pkcs - implementation("org.apache.commons:commons-compress:1.21") + implementation("org.apache.commons:commons-compress:1.26.0") implementation("org.tukaani:xz:1.9") implementation("com.github.freva:ascii-table:1.2.0") implementation("com.nimbusds:nimbus-jose-jwt:9.31") @@ -50,7 +50,7 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.13.3") - testImplementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") + testImplementation("com.fasterxml.jackson.core:jackson-databind:2.16.1") } java { diff --git a/lazybox/build.gradle.kts b/lazybox/build.gradle.kts index 2b5e699..074fc06 100644 --- a/lazybox/build.gradle.kts +++ b/lazybox/build.gradle.kts @@ -4,7 +4,6 @@ * This generated file contains a sample Kotlin application project to get you started. * For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.3/userguide/building_java_projects.html in the Gradle documentation. */ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.9.22" @@ -19,9 +18,10 @@ dependencies { implementation("org.apache.commons:commons-exec:1.3") implementation("com.fasterxml.jackson.core:jackson-annotations:2.14.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") - implementation("org.slf4j:slf4j-api:2.0.9") - implementation("org.apache.commons:commons-compress:1.21") implementation(project(mapOf("path" to ":helper"))) + implementation("org.slf4j:slf4j-api:2.0.9") + implementation("org.apache.commons:commons-compress:1.26.0") + implementation("com.github.freva:ascii-table:1.8.0") // Use the Kotlin JUnit 5 integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") // Use the JUnit 5 integration. diff --git a/lazybox/src/main/kotlin/cfig/lazybox/App.kt b/lazybox/src/main/kotlin/cfig/lazybox/App.kt index 28aa4b6..75e3e4f 100644 --- a/lazybox/src/main/kotlin/cfig/lazybox/App.kt +++ b/lazybox/src/main/kotlin/cfig/lazybox/App.kt @@ -21,9 +21,10 @@ fun main(args: Array) { println("\nCommand Usage:") println("bootchart: generate Android bootchart") println("pidstat : given a pid, profile its CPU usage") + println("tracecmd : analyze trace-cmd report") exitProcess(0) } - if (args.get(0) == "cpuinfo") { + if (args[0] == "cpuinfo") { val ret = CpuInfo.construct() File("cpuinfo.json").writeText( ObjectMapper() @@ -32,16 +33,28 @@ fun main(args: Array) { ) log.info("cpuinfo.json is ready") } - if (args.get(0) == "sysinfo") { + if (args[0] == "sysinfo") { SysInfo().run() } - if (args.get(0) == "sysstat") { + if (args[0] == "sysstat") { println("adb shell /data/vendor/sadc -F -L -S ALL 2 20 /data/vendor") } - if (args.get(0) == "pidstat") { + if (args[0] == "pidstat") { Pidstat.run() } - if (args.get(0) == "bootchart") { + if (args[0] == "bootchart") { BootChart.run() } + if (args[0] == "tracecmd") { + if (args.size == 2) { + val traceCmdReport = args[1] + if (File(traceCmdReport).exists()) { + TraceCmdParser().mainFunction(traceCmdReport) + } else { + log.error("File not found: ${args[1]}") + } + } else { + log.error("Usage: tracecmd ") + } + } } diff --git a/lazybox/src/main/kotlin/cfig/lazybox/TraceCmdParser.kt b/lazybox/src/main/kotlin/cfig/lazybox/TraceCmdParser.kt new file mode 100644 index 0000000..ea8848f --- /dev/null +++ b/lazybox/src/main/kotlin/cfig/lazybox/TraceCmdParser.kt @@ -0,0 +1,250 @@ +package cfig.lazybox + +import com.github.freva.asciitable.AsciiTable +import com.github.freva.asciitable.Column +import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.text.SimpleDateFormat +import java.util.* + +class TraceCmdParser { + companion object { + val log = LoggerFactory.getLogger(TraceCmdParser::class.qualifiedName) + } + data class Event( + val task: String, + val type: String, + val cpu: Int, + val timestamp: Double, + val target: Int, + val step: Int, + val info: String, + ) + + fun readEventsFromFile(filePath: String): List { + val eventList = mutableListOf() + File(filePath).bufferedReader().useLines { lines -> + lines.forEach { line -> + if (!line.startsWith(" ")) { + log.info("Skip line: $line") + return@forEach + } + val parts = line.trim().split("\\s+".toRegex()) + val task = parts[0].trim() + val cpu = parts[1].trim('[', ']').toInt() + val timestampString = parts[2].split(':')[0] + val timestamp = timestampString.toDouble() + val eventType = parts[3].removeSuffix(":") + var info = line.trim().substring(line.trim().indexOf(parts[4])) + log.debug(info) + info.split(" ").forEachIndexed { index, s -> + log.debug("info#$index: $s") + } + var target = 0 + var step = 0 + var infoCpu = "" + var infoState = "" + var infoRet = "" + val infoList = mutableListOf() + var bWaitForTarget = false + var bWaitForStep = false + var bWaitForCPU = false + var bWaitForState = false + var bWaitForRet = false + for (item in info.split("\\s+".toRegex())) { + when (item) { + "target:" -> { + bWaitForTarget = true + continue + } + + "step:" -> { + bWaitForStep = true + continue + } + + "cpu:" -> { + bWaitForCPU = true + continue + } + + "state:" -> { + bWaitForState = true + continue + } + + "ret:" -> { + bWaitForRet = true + continue + } + + else -> { + if (bWaitForTarget) { + target = item.toInt() + bWaitForTarget = false + continue + } + if (bWaitForStep) { + step = item.toInt() + bWaitForStep = false + continue + } + if (bWaitForCPU) { + bWaitForCPU = false + infoCpu = item + continue + } + if (bWaitForState) { + bWaitForState = false + infoState = item + continue + } + if (bWaitForRet) { + bWaitForRet = false + infoRet = item + continue + } + if (item.startsWith("(")) { + //info = "cpu=$infoCpu $item" + continue + } + if (item.startsWith("cpu_id=")) { + infoCpu = item.substringAfter("cpu_id=") + continue + } + if (item.startsWith("state=")) { + infoState = item.substringAfter("state=") + continue + } + infoList.add(item) + } + } + } + eventList.add(Event(task, eventType, cpu, timestamp, target, step, info)) + } + } + return eventList + } + + private fun calcTimeDiff(eventList: List): List> { + val result = mutableListOf>() + eventList.sortedBy { it.timestamp }.forEach { evt -> + result.add( + mutableMapOf( + "task" to evt.task, + "eventType" to evt.type, + "cpu" to evt.cpu, + "startTime" to (if (!evt.type.endsWith("_exit")) evt.timestamp else 0.0), + "endTime" to (if (evt.type.endsWith("_exit")) evt.timestamp else 0.0), + "timeDifference" to "", + "target" to evt.target, + "step" to evt.step, + "info" to evt.info, + ) + ) + } + + eventList.map { it.step }.toSet().forEach { stepNo -> + val eventPair = eventList.filter { it.step == stepNo }.sortedBy { it.timestamp } + if (eventPair.size.rem(2) == 0) { + eventPair.windowed(2, step = 2).forEachIndexed { index, events -> + val startEvent = events[0] + val endEvent = events[1] + if (startEvent.type.endsWith("_enter") and endEvent.type.endsWith("_exit")) { + val timeDifference = (endEvent.timestamp - startEvent.timestamp) * 1000 + var timeDiffStr = "%.2f".format(timeDifference) + if (timeDiffStr == "0.00") { + timeDiffStr = "0" + } + val idx = result.indexOfFirst { it["step"] == stepNo && it["startTime"] == startEvent.timestamp } + result.get(idx).put("timeDifference", timeDiffStr) + val idx2 = result.indexOfFirst { it["step"] == stepNo && it["endTime"] == endEvent.timestamp } + result.get(idx2).put("timeDifference", "-") + } else { + println("Invalid event pair: $startEvent, $endEvent") + return@forEachIndexed + } + } + } + } + + return result + } + + fun mainFunction(filePath: String) { + val eventList = readEventsFromFile(filePath) + val timeTable = calcTimeDiff(eventList) + val tb = AsciiTable.getTable( + timeTable, Arrays.asList( + Column().header("Task").with { it["task"] as String }, + Column().header("Event Type").with { it["eventType"] as String }, + Column().header("Step").with { (it["step"] as Int).toString() }, + Column().header("CPU").with { (it["cpu"] as Int).toString() }, + Column().header("Start Time (s)") + .with { val v = (it["startTime"] as Double).toString(); if (v == "0.0") "" else v }, + Column().header("End Time (s)") + .with { val v = (it["endTime"] as Double).toString(); if (v == "0.0") "" else v }, + Column().header("Duration (ms)").with { it["timeDifference"] as String }, + Column().header("Info").with { "`" + it["info"] as String + "`" }, + ) + ) + val mdFile = File(File(filePath).parent, "trace.md") + log.info("Writing to $mdFile ...") + mdFile.let { outFile -> + val cssStyle: String = """ + | +""".trimMargin() + val sep = "| -- | -- | -- | -- | -- | -- | -- | -- |" + val tableLines = tb.toString().split("\n").filterNot { it.contains("+--") } + val modifiedList = mutableListOf().apply { + addAll(tableLines.subList(0, 1)) // Add items 0 (inclusive) to 1 (exclusive) + add(sep) // Add the new string + addAll(tableLines.subList(1, tableLines.size)) // Add items 1 (inclusive) to the end + } + outFile.writeText(cssStyle) + outFile.appendText("\n::: {style=\"text-align:center\"}\n") + outFile.appendText("# Trace\n") + outFile.appendText(":::\n") + outFile.appendText("Generated at ${getCurrentTime()}\n\n") + modifiedList.forEach { outFile.appendText("$it\n") } + } + + val htmlFile = File(File(filePath).parent, "trace.html") + runPandocCommand(mdFile.toString(), htmlFile.toString()) + } + + private fun getCurrentTime(): String { + val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + val currentTime = Date() + return dateFormat.format(currentTime) + } + + private fun runPandocCommand(inputFile: String, outputFile: String) { + val pandocCommand = "pandoc $inputFile -o $outputFile" + log.info("Pandoc: $inputFile -> $outputFile") + try { + val processBuilder = ProcessBuilder("/bin/bash", "-c", pandocCommand) + processBuilder.redirectErrorStream(true) + val process = processBuilder.start() + val reader = BufferedReader(InputStreamReader(process.inputStream)) + var line: String? + while (reader.readLine().also { line = it } != null) { + log.info(line) + } + val exitCode = process.waitFor() + log.info("Pandoc process exited with code $exitCode") + } catch (e: Exception) { + e.printStackTrace() + } + } +}