summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Makoto Onuki <omakoto@google.com> 2025-03-12 14:26:09 -0700
committer Makoto Onuki <omakoto@google.com> 2025-03-13 16:01:27 -0700
commite18b7781056a39fb65260ac87a5fc98ed28a93dd (patch)
tree8f316f6ea5fc4b695be22438c84e6182fbb24e94
parentc6af79cff3599a387961cdc98e2c8c152ce3ba21 (diff)
Update "stats" generation
- Stop hardcoding test directory path in ravenwood-stats-collector.sh - Don't count native methods kept by KeepNativeFilter as "supported" - Hide some of the "obvious" classes and methods (annotations classes, Object methods, etc etc) Flag: EXEMPT host test change only Bug: 402797626 Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s Change-Id: I0a12ec51cc6cc852bc2d1bde68021c34c2c8bb59
-rwxr-xr-xravenwood/scripts/ravenwood-stats-collector.sh53
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt33
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt62
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt7
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt50
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt28
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt5
-rw-r--r--ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt3
-rwxr-xr-xravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py2
9 files changed, 174 insertions, 69 deletions
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index b83216af95fe..c2bf8d82e272 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -29,21 +29,46 @@ mkdir -p $out_dir
mkdir -p $keep_all_dir
mkdir -p $dump_dir
-# Where the input files are.
-path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
-timestamp="$(date --iso-8601=seconds)"
+stats_checker_module="ravenwood-stats-checker"
+minfo=$OUT/module-info.json
-m() {
- ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
-}
+timestamp="$(date --iso-8601=seconds)"
-# Building this will generate the files we need.
-m ravenwood-stats-checker
+# First, use jq to get the output files from the checker module. This will be something like this:
+#
+# ---
+# out/host/linux-x86/nativetest64/ravenwood-stats-checker/framework-configinfrastructure_apis.csv
+# out/host/linux-x86/nativetest64/ravenwood-stats-checker/framework-configinfrastructure_dump.txt
+# :
+# out/host/linux-x86/nativetest64/ravenwood-stats-checker/hoststubgen_services.core_stats.csv
+# out/host/linux-x86/nativetest64/ravenwood-stats-checker/ravenwood-stats-checker
+# ---
+# Then, use grep to find the script's path (the last line in the above examle)
+script_path="$(
+ jq -r ".\"$stats_checker_module\".installed | .[]" $minfo |
+ grep '/ravenwood-stats-checker$'
+)"
+
+if [[ "$script_path" == "" ]] ; then
+ echo "Error: $stats_checker_module script not found from $minfo"
+ exit 1
+fi
+
+# This is the directory where our input files are.
+script_dir="$ANDROID_BUILD_TOP/$(dirname "$script_path")"
+
+# Clear it before (re-)buildign the script, to make sure we won't have stale files.
+rm -fr "$script_dir"
+
+# Then build it, which will also collect the input files in the same dir.
+echo "Collecting the input files..."
+m "$stats_checker_module"
# Start...
-cd $path
+echo "Files directory is: $script_dir"
+cd "$script_dir"
dump() {
local jar=$1
@@ -55,6 +80,7 @@ dump() {
collect_stats() {
local out="$1"
+ local desc="$2"
{
# Copy the header, with the first column appended.
echo -n "Jar,Generated Date,"
@@ -66,11 +92,12 @@ collect_stats() {
dump "framework-statsd" framework-statsd_stats.csv
} > "$out"
- echo "Stats CVS created at $out"
+ echo "Stats CVS created at $out$desc"
}
collect_apis() {
local out="$1"
+ local desc="$2"
{
# Copy the header, with the first column appended.
echo -n "Jar,Generated Date,"
@@ -82,12 +109,12 @@ collect_apis() {
dump "framework-statsd" framework-statsd_apis.csv
} > "$out"
- echo "API CVS created at $out"
+ echo "API CVS created at $out$desc"
}
-collect_stats $stats
-collect_apis $apis
+collect_stats $stats " (import it as 'ravenwood_stats')"
+collect_apis $apis " (import it as 'ravenwood_supported_apis')"
cp *keep_all.txt $keep_all_dir
echo "Keep all files created at:"
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
index 9045db210495..ea8c25b6833c 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
@@ -15,13 +15,24 @@
*/
package com.android.hoststubgen
+import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.getOuterClassNameFromFullClassName
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.StatsLabel
import org.objectweb.asm.Opcodes
import java.io.PrintWriter
-open class HostStubGenStats {
+/**
+ * TODO This is for the legacy API coverage stats CSV that shows how many APIs are "supported"
+ * in each class with some heuristics. We created [ApiDumper] later, which dumpps all methods
+ * with the "supported" status. We should update the coverage dashboard to use the [ApiDumper]
+ * output and remove this class, once we port all the heuristics to [ApiDumper] as well.
+ * (For example, this class ignores non-public and/or abstract methods, but [ApiDumper] shows
+ * all of them in the same way. We should probably mark them as "Boring" or maybe "Ignore"
+ * for [ApiDumper])
+ */
+open class HostStubGenStats(val classes: ClassNodes) {
data class Stats(
var supported: Int = 0,
var total: Int = 0,
@@ -30,14 +41,6 @@ open class HostStubGenStats {
private val stats = mutableMapOf<String, Stats>()
- data class Api(
- val fullClassName: String,
- val methodName: String,
- val methodDesc: String,
- )
-
- private val apis = mutableListOf<Api>()
-
fun onVisitPolicyForMethod(
fullClassName: String,
methodName: String,
@@ -45,16 +48,16 @@ open class HostStubGenStats {
policy: FilterPolicyWithReason,
access: Int
) {
- if (policy.policy.isSupported) {
- apis.add(Api(fullClassName, methodName, descriptor))
- }
-
// Ignore methods that aren't public
if ((access and Opcodes.ACC_PUBLIC) == 0) return
// Ignore methods that are abstract
if ((access and Opcodes.ACC_ABSTRACT) != 0) return
+
// Ignore methods where policy isn't relevant
- if (policy.isIgnoredForStats) return
+ val statsLabel = policy.statsLabel
+ if (statsLabel == StatsLabel.Ignored) return
+
+ val cn = classes.findClass(fullClassName) ?: return
val packageName = getPackageNameFromFullClassName(fullClassName)
val className = getOuterClassNameFromFullClassName(fullClassName)
@@ -70,7 +73,7 @@ open class HostStubGenStats {
val packageStats = stats.getOrPut(packageName) { Stats() }
val classStats = packageStats.children.getOrPut(className) { Stats() }
- if (policy.policy.isSupported) {
+ if (statsLabel == StatsLabel.Supported) {
packageStats.supported += 1
classStats.supported += 1
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
index 5e4e70f0cbaa..bb8cdccafaa6 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -25,6 +25,7 @@ import com.android.hoststubgen.csvEscape
import com.android.hoststubgen.filters.FilterPolicy
import com.android.hoststubgen.filters.FilterPolicyWithReason
import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.filters.StatsLabel
import com.android.hoststubgen.log
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
@@ -44,7 +45,10 @@ class ApiDumper(
val descriptor: String,
)
- private val javaStandardApiPolicy = FilterPolicy.Keep.withReason("Java standard API")
+ private val javaStandardApiPolicy = FilterPolicy.Keep.withReason(
+ "Java standard API",
+ StatsLabel.Supported,
+ )
private val shownMethods = mutableSetOf<MethodKey>()
@@ -53,7 +57,7 @@ class ApiDumper(
*/
fun dump() {
pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
- ",Supported,Policy,Reason\n")
+ ",Supported,Policy,Reason,SupportedLabel\n")
classes.forEach { classNode ->
shownMethods.clear()
@@ -68,23 +72,36 @@ class ApiDumper(
methodClassName: String,
methodName: String,
methodDesc: String,
- policy: FilterPolicyWithReason,
+ classPolicy: FilterPolicyWithReason,
+ methodPolicy: FilterPolicyWithReason,
) {
+ if (methodPolicy.statsLabel == StatsLabel.Ignored) {
+ return
+ }
+ // Label hack -- if the method is supported, but the class is boring, then the
+ // method is boring too.
+ var methodLabel = methodPolicy.statsLabel
+ if (methodLabel == StatsLabel.SupportedButBoring
+ && classPolicy.statsLabel == StatsLabel.SupportedButBoring) {
+ methodLabel = classPolicy.statsLabel
+ }
+
pw.printf(
- "%s,%s,%d,%s,%s,%s,%d,%s,%s\n",
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s,%s\n",
csvEscape(classPackage),
csvEscape(className),
if (isSuperClass) { 1 } else { 0 },
csvEscape(methodClassName),
csvEscape(methodName),
csvEscape(methodDesc),
- if (policy.policy.isSupported) { 1 } else { 0 },
- policy.policy,
- csvEscape(policy.reason),
+ methodLabel.statValue,
+ methodPolicy.policy,
+ csvEscape(methodPolicy.reason),
+ methodLabel,
)
}
- private fun isDuplicate(methodName: String, methodDesc: String): Boolean {
+ private fun shownAlready(methodName: String, methodDesc: String): Boolean {
val methodKey = MethodKey(methodName, methodDesc)
if (shownMethods.contains(methodKey)) {
@@ -98,6 +115,12 @@ class ApiDumper(
dumpClass: ClassNode,
methodClass: ClassNode,
) {
+ val classPolicy = filter.getPolicyForClass(dumpClass.name)
+ if (classPolicy.statsLabel == StatsLabel.Ignored) {
+ return
+ }
+ log.d("Class ${dumpClass.name} -- policy $classPolicy")
+
val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
@@ -112,23 +135,23 @@ class ApiDumper(
}
}
// If we already printed the method from a subclass, don't print it.
- if (isDuplicate(method.name, method.desc)) {
+ if (shownAlready(method.name, method.desc)) {
return@forEach
}
- val policy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
+ val methodPolicy = filter.getPolicyForMethod(methodClass.name, method.name, method.desc)
// Let's skip "Remove" APIs. Ideally we want to print it, just to make the CSV
// complete, we still need to hide methods substituted (== @RavenwoodReplace) methods
// and for now we don't have an easy way to detect it.
- if (policy.policy == FilterPolicy.Remove) {
+ if (methodPolicy.policy == FilterPolicy.Remove) {
return@forEach
}
val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
- renameTo ?: method.name, method.desc, policy)
+ renameTo ?: method.name, method.desc, classPolicy, methodPolicy)
}
// Dump super class methods.
@@ -155,10 +178,13 @@ class ApiDumper(
dump(dumpClass, methodClass)
return
}
- if (methodClassName.startsWith("java/") ||
- methodClassName.startsWith("javax/")
- ) {
- dumpStandardClass(dumpClass, methodClassName)
+
+ // Dump overriding methods from Java standard classes, except for the Object methods,
+ // which are obvious.
+ if (methodClassName.startsWith("java/") || methodClassName.startsWith("javax/")) {
+ if (methodClassName != "java/lang/Object") {
+ dumpStandardClass(dumpClass, methodClassName)
+ }
return
}
log.w("Super class or interface $methodClassName (used by ${dumpClass.name}) not found.")
@@ -188,12 +214,12 @@ class ApiDumper(
val methodDesc = Type.getMethodDescriptor(method)
// If we already printed the method from a subclass, don't print it.
- if (isDuplicate(methodName, methodDesc)) {
+ if (shownAlready(methodName, methodDesc)) {
return@forEach
}
dumpMethod(pkg, cls, true, methodClassName,
- methodName, methodDesc, javaStandardApiPolicy)
+ methodName, methodDesc, javaStandardApiPolicy, javaStandardApiPolicy)
}
} catch (e: ClassNotFoundException) {
log.w("JVM type $methodClassName (used by ${dumpClass.name}) not found.")
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt
index 81c26ffdf1f4..c3c870f59347 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicy.kt
@@ -155,7 +155,10 @@ enum class FilterPolicy(val policyStringOrPrefix: String) {
/**
* Create a [FilterPolicyWithReason] with a given reason.
*/
- fun withReason(reason: String): FilterPolicyWithReason {
- return FilterPolicyWithReason(this, reason)
+ fun withReason(
+ reason: String,
+ statsLabelOverride: StatsLabel? = null,
+ ): FilterPolicyWithReason {
+ return FilterPolicyWithReason(this, reason, statsLabelOverride = statsLabelOverride)
}
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index b10165b835f2..7358a0bfb3e6 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -16,32 +16,54 @@
package com.android.hoststubgen.filters
/**
+ * How each entry should be handled on the dashboard.
+ */
+enum class StatsLabel(val statValue: Int, val label: String) {
+ /** Entry shouldn't show up in the dashboard. */
+ Ignored(-1, ""),
+
+ /** Entry should be shown as "not supported" */
+ NotSupported(0, "NotSupported"),
+
+ /**
+ * Entry should be shown as "supported", but are too "boring" to show on the dashboard,
+ * e.g. annotation classes.
+ */
+ SupportedButBoring(1, "Boring"),
+
+ /** Entry should be shown as "supported" */
+ Supported(2, "Supported"),
+}
+
+/**
* Captures a [FilterPolicy] with a human-readable reason.
*/
data class FilterPolicyWithReason (
- val policy: FilterPolicy,
- val reason: String = "",
+ val policy: FilterPolicy,
+ val reason: String = "",
+ private val statsLabelOverride: StatsLabel? = null
) {
/**
* Return a new [FilterPolicy] with an updated reason, while keeping the original reason
* as an "inner-reason".
*/
- fun wrapReason(reason: String): FilterPolicyWithReason {
- return FilterPolicyWithReason(policy, "$reason [inner-reason: ${this.reason}]")
+ fun wrapReason(reason: String, statsLabelOverride: StatsLabel? = null): FilterPolicyWithReason {
+ return FilterPolicyWithReason(
+ policy,
+ "$reason [inner-reason: ${this.reason}]",
+ statsLabelOverride = statsLabelOverride,
+ )
}
override fun toString(): String {
- return "[$policy - reason: $reason]"
+ return "[$policy/$statsLabel - reason: $reason]"
}
- /** Returns whether this policy should be ignored for stats. */
- val isIgnoredForStats: Boolean
- get() {
- return reason.contains("anonymous-inner-class")
- || reason.contains("is-annotation")
- || reason.contains("is-enum")
- || reason.contains("is-synthetic-method")
- || reason.contains("special-class")
- || reason.contains("substitute-to")
+ val statsLabel: StatsLabel get() {
+ statsLabelOverride?.let { return it }
+ if (policy.isSupported) {
+ return StatsLabel.Supported
}
+ return StatsLabel.NotSupported
+ }
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
index d44d016f7c5b..1145da635606 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -48,7 +48,11 @@ class ImplicitOutputFilter(
// If the outer class needs to be in impl, it should be in impl too.
val outerPolicy = outermostFilter.getPolicyForClass(cn.outerClass)
if (outerPolicy.policy.needsInOutput) {
- return FilterPolicy.KeepClass.withReason("anonymous-inner-class")
+ // We keep this class, but don't need to show it in the dashboard.
+ return FilterPolicy.KeepClass.withReason(
+ "anonymous-inner-class",
+ StatsLabel.Ignored,
+ )
}
}
return null
@@ -62,6 +66,15 @@ class ImplicitOutputFilter(
// Use the implicit policy, if any.
getClassImplicitPolicy(cn)?.let { return it }
+ // If it's an annotation class and we need to keep it, then
+ // change the reason to hide it from the stats.
+ if (cn.isAnnotation() && fallback.policy.needsInOutput) {
+ return FilterPolicy.KeepClass.withReason(
+ "is-annotation",
+ StatsLabel.Ignored,
+ )
+ }
+
return fallback
}
@@ -102,14 +115,20 @@ class ImplicitOutputFilter(
if (cn.isEnum()) {
mn?.let { mn ->
if (isAutoGeneratedEnumMember(mn)) {
- return memberPolicy.withReason(classPolicy.reason).wrapReason("is-enum")
+ return memberPolicy.withReason(classPolicy.reason).wrapReason(
+ "is-enum",
+ StatsLabel.Ignored,
+ )
}
}
}
// Keep (or stub) all members of annotations.
if (cn.isAnnotation()) {
- return memberPolicy.withReason(classPolicy.reason).wrapReason("is-annotation")
+ return memberPolicy.withReason(classPolicy.reason).wrapReason(
+ "is-annotation",
+ StatsLabel.Ignored,
+ )
}
mn?.let {
@@ -117,7 +136,8 @@ class ImplicitOutputFilter(
// For synthetic methods (such as lambdas), let's just inherit the class's
// policy.
return memberPolicy.withReason(classPolicy.reason).wrapReason(
- "is-synthetic-method"
+ "is-synthetic-method",
+ StatsLabel.Ignored,
)
}
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt
index 00e7d77fa6e7..57309b49a2cd 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/KeepNativeFilter.kt
@@ -22,6 +22,9 @@ import com.android.hoststubgen.asm.isNative
* For native methods that weren't handled by outer filters, we keep it so that
* native method registration will not crash at runtime. Ideally we shouldn't need
* this, but in practice unsupported native method registrations do occur.
+ *
+ * Native methods kept by this filter will all have a "Keep" policy, but they won't show
+ * up as "supported" in the stats dashboard beucase we set reallySupported to false.
*/
class KeepNativeFilter(
private val classes: ClassNodes,
@@ -34,7 +37,7 @@ class KeepNativeFilter(
): FilterPolicyWithReason {
return classes.findMethod(className, methodName, descriptor)?.let { mn ->
if (mn.isNative()) {
- FilterPolicy.Keep.withReason("native-preserve")
+ FilterPolicy.Keep.withReason("native-preserve", StatsLabel.NotSupported)
} else {
null
}
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 2edcb2a6c199..3291ff6b8bc6 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -31,12 +31,13 @@ import org.apache.commons.compress.archivers.zip.ZipFile
class HostStubGen(val options: HostStubGenOptions) {
fun run() {
val errors = HostStubGenErrors()
- val stats = HostStubGenStats()
val inJar = ZipFile(options.inJar.get)
// Load all classes.
val allClasses = ClassNodes.loadClassStructures(inJar, options.inJar.get)
+ val stats = HostStubGenStats(allClasses)
+
// Dump the classes, if specified.
options.inputJarDumpFile.ifSet {
log.iTime("Dump file created at $it") {
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index 761748265726..0c2269ab5e0d 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -33,7 +33,7 @@ def run_diff(file1, file2):
'--ignore-space-change',
# Ignore the class file version.
- '--ignore-matching-lines=^ *\(major\|minor\) version:$',
+ '--ignore-matching-lines=^ *\\(major\\|minor\\) version:$',
# We shouldn't need `--ignore-matching-lines`, but somehow
# the golden files were generated without these lines for b/388562869,