lazybox: add trace-cmd analyzer

pull/140/head
cfig 12 months ago
parent afc0eabc32
commit 5b08470d76
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -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")

@ -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

@ -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 {

@ -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.

@ -21,9 +21,10 @@ fun main(args: Array<String>) {
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<String>) {
)
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 <report_file>")
}
}
}

@ -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<Event> {
val eventList = mutableListOf<Event>()
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<String>()
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<Event>): List<Map<String, Any>> {
val result = mutableListOf<MutableMap<String, Any>>()
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 = """
|<style>
| table {
| border-collapse: collapse;
| }
|
| table, th, td {
| border: 1px solid black;
| }
|</style>
""".trimMargin()
val sep = "| -- | -- | -- | -- | -- | -- | -- | -- |"
val tableLines = tb.toString().split("\n").filterNot { it.contains("+--") }
val modifiedList = mutableListOf<String>().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()
}
}
}
Loading…
Cancel
Save