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

Commit 801239d2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Update "stats" generation" into main

parents 94f13f4c e18b7781
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