Skip to content

Commit 33224b0

Browse files
committed
lazybox: update gki subcommand
1 parent 52e600d commit 33224b0

4 files changed

Lines changed: 345 additions & 12 deletions

File tree

lazybox/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ dependencies {
2020
implementation("org.apache.commons:commons-exec:1.3")
2121
implementation("com.fasterxml.jackson.core:jackson-annotations:2.14.0")
2222
implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0")
23+
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.0")
2324
implementation(project(mapOf("path" to ":helper")))
2425
implementation("org.slf4j:slf4j-api:2.0.9")
2526
implementation("org.apache.commons:commons-compress:1.26.0")
2627
implementation("com.github.freva:ascii-table:1.8.0")
2728
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
29+
implementation("com.squareup.okhttp3:okhttp:4.12.0")
30+
implementation("com.squareup.okio:okio:3.9.0")
2831
// Use the Kotlin JUnit 5 integration.
2932
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
3033
// Use the JUnit 5 integration.
@@ -57,3 +60,7 @@ tasks.named<Test>("test") {
5760
// Use JUnit Platform for unit tests.
5861
useJUnitPlatform()
5962
}
63+
64+
tasks.named<JavaExec>("run") {
65+
standardInput = System.`in`
66+
}

lazybox/src/main/kotlin/cfig/lazybox/AMS.kt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,15 @@ class AMS {
9999
log.info(text)
100100
val lines = text.trim().split("\n") // Split lines
101101
val regex = Regex("""^ +Proc #\d+: (.*?) +oom: max=\d+ curRaw=(-?\d+) setRaw=(-?\d+) cur=(-?\d+) set=(-?\d+)""") // Match relevant parts
102-
val results = lines.mapNotNull { line ->
103-
val matchResult = regex.matchEntire(line) ?: return@mapNotNull null
104-
val groups = matchResult.groups
105-
// Extract data from groups
106-
val packageName = groups[1]?.value ?: ""
107-
val oomCurValue = groups[2]?.value?.toIntOrNull() ?: 0
108-
val status = groups[3]?.value ?: ""
109-
110-
// Create the Any array
111-
arrayOf(packageName, oomCurValue, status)
112-
log.info("$packageName -> $oomCurValue -> $status")
102+
lines.forEach { line ->
103+
regex.matchEntire(line)?.let { matchResult ->
104+
val groups = matchResult.groups
105+
// Extract data from groups
106+
val packageName = groups[1]?.value ?: ""
107+
val oomCurValue = groups[2]?.value?.toIntOrNull() ?: 0
108+
val status = groups[3]?.value ?: ""
109+
log.info("$packageName -> $oomCurValue -> $status")
110+
}
113111
}
114112
}
115113

lazybox/src/main/kotlin/cfig/lazybox/App.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ fun main(args: Array<String>) {
2222
println("Usage: args: (Array<String>) ...")
2323
println(" or: function [arguments]...")
2424
println("\nCurrently defined functions:")
25-
println("\tcpuinfo sysinfo sysstat pidstat bootchart thermal_info compiledb")
25+
println("\tcpuinfo gki sysinfo sysstat pidstat bootchart thermal_info compiledb")
2626
println("\nCommand Usage:")
2727
println("bootchart: generate Android bootchart")
28+
println("gki : interactive GKI JSON downloader/parser, or process GKI modules from <dir>")
2829
println("pidstat : given a pid, profile its CPU usage")
2930
println("tracecmd : analyze trace-cmd report")
3031
println("cpuinfo : get cpu info from /sys/devices/system/cpu/")
@@ -49,6 +50,9 @@ fun main(args: Array<String>) {
4950
)
5051
log.info("cpuinfo.json is ready")
5152
}
53+
if (args[0] == "gki") {
54+
Gki.run(args.drop(1).toTypedArray())
55+
}
5256
if (args[0] == "sysinfo") {
5357
SysInfo().run()
5458
}
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
package cfig.lazybox
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
4+
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
5+
import com.fasterxml.jackson.module.kotlin.readValue
6+
import java.io.File
7+
import okhttp3.OkHttpClient
8+
import okhttp3.Request
9+
import java.util.concurrent.TimeUnit
10+
import java.security.MessageDigest
11+
import kotlin.io.path.createTempDirectory
12+
import kotlin.system.exitProcess
13+
14+
@JsonIgnoreProperties(ignoreUnknown = true)
15+
data class GkiBuild(
16+
val name: String,
17+
val branches: List<Branch>
18+
)
19+
20+
@JsonIgnoreProperties(ignoreUnknown = true)
21+
data class Branch(
22+
val name: String,
23+
val kernel_version: String,
24+
val releases: List<Release>
25+
)
26+
27+
@JsonIgnoreProperties(ignoreUnknown = true)
28+
data class Release(
29+
val tag: String,
30+
val date: String,
31+
val sha1: String,
32+
val kernel_bid: String
33+
)
34+
35+
fun printHelp() {
36+
println("""
37+
Usage:
38+
kotlinc -script gki.kts # Run interactive GKI JSON downloader/parser
39+
kotlinc -script gki.kts <dir> # Process GKI modules from input directory
40+
kotlinc -script gki.kts -h # Show this help message
41+
""".trimIndent())
42+
}
43+
44+
fun runGkiJsonLogic() {
45+
println("Running GKI JSON logic...")
46+
val todoFile = File("to-do.txt")
47+
48+
val branchesMap = listOf(
49+
"android14-5_15" to "https://source.android.com/static/docs/core/architecture/kernel/gki-android14-5_15-release-builds.json",
50+
"android14-6_1" to "https://source.android.com/static/docs/core/architecture/kernel/gki-android14-6_1-release-builds.json",
51+
"android15-6_6" to "https://source.android.com/static/docs/core/architecture/kernel/gki-android15-6_6-release-builds.json",
52+
"android16-6_12" to "https://source.android.com/static/docs/core/architecture/kernel/gki-android16-6_12-release-builds.json"
53+
)
54+
55+
println("Select a GKI branch:")
56+
branchesMap.forEachIndexed { index, (name, _) ->
57+
println("${index + 1}. $name")
58+
}
59+
60+
print("Enter choice (1-${branchesMap.size}) [Default: 1]: ")
61+
val input = readlnOrNull()
62+
val choice = if (input.isNullOrBlank()) 1 else input.toIntOrNull()
63+
64+
if (choice == null || choice !in 1..branchesMap.size) {
65+
println("Invalid choice.")
66+
return
67+
}
68+
69+
val (branchName, url) = branchesMap[choice - 1]
70+
val jsonFileName = "gki-$branchName-release-builds.json"
71+
val file = File(jsonFileName)
72+
73+
if (!file.exists()) {
74+
println("File $jsonFileName not found, downloading from $url.")
75+
try {
76+
val client = OkHttpClient()
77+
val request = Request.Builder().url(url).build()
78+
client.newCall(request).execute().use { response ->
79+
if (!response.isSuccessful) throw Exception("Request failed: $response")
80+
file.writeText(response.body!!.string())
81+
}
82+
println("Download complete.")
83+
} catch (e: Exception) {
84+
println("Download failed: ${e.message}")
85+
return
86+
}
87+
}
88+
89+
val content = file.readText()
90+
val jsonString: String
91+
val jsonStartMarker = "<code translate=\"no\" dir=\"ltr\">"
92+
val startIndex = content.indexOf(jsonStartMarker)
93+
94+
if (startIndex != -1) {
95+
val fromStart = content.substring(startIndex + jsonStartMarker.length)
96+
jsonString = fromStart.substringBefore("</code>").trim()
97+
} else if (content.trim().startsWith("{")) {
98+
jsonString = content.trim()
99+
} else {
100+
println("Error: JSON data not found in file.")
101+
return
102+
}
103+
104+
val mapper = jacksonObjectMapper()
105+
val gkiBuild: GkiBuild
106+
try {
107+
gkiBuild = mapper.readValue(jsonString)
108+
} catch (e: Exception) {
109+
println("JSON parsing failed: ${e.message}")
110+
return
111+
}
112+
113+
val expectedNameFromFile = jsonFileName.substringAfter("gki-").substringBefore("-release-builds.json")
114+
if (gkiBuild.name.replace('_', '.') != expectedNameFromFile.replace('_', '.')) {
115+
println("Error: JSON name ('${gkiBuild.name}') does not match expected name from filename ('$expectedNameFromFile').")
116+
return
117+
}
118+
119+
println("\nFound ${gkiBuild.branches.size} branches. Please select one:")
120+
gkiBuild.branches.forEachIndexed { index, branch ->
121+
println("${index + 1}. ${branch.name}")
122+
}
123+
124+
print("Enter choice (1-${gkiBuild.branches.size}): ")
125+
val branchChoice = readlnOrNull()?.toIntOrNull()
126+
if (branchChoice == null || branchChoice !in 1..gkiBuild.branches.size) {
127+
println("Invalid choice.")
128+
return
129+
}
130+
131+
val selectedBranch = gkiBuild.branches[branchChoice - 1]
132+
println("\n--- Branch Info ---\n${mapper.writerWithDefaultPrettyPrinter().writeValueAsString(selectedBranch)}")
133+
134+
if (selectedBranch.releases.isEmpty()) {
135+
println("\nNo releases available for this branch.")
136+
return
137+
}
138+
139+
println("\nFound ${selectedBranch.releases.size} releases. Please select one:")
140+
selectedBranch.releases.forEachIndexed { index, release ->
141+
println("${index + 1}. ${release.tag}")
142+
}
143+
144+
print("Enter choice (1-${selectedBranch.releases.size}): ")
145+
val releaseChoice = readlnOrNull()?.toIntOrNull()
146+
if (releaseChoice == null || releaseChoice !in 1..selectedBranch.releases.size) {
147+
println("Invalid choice.")
148+
return
149+
}
150+
151+
val selectedRelease = selectedBranch.releases[releaseChoice - 1]
152+
val tempVal = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(selectedRelease)
153+
println("\n--- Release Info ---\n$tempVal")
154+
155+
// --- URL Generation Logic ---
156+
println("\nGenerating URL list...")
157+
158+
val kernelBid = selectedRelease.kernel_bid
159+
160+
val urlsToGenerate = listOf(
161+
// Debug
162+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_debug_aarch64/latest/boot.img",
163+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_debug_aarch64/latest/system_dlkm_staging_archive.tar.gz",
164+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_debug_aarch64/latest/vmlinux.symvers",
165+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_debug_aarch64/latest/view/BUILD_INFO",
166+
// Release
167+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_aarch64/latest/signed/certified-boot-img-$kernelBid.tar.gz",
168+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_aarch64/latest/system_dlkm_staging_archive.tar.gz",
169+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_aarch64/latest/vmlinux.symvers",
170+
"https://ci.android.com/builds/submitted/$kernelBid/kernel_aarch64/latest/view/BUILD_INFO"
171+
)
172+
173+
todoFile.writeText(urlsToGenerate.joinToString("\n"))
174+
println("URL list successfully written to ${todoFile.path}")
175+
println("\nScript execution complete.")
176+
File("bid_$kernelBid").mkdir()
177+
}
178+
179+
// --- Logic from gki2.kts ---
180+
fun runGki2Logic(inputPath: String) {
181+
println("Running GKI Modules Processing logic...")
182+
val inputDir = File(inputPath)
183+
184+
if (!inputDir.exists() || !inputDir.isDirectory) {
185+
System.err.println("Error: Input directory '$inputPath' does not exist or is not a directory.")
186+
exitProcess(1)
187+
}
188+
189+
println("Input directory: ${inputDir.absolutePath}")
190+
191+
val outputDir = File("gki_modules")
192+
println("Cleaning up and creating output directory: ${outputDir.absolutePath}")
193+
if (outputDir.exists()) {
194+
outputDir.deleteRecursively()
195+
}
196+
outputDir.mkdir()
197+
198+
// --- Boot Image Handling ---
199+
val certifiedBootImg = inputDir.walk().firstOrNull { it.isFile && it.name.startsWith("certified-boot-img-") && it.name.endsWith(".tar.gz") }
200+
201+
if (certifiedBootImg != null) {
202+
println("Found certified boot image: ${certifiedBootImg.name}")
203+
val tempBootDir = createTempDirectory("boot_img_extraction").toFile()
204+
println("Extracting ${certifiedBootImg.name} to ${tempBootDir.absolutePath}...")
205+
"tar -xzf ${certifiedBootImg.absolutePath} -C ${tempBootDir.absolutePath}".runCommand()
206+
207+
val bootImgInTar = tempBootDir.resolve("boot.img")
208+
if (!bootImgInTar.exists()) {
209+
System.err.println("Error: boot.img not found inside ${certifiedBootImg.name}")
210+
tempBootDir.deleteRecursively()
211+
exitProcess(1)
212+
}
213+
214+
val newBootImg = outputDir.resolve("boot-5.15.img")
215+
println("Copying and renaming boot.img to ${newBootImg.absolutePath}")
216+
bootImgInTar.copyTo(newBootImg, overwrite = true)
217+
tempBootDir.deleteRecursively()
218+
219+
} else {
220+
val bootImg = inputDir.resolve("boot.img")
221+
if (!bootImg.exists()) {
222+
System.err.println("Error: Neither certified-boot-img-*.tar.gz nor boot.img found in '${inputDir.absolutePath}'")
223+
exitProcess(1)
224+
}
225+
println("Found boot.img directly in input directory.")
226+
val newBootImg = outputDir.resolve("boot-5.15.img")
227+
println("Copying boot.img to ${newBootImg.absolutePath}")
228+
bootImg.copyTo(newBootImg, overwrite = true)
229+
}
230+
231+
232+
// --- Kernel Modules Handling ---
233+
val systemDlkm = inputDir.resolve("system_dlkm_staging_archive.tar.gz")
234+
235+
if (!systemDlkm.exists()) {
236+
System.err.println("Error: system_dlkm_staging_archive.tar.gz not found in '${inputDir.absolutePath}'")
237+
exitProcess(1)
238+
}
239+
240+
println("Found kernel modules archive: ${systemDlkm.name}")
241+
val tempModulesDir = createTempDirectory("modules_extraction").toFile()
242+
println("Extracting ${systemDlkm.name} to ${tempModulesDir.absolutePath}...")
243+
"tar -xzf ${systemDlkm.absolutePath} -C ${tempModulesDir.absolutePath}".runCommand()
244+
245+
val modulesDir = tempModulesDir.resolve("flatten/lib/modules")
246+
if (!modulesDir.exists() || !modulesDir.isDirectory) {
247+
System.err.println("Error: 'flatten/lib/modules' directory not found inside the extracted archive.")
248+
tempModulesDir.deleteRecursively()
249+
exitProcess(1)
250+
}
251+
252+
println("Copying .ko files to ${outputDir.absolutePath}...")
253+
modulesDir.walk().forEach {
254+
if (it.isFile && it.extension == "ko") {
255+
it.copyTo(outputDir.resolve(it.name), overwrite = true)
256+
}
257+
}
258+
tempModulesDir.deleteRecursively()
259+
println("Finished copying kernel modules.")
260+
261+
262+
// --- MD5 Sum Generation ---
263+
println("Generating md5sum.txt...")
264+
val md5sumFile = outputDir.resolve("md5sum.txt")
265+
val md5sums = mutableListOf<String>()
266+
outputDir.walk().sortedBy { it.name }.forEach {
267+
if (it.isFile && it.name != "md5sum.txt") {
268+
val md5 = it.md5()
269+
md5sums.add("$md5 ${it.name}")
270+
println(" - Calculated MD5 for ${it.name}")
271+
}
272+
}
273+
md5sumFile.writeText(md5sums.joinToString("\n"))
274+
println("md5sum.txt created successfully.")
275+
276+
println("\nDone! All files are in '${outputDir.absolutePath}'.")
277+
}
278+
279+
// --- Helpers ---
280+
fun String.runCommand(workingDir: File = File(".")) {
281+
val process = ProcessBuilder(*split(" ").toTypedArray())
282+
.directory(workingDir)
283+
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
284+
.redirectError(ProcessBuilder.Redirect.INHERIT)
285+
.start()
286+
if (process.waitFor() != 0) {
287+
throw RuntimeException("Failed to run command: $this")
288+
}
289+
}
290+
291+
fun File.md5(): String {
292+
val md = MessageDigest.getInstance("MD5")
293+
this.inputStream().use {
294+
val buffer = ByteArray(8192)
295+
var bytesRead = it.read(buffer)
296+
while (bytesRead != -1) {
297+
md.update(buffer, 0, bytesRead)
298+
bytesRead = it.read(buffer)
299+
}
300+
}
301+
return md.digest().joinToString("") { "%02x".format(it) }
302+
}
303+
304+
class Gki {
305+
companion object {
306+
fun run(args: Array<String>) {
307+
// --- Main Entry Point ---
308+
if (args.isNotEmpty() && (args[0] == "-h" || args[0] == "--help")) {
309+
printHelp()
310+
exitProcess(0)
311+
}
312+
313+
if (args.isEmpty()) {
314+
runGkiJsonLogic()
315+
} else if (args.size == 1) {
316+
runGki2Logic(args[0])
317+
} else {
318+
println("Invalid arguments.")
319+
printHelp()
320+
exitProcess(1)
321+
}
322+
}
323+
}
324+
}

0 commit comments

Comments
 (0)