Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e18b7781 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

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
parent c6af79cf
Loading
Loading
Loading
Loading
+40 −13
Original line number Diff line number Diff line
@@ -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:"
+18 −15
Original line number Diff line number Diff line
@@ -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
        }
+44 −18
Original line number Diff line number Diff line
@@ -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/")
            ) {

        // 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.")
+5 −2
Original line number Diff line number Diff line
@@ -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)
    }
}
+36 −14
Original line number Diff line number Diff line
@@ -15,33 +15,55 @@
 */
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 = "",
    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
    }
}
Loading