summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Makoto Onuki <omakoto@google.com> 2025-03-21 13:43:43 -0700
committer Makoto Onuki <omakoto@google.com> 2025-03-21 16:27:35 -0700
commitb993f5177d5fe015a1159602d8576946bff90aec (patch)
tree784f0a1563b3e2415fe17587795617876cbb1521
parent364d006c19c6b43d5e1c7cccebf8aeeae6e2a6f1 (diff)
Update stats CSV for the new dashboard
Created a new dashboard (with the same go link, go/ravenwood-stats) which is a pivot table that supports drill-down into methods. Updated the CSV to support it. Bug: 402797626 Test: Manual test with importing data and browsing go/ravenwood-stats Flag: EXEMPT host tool change only Change-Id: Ia23c0fae7fc6a096565e9902fafab0a4a3ea3067
-rwxr-xr-xravenwood/scripts/ravenwood-stats-collector.sh4
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt10
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt4
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt129
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt10
5 files changed, 74 insertions, 83 deletions
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index c2bf8d82e272..3b323411fd91 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -114,7 +114,7 @@ collect_apis() {
collect_stats $stats " (import it as 'ravenwood_stats')"
-collect_apis $apis " (import it as 'ravenwood_supported_apis')"
+collect_apis $apis " (import it as 'ravenwood_supported_apis2')"
cp *keep_all.txt $keep_all_dir
echo "Keep all files created at:"
@@ -122,4 +122,4 @@ find $keep_all_dir -type f
cp *dump.txt $dump_dir
echo "Dump files created at:"
-find $dump_dir -type f \ No newline at end of file
+find $dump_dir -type f
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
index ea8c25b6833c..4cfc205d5912 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/HostStubGenStats.kt
@@ -24,13 +24,9 @@ import org.objectweb.asm.Opcodes
import java.io.PrintWriter
/**
- * 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])
+ * This class is no longer used. It was used for the old ravenwood dashboard. (b/402797626)
+ *
+ * TODO: Delete the class.
*/
open class HostStubGenStats(val classes: ClassNodes) {
data class Stats(
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
index 112ef01e20cb..741abe3df638 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/asm/AsmUtils.kt
@@ -377,6 +377,10 @@ fun MethodNode.isPublic(): Boolean {
return (this.access and Opcodes.ACC_PUBLIC) != 0
}
+fun MethodNode.isAbstract(): Boolean {
+ return (this.access and Opcodes.ACC_ABSTRACT) != 0
+}
+
fun MethodNode.isNative(): Boolean {
return (this.access and Opcodes.ACC_NATIVE) != 0
}
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 bb8cdccafaa6..6ece17ffa6c2 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/dumper/ApiDumper.kt
@@ -20,6 +20,8 @@ import com.android.hoststubgen.asm.CTOR_NAME
import com.android.hoststubgen.asm.ClassNodes
import com.android.hoststubgen.asm.getClassNameFromFullClassName
import com.android.hoststubgen.asm.getPackageNameFromFullClassName
+import com.android.hoststubgen.asm.isAbstract
+import com.android.hoststubgen.asm.isPublic
import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.csvEscape
import com.android.hoststubgen.filters.FilterPolicy
@@ -27,8 +29,8 @@ 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
+import org.objectweb.asm.tree.MethodNode
import java.io.PrintWriter
/**
@@ -45,19 +47,14 @@ class ApiDumper(
val descriptor: String,
)
- private val javaStandardApiPolicy = FilterPolicy.Keep.withReason(
- "Java standard API",
- StatsLabel.Supported,
- )
-
private val shownMethods = mutableSetOf<MethodKey>()
/**
* Do the dump.
*/
fun dump() {
- pw.printf("PackageName,ClassName,FromSubclass,DeclareClass,MethodName,MethodDesc" +
- ",Supported,Policy,Reason,SupportedLabel\n")
+ pw.printf("PackageName,ClassName,Inherited,DeclareClass,MethodName,MethodDesc" +
+ ",Supported,Policy,Reason,Boring\n")
classes.forEach { classNode ->
shownMethods.clear()
@@ -72,32 +69,21 @@ class ApiDumper(
methodClassName: String,
methodName: String,
methodDesc: String,
- classPolicy: FilterPolicyWithReason,
+ computedMethodLabel: StatsLabel,
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,%s\n",
+ "%s,%s,%d,%s,%s,%s,%d,%s,%s,%d\n",
csvEscape(classPackage),
csvEscape(className),
if (isSuperClass) { 1 } else { 0 },
csvEscape(methodClassName),
csvEscape(methodName),
- csvEscape(methodDesc),
- methodLabel.statValue,
+ csvEscape(methodName + methodDesc),
+ if (computedMethodLabel.isSupported) { 1 } else { 0 },
methodPolicy.policy,
csvEscape(methodPolicy.reason),
- methodLabel,
+ if (computedMethodLabel == StatsLabel.SupportedButBoring) { 1 } else { 0 },
)
}
@@ -111,6 +97,42 @@ class ApiDumper(
return false
}
+ private fun getClassLabel(cn: ClassNode, classPolicy: FilterPolicyWithReason): StatsLabel {
+ if (!classPolicy.statsLabel.isSupported) {
+ return classPolicy.statsLabel
+ }
+ if (cn.name.endsWith("Proto")
+ || cn.name.endsWith("ProtoEnums")
+ || cn.name.endsWith("LogTags")
+ || cn.name.endsWith("StatsLog")) {
+ return StatsLabel.SupportedButBoring
+ }
+
+ return classPolicy.statsLabel
+ }
+
+ private fun resolveMethodLabel(
+ mn: MethodNode,
+ methodPolicy: FilterPolicyWithReason,
+ classLabel: StatsLabel,
+ ): StatsLabel {
+ // Class label will override the method label
+ if (!classLabel.isSupported) {
+ return classLabel
+ }
+ // If method isn't supported, just use it as-is.
+ if (!methodPolicy.statsLabel.isSupported) {
+ return methodPolicy.statsLabel
+ }
+
+ // Use heuristics to override the label.
+ if (!mn.isPublic() || mn.isAbstract()) {
+ return StatsLabel.SupportedButBoring
+ }
+
+ return methodPolicy.statsLabel
+ }
+
private fun dump(
dumpClass: ClassNode,
methodClass: ClassNode,
@@ -120,9 +142,11 @@ class ApiDumper(
return
}
log.d("Class ${dumpClass.name} -- policy $classPolicy")
+ val classLabel = getClassLabel(dumpClass, classPolicy)
- val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
- val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
+ val humanReadableClassName = dumpClass.name.toHumanReadableClassName()
+ val pkg = getPackageNameFromFullClassName(humanReadableClassName)
+ val cls = getClassNameFromFullClassName(humanReadableClassName)
val isSuperClass = dumpClass != methodClass
@@ -150,8 +174,12 @@ class ApiDumper(
val renameTo = filter.getRenameTo(methodClass.name, method.name, method.desc)
- dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
- renameTo ?: method.name, method.desc, classPolicy, methodPolicy)
+ val methodLabel = resolveMethodLabel(method, methodPolicy, classLabel)
+
+ if (methodLabel != StatsLabel.Ignored) {
+ dumpMethod(pkg, cls, isSuperClass, methodClass.name.toHumanReadableClassName(),
+ renameTo ?: method.name, method.desc, methodLabel, methodPolicy)
+ }
}
// Dump super class methods.
@@ -178,51 +206,6 @@ class ApiDumper(
dump(dumpClass, methodClass)
return
}
-
- // 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.")
}
-
- /**
- * Dump methods from Java standard classes.
- */
- private fun dumpStandardClass(
- dumpClass: ClassNode,
- methodClassName: String,
- ) {
- val pkg = getPackageNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
- val cls = getClassNameFromFullClassName(dumpClass.name).toHumanReadableClassName()
-
- val methodClassName = methodClassName.toHumanReadableClassName()
-
- try {
- val clazz = Class.forName(methodClassName)
-
- // Method.getMethods() returns only public methods, but with inherited ones.
- // Method.getDeclaredMethods() returns private methods too, but no inherited methods.
- //
- // Since we're only interested in public ones, just use getMethods().
- clazz.methods.forEach { method ->
- val methodName = method.name
- val methodDesc = Type.getMethodDescriptor(method)
-
- // If we already printed the method from a subclass, don't print it.
- if (shownAlready(methodName, methodDesc)) {
- return@forEach
- }
-
- dumpMethod(pkg, cls, true, methodClassName,
- 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/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index e082bbb0a119..f135c60947b3 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -32,7 +32,15 @@ enum class StatsLabel(val statValue: Int, val label: String) {
SupportedButBoring(1, "Boring"),
/** Entry should be shown as "supported" */
- Supported(2, "Supported"),
+ Supported(2, "Supported");
+
+ val isSupported: Boolean
+ get() {
+ return when (this) {
+ SupportedButBoring, Supported -> true
+ else -> false
+ }
+ }
}
/**