lazybox: add lazybox

pull/140/head
cfig 1 year ago
parent d558c77755
commit 7c8e997ef9
No known key found for this signature in database
GPG Key ID: B104C307F0FDABB7

@ -267,8 +267,8 @@ class Helper {
log.error("fail to execute [$cmd]")
ret = false
}
log.debug("stdout [$outStream]")
log.debug("stderr [$errStream]")
log.debug("stdout [{}]", outStream)
log.debug("stderr [{}]", errStream)
return arrayOf(ret, outStream.toByteArray(), errStream.toByteArray())
}
@ -284,8 +284,8 @@ class Helper {
} catch (e: ExecuteException) {
log.error("fail to execute [$cmd]")
}
log.debug("stdout [$outStream]")
log.debug("stderr [$errStream]")
log.debug("stdout [{}]", outStream)
log.debug("stderr [{}]", errStream)
return arrayOf(outStream.toByteArray(), errStream.toByteArray())
}
@ -397,6 +397,43 @@ class Helper {
return readFully(data, coordinate.first, coordinate.second)
}
fun adbCmd(cmd: String): String {
val outputStream = ByteArrayOutputStream()
val exec = DefaultExecutor()
exec.streamHandler = PumpStreamHandler(outputStream)
val cmdline = "adb shell $cmd"
log.info(cmdline)
exec.execute(CommandLine.parse(cmdline))
//println(outputStream)
return outputStream.toString().trim()
}
fun str2range(str: String): List<IntRange> {
return str.trim().let { ranges ->
val ret = mutableListOf<IntRange>()
ranges.split(",").forEach { rangeItem ->
rangeItem.split("-").let { v ->
when (v.size) {
1 -> {
if (v.get(0).isNotEmpty()) {
ret.add(IntRange(v.get(0).toInt(), v.get(0).toInt()))
} else {
//pass
}
}
2 -> {
ret.add(IntRange(v.get(0).toInt(), v.get(1).toInt()))
}
else -> {
throw IllegalArgumentException("invalid range: [$rangeItem]")
}
}
}
}
ret
}
}
private val log = LoggerFactory.getLogger("Helper")
}
}

@ -17,6 +17,8 @@ package cfig.helper
import cc.cfig.io.Struct
import cfig.helper.Helper.Companion.check_call
import cfig.helper.Helper.Companion.check_output
import org.apache.commons.compress.archivers.tar.TarArchiveEntry
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
import org.apache.commons.compress.archivers.zip.*
import org.apache.commons.compress.compressors.CompressorOutputStream
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream
@ -38,6 +40,7 @@ import java.io.*
import java.net.URI
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@ -590,5 +593,23 @@ class ZipHelper {
}
log.info("compress(bzip2) done: $compressedFile")
}
fun makeTar(compressedFile: String, srcDir: String) {
FileOutputStream(compressedFile).use { fos ->
val appendTarEntry: (TarArchiveOutputStream, String) -> Unit = { tarOut, entry ->
tarOut.putArchiveEntry(TarArchiveEntry(File(entry)))
tarOut.write(File(entry).readBytes())
tarOut.closeArchiveEntry()
}
TarArchiveOutputStream(fos).use { to ->
Files.walk(Paths.get(srcDir))
.filter { Files.isRegularFile(it) }
.forEach {
log.info("tar << $it")
appendTarEntry(to, it.toString())
}
}
}
}
} // end-of-companion
}

@ -0,0 +1,47 @@
/*
* This file was generated by the Gradle 'init' task.
*
* 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.0"
application
}
repositories {
mavenCentral()
}
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")))
// Use the Kotlin JUnit 5 integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
// Use the JUnit 5 integration.
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}
application {
// Define the main class for the application.
mainClass.set("cfig.lazybox.AppKt")
}
tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

@ -0,0 +1,33 @@
package cfig.lazybox
import cfig.lazybox.sysinfo.CpuInfo
import cfig.lazybox.sysinfo.SysInfo
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import java.io.File
import kotlin.system.exitProcess
class App
fun main(args: Array<String>) {
val log = LoggerFactory.getLogger(App::class.java)
if (args.isEmpty()) {
println("Usage: args: (Array<String>) ...")
println(" or: function [arguments]...")
println("\nCurrently defined functions:")
println("\tcpuinfo sysinfo")
exitProcess(0)
}
if (args.get(0) == "cpuinfo") {
val ret = CpuInfo.construct()
File("cpuinfo.json").writeText(
ObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(ret)
)
log.info("cpuinfo.json is ready")
}
if (args.get(0) == "sysinfo") {
SysInfo().run()
}
}

@ -0,0 +1,211 @@
package cfig.lazybox.sysinfo
import cfig.helper.Helper
import org.apache.commons.exec.ExecuteException
import org.slf4j.LoggerFactory
class CpuInfo(
val cpuinfo: RawCores,
val policies: List<CpuFreqPolicy>,
val onDemandGovernor: OndemandGovernor? = null,
val conservativeGovernor: ConservativeGovernor? = null,
val schedutilGovernor: SchedutilGovernor? = null,
) {
companion object {
fun construct(): CpuInfo {
val rawCores = RawCores.construct()
rawCores.toCores()
val policies = CpuFreqPolicy.construct()
val onDemand = if (policies.any { it.scaling_governor == "ondemand" }) {
OndemandGovernor.construct()
} else {
null
}
val conservative = if (policies.any { it.scaling_governor == "conservative" }) {
try {
ConservativeGovernor.construct()
} catch (_: ExecuteException) {
ConservativeGovernor.construct("/sys/devices/system/cpu/cpu0/cpufreq/conservative")
}
} else {
null
}
val sched = if (policies.any { it.scaling_governor == "schedutil" }) {
SchedutilGovernor.construct()
} else {
null
}
return CpuInfo(rawCores, policies, onDemand, conservative, sched)
}
val getAdbCmdResult: (String, Boolean) -> String? = { cmd, check ->
Helper.powerRun2(cmd, null).let {
if (it[0] as Boolean) {
String(it[1] as ByteArray)
} else {
if (check) {
log.warn(String(it[1] as ByteArray))
throw RuntimeException(String(it[2] as ByteArray))
} else {
log.warn(String(it[1] as ByteArray))
log.warn(String(it[2] as ByteArray))
}
null
}
}
}
private val log = LoggerFactory.getLogger(CpuInfo::class.java)
}
data class SchedutilGovernor(
var rate_limit_us: Long = 0,
) {
companion object {
fun construct(): SchedutilGovernor {
val prefix = "/sys/devices/system/cpu/cpufreq/schedutil"
return SchedutilGovernor(Helper.adbCmd("cat $prefix/rate_limit_us").toLong())
}
}
}
data class ConservativeGovernor(
var down_threshold: Long = 0,
var up_threshold: Long = 0,
var ignore_nice_load: Long = 0,
var sampling_rate: Long = 0,
var sampling_down_factor: Long = 0,
var freq_step: Long = 0,
) {
companion object {
fun construct(prefix: String = "/sys/devices/system/cpu/cpufreq/conservative"): ConservativeGovernor {
return ConservativeGovernor(
Helper.adbCmd("cat $prefix/down_threshold").toLong(),
Helper.adbCmd("cat $prefix/up_threshold").toLong(),
Helper.adbCmd("cat $prefix/ignore_nice_load").toLong(),
Helper.adbCmd("cat $prefix/sampling_rate").toLong(),
Helper.adbCmd("cat $prefix/sampling_down_factor").toLong(),
Helper.adbCmd("cat $prefix/freq_step").toLong(),
)
}
}
}
data class OndemandGovernor(
var ignore_nice_load: Long? = null,
var io_is_busy: Long? = null,
var min_sampling_rate: Long? = null,
var powersave_bias: Long? = null,
var sampling_down_factor: Long? = null,
var sampling_rate: Long? = null,
var up_threshold: Long? = null,
) {
companion object {
fun construct(): OndemandGovernor {
val prefix = "/sys/devices/system/cpu/cpufreq/ondemand"
return OndemandGovernor(
Helper.adbCmd("cat $prefix/ignore_nice_load").toLong(),
Helper.adbCmd("cat $prefix/io_is_busy").toLong(),
Helper.adbCmd("cat $prefix/min_sampling_rate").toLong(),
Helper.adbCmd("cat $prefix/powersave_bias").toLong(),
Helper.adbCmd("cat $prefix/sampling_down_factor").toLong(),
Helper.adbCmd("cat $prefix/sampling_rate").toLong(),
Helper.adbCmd("cat $prefix/up_threshold").toLong(),
)
}
}
}
data class RawCores(
var possible: String,
var present: String,
var online: String,
var offline: String
) {
companion object {
fun construct(): RawCores {
return RawCores(
Helper.adbCmd("cat /sys/devices/system/cpu/possible"),
Helper.adbCmd("cat /sys/devices/system/cpu/present"),
Helper.adbCmd("cat /sys/devices/system/cpu/online"),
Helper.adbCmd("cat /sys/devices/system/cpu/offline"),
)
}
}
fun toCores(): Cores {
return Cores(
Helper.str2range(possible).get(0),
Helper.str2range(present).get(0),
Helper.str2range(online),
Helper.str2range(offline),
)
}
}
data class Cores(
var possible: IntRange = 0..0,
var present: IntRange = 0..0,
var online: List<IntRange> = listOf(),
var offline: List<IntRange> = listOf(),
)
class CpuFreqPolicy(
var name: String = "policyX",
var affected_cpus: String = "",
var related_cpus: String = "",
//cpuinfo
var cpuinfo_max_freq: String = "",
var cpuinfo_min_freq: String = "",
var cpuinfo_cur_freq: String? = null,
var cpuinfo_transition_latency: String = "",
//freq
var scaling_available_frequencies: String = "",
var scaling_max_freq: String? = null,
var scaling_min_freq: String = "",
var scaling_cur_freq: String = "",
//governor
var scaling_governor: String? = null,
var scaling_driver: String = "",
var scaling_available_governors: String = "",
var scaling_setspeed: String? = null,
) {
companion object {
fun construct(): List<CpuFreqPolicy> {
val ret = mutableListOf<CpuFreqPolicy>()
val policies = Helper.adbCmd("ls /sys/devices/system/cpu/cpufreq")
policies.split("\n").forEach {
if (it.matches("^policy\\d$".toRegex())) {
log.info("Found: $it")
ret.add(construct(it.trim()))
}
}
return ret
}
private fun construct(inName: String): CpuFreqPolicy {
val prefix = "/sys/devices/system/cpu/cpufreq"
return CpuFreqPolicy(
name = inName,
affected_cpus = Helper.adbCmd("cat $prefix/$inName/affected_cpus"),
related_cpus = Helper.adbCmd("cat $prefix/$inName/related_cpus"),
scaling_governor = getAdbCmdResult("cat $prefix/$inName/scaling_governor", false), //HMOS
cpuinfo_cur_freq = getAdbCmdResult("cat $prefix/$inName/cpuinfo_cur_freq", false),
scaling_available_frequencies = Helper.adbCmd("cat $prefix/$inName/scaling_available_frequencies"),
scaling_max_freq = getAdbCmdResult("cat $prefix/$inName/scaling_max_freq", false),
cpuinfo_max_freq = Helper.adbCmd("cat $prefix/$inName/cpuinfo_max_freq"),
scaling_available_governors = Helper.adbCmd("cat $prefix/$inName/scaling_available_governors"),
scaling_min_freq = Helper.adbCmd("cat $prefix/$inName/scaling_min_freq"),
cpuinfo_min_freq = Helper.adbCmd("cat $prefix/$inName/cpuinfo_min_freq"),
scaling_cur_freq = Helper.adbCmd("cat $prefix/$inName/scaling_cur_freq"),
scaling_setspeed = getAdbCmdResult("cat $prefix/$inName/scaling_setspeed", false),
cpuinfo_transition_latency = Helper.adbCmd("cat $prefix/$inName/cpuinfo_transition_latency"),
scaling_driver = Helper.adbCmd("cat $prefix/$inName/scaling_driver")
)
}
}
}
}

@ -0,0 +1,101 @@
package cfig.lazybox.sysinfo
import cfig.helper.Helper
import cfig.helper.Helper.Companion.check_call
import cfig.helper.Helper.Companion.deleteIfExists
import cfig.helper.ZipHelper
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
class SysInfo {
private fun runAndWrite(cmd: String, outStream: OutputStream, check: Boolean) {
Helper.powerRun2(cmd, null).let {
if (it[0] as Boolean) {
outStream.write(it[1] as ByteArray)
} else {
if (check) {
log.warn(String(it[1] as ByteArray))
throw RuntimeException(String(it[2] as ByteArray))
} else {
log.warn(String(it[1] as ByteArray))
log.warn(String(it[2] as ByteArray))
}
}
}
}
private 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()
"sysinfo.tar".deleteIfExists()
val prefix = "sysinfo"
File("sysinfo").let {
if (it.exists()) {
log.info("purging directory sysinfo/ ...")
it.deleteRecursively()
}
}
File(prefix).mkdir()
FileOutputStream("$prefix/0_prop").use {
runAndWrite("adb shell getprop", it, true)
}
FileOutputStream("$prefix/1_partitions").use { file ->
runAndWrite("adb shell cat /proc/partitions", file, false) //HMOS
runAndWrite("adb shell ls -l /dev/block/by-name", file, false)
}
FileOutputStream("$prefix/2_mount").use { file ->
runAndWrite("adb shell mount", file, true)
}
FileOutputStream("$prefix/3_kernel_cmdline").use { file ->
file.write("[version]\n".toByteArray())
runAndWrite("adb shell cat /proc/version", file, true)
file.write("\n[cmdline]\n".toByteArray())
runAndWrite("adb shell cat /proc/cmdline", file, false)
file.write("\n[bootconfig]\n".toByteArray())
runAndWrite("adb shell cat /proc/bootconfig", file, false)
// cpuinfo
file.write("\n[cpuinfo]\n".toByteArray())
runAndWrite("adb shell cat /proc/cpuinfo", file, false)
// meminfo
file.write("\n[meminfo]\n".toByteArray())
runAndWrite("adb shell cat /proc/meminfo", file, false)
// defconfig
file.write("\n[defconfig]\n".toByteArray())
"adb pull /proc/config.gz".check_call()
ZipHelper.zcat("config.gz", "config")
file.write(File("config").readBytes())
File("config.gz").deleteOnExit()
File("config").deleteOnExit()
}
"adb pull /proc/device-tree".check_call(prefix)
Files.move(Paths.get("$prefix/device-tree"), Paths.get("$prefix/device_tree"))
makeTar("sysinfo.tar.xz", "sysinfo")
File("sysinfo").deleteRecursively()
}
companion object {
private val log = LoggerFactory.getLogger(SysInfo::class.java)
}
}

@ -0,0 +1,21 @@
<configuration debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="SOME_ERRORS" class="ch.qos.logback.core.FileAppender">
<file>uiderrors</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<logger name="uiderrors" level="DEBUG" additivity="false">
<appender-ref ref="SOME_ERRORS"/>
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

@ -0,0 +1,14 @@
package cfig.lazybox.sysinfo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import java.io.File
class CpuInfoTest {
@Test
fun probeInfo() {
CpuInfo.construct()
}
}

@ -5,3 +5,4 @@ include("aosp:libavb1.1")
include("aosp:libavb1.2")
include("avbImpl")
include("helper")
include("lazybox")

@ -1,71 +0,0 @@
import java.io.*
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.DefaultExecutor
import org.apache.commons.exec.PumpStreamHandler
import com.fasterxml.jackson.databind.ObjectMapper
fun adbCmd(cmd: String): String {
val outputStream = ByteArrayOutputStream()
val exec = DefaultExecutor()
exec.streamHandler = PumpStreamHandler(outputStream)
val cmdline = "adb shell $cmd"
//println(cmdline)
exec.execute(CommandLine.parse(cmdline))
//println(outputStream)
return outputStream.toString().trim()
}
val cpufreqDir = "/sys/devices/system/cpu/cpufreq/policy0"
val interactGov = "/sys/devices/system/cpu/cpufreq/interactive"
val scaling_governor = adbCmd("cat $cpufreqDir/scaling_governor")
val avail_governer = adbCmd("cat $cpufreqDir/scaling_available_governors")
val avail_freq = adbCmd("cat $cpufreqDir/scaling_available_frequencies")
println("Available governers: " + avail_governer)
println("Available frequency: " + avail_freq)
val scaleMax = adbCmd("cat $cpufreqDir/scaling_max_freq")
val scaleMin = adbCmd("cat $cpufreqDir/scaling_min_freq")
println("scaling_X_freq: [$scaleMin, $scaleMax]")
println("Current governer: $scaling_governor")
fun getInteractValue(k: String): String {
return adbCmd("cat $interactGov/$k")
}
fun getInteractInt(k: String): Int {
return Integer.decode(adbCmd("cat $interactGov/$k"))
}
data class Boost(
var boost: Int,
var boostpulse_duration_ms: Int)
val boostInfo = Boost(getInteractInt("boost"), getInteractInt("boostpulse_duration") / 1000)
data class HiSpeed(
var load: Int,
var above_delay_Ms: Int,
var freq_GHz: Double)
val hiSpeedInfo = HiSpeed(
getInteractInt("go_hispeed_load"),
getInteractInt("above_hispeed_delay") / 1000,
getInteractInt("hispeed_freq") / 1000000.0)
data class InteractiveGov(
var target_loads: Int,
var boost: Boost,
var hiSpeed: HiSpeed,
var minSampleTimeMs: Int,
var timerRateMs: Int,
var timerSlackMs: Int,
var io_is_busy: Int)
val info = InteractiveGov(
getInteractInt("target_loads"),
boostInfo,
hiSpeedInfo,
getInteractInt("min_sample_time") / 1000,
getInteractInt("timer_rate") / 1000,
getInteractInt("timer_slack") / 1000,
getInteractInt("io_is_busy"))
println(ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(info))

@ -1,65 +0,0 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
import os, sys, subprocess, gzip, logging, shutil, tarfile, os.path
#########################
## globals
#########################
log = logging.getLogger("|")
log.setLevel(logging.DEBUG)
consoleHandler = logging.StreamHandler(sys.stdout)
consoleHandler.setFormatter(logging.Formatter(fmt='%(asctime)s %(levelname)-8s %(name)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'))
log.addHandler(consoleHandler)
#########################
## functions
#########################
def purgeFolder(folderPath):
if os.path.exists(folderPath):
log.info("cleaning %s" % folderPath)
shutil.rmtree(folderPath)
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))
#########################
## main
#########################
log.info("adb wait-for-device ...")
subprocess.check_call("adb wait-for-device", shell = True)
subprocess.check_call("adb root", shell = True)
purgeFolder("sysinfo")
os.mkdir("sysinfo")
with open("sysinfo/0_prop", "wb") as f:
f.write(subprocess.check_output("adb shell getprop", shell = True))
with open("sysinfo/1_partitions", "wb") as f:
f.write(subprocess.check_output("adb shell cat /proc/partitions", shell = True))
f.write(subprocess.check_output("adb shell ls -l /dev/block/by-name", shell = True))
with open("sysinfo/2_mount", "wb") as f:
f.write(subprocess.check_output("adb shell mount", shell = True))
with open("sysinfo/3_kernel_cmdline", "wb") as f:
f.write(bytes("[version]\n", "utf-8"))
f.write(subprocess.check_output("adb shell cat /proc/version", shell = True))
f.write(bytes("\n[cmdline]\n", "utf-8"))
f.write(subprocess.check_output("adb shell cat /proc/cmdline", shell = True))
f.write(bytes("\n[bootconfig]\n", "utf-8"))
try:
f.write(subprocess.check_output("adb shell cat /proc/bootconfig", shell = True))
except subprocess.CalledProcessError as e:
log.warning("can not read bootconfig")
pass
subprocess.check_call("adb pull /proc/config.gz", shell = True)
with gzip.open("config.gz", "rb") as gz_file:
file_content = gz_file.read()
f.write(bytes("\n[defconfig]\n", "utf-8"))
f.write(file_content)
os.remove("config.gz")
subprocess.check_call("adb pull /proc/device-tree", cwd = "sysinfo", shell = True)
shutil.move("sysinfo/device-tree", "sysinfo/device_tree")
makeTar("sysinfo.py.tar.xz", "sysinfo")
shutil.rmtree("sysinfo")
log.info("sysinfo.py.tar.xz is ready")
Loading…
Cancel
Save