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

Commit d2cc1917 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

hoststubgen: Emit stats for dashboarding.

As we expand our audience, developers will be interested in knowing
what APIs are supported through a top-down dashboard view that we
can continually update over time.

This change emits a statistics CSV that can be easily bulk-imported
to generate a dashboard.

Bug: 322895594
Test: TH
Change-Id: Idea55b64cdb79e9a49f63340f83a1b395f8e5ec7
parent 7816eba7
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ java_genrule {
        "@$(location ravenwood/ravenwood-standard-options.txt) " +

        "--debug-log $(location hoststubgen_framework-minus-apex.log) " +
        "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " +

        "--out-impl-jar $(location ravenwood.jar) " +

@@ -56,6 +57,7 @@ java_genrule {
        "hoststubgen_dump.txt",

        "hoststubgen_framework-minus-apex.log",
        "hoststubgen_framework-minus-apex_stats.csv",
    ],
    visibility: ["//visibility:private"],
}
+18 −4
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import java.util.zip.ZipOutputStream
class HostStubGen(val options: HostStubGenOptions) {
    fun run() {
        val errors = HostStubGenErrors()
        val stats = HostStubGenStats()

        // Load all classes.
        val allClasses = loadClassStructures(options.inJar.get)
@@ -80,7 +81,14 @@ class HostStubGen(val options: HostStubGenOptions) {
                options.enableClassChecker.get,
                allClasses,
                errors,
                stats,
        )

        // Dump statistics, if specified.
        options.statsFile.ifSet {
            PrintWriter(it).use { pw -> stats.dump(pw) }
            log.i("Dump file created at $it")
        }
    }

    /**
@@ -237,6 +245,7 @@ class HostStubGen(val options: HostStubGenOptions) {
            enableChecker: Boolean,
            classes: ClassNodes,
            errors: HostStubGenErrors,
            stats: HostStubGenStats,
            ) {
        log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
        log.i("ASM CheckClassAdapter is %s", if (enableChecker) "enabled" else "disabled")
@@ -254,7 +263,8 @@ class HostStubGen(val options: HostStubGenOptions) {
                        while (inEntries.hasMoreElements()) {
                            val entry = inEntries.nextElement()
                            convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
                                    filter, packageRedirector, enableChecker, classes, errors)
                                    filter, packageRedirector, enableChecker, classes, errors,
                                    stats)
                        }
                        log.i("Converted all entries.")
                    }
@@ -287,6 +297,7 @@ class HostStubGen(val options: HostStubGenOptions) {
            enableChecker: Boolean,
            classes: ClassNodes,
            errors: HostStubGenErrors,
            stats: HostStubGenStats,
            ) {
        log.d("Entry: %s", entry.name)
        log.withIndent {
@@ -300,7 +311,7 @@ class HostStubGen(val options: HostStubGenOptions) {
            // If it's a class, convert it.
            if (name.endsWith(".class")) {
                processSingleClass(inZip, entry, stubOutStream, implOutStream, filter,
                        packageRedirector, enableChecker, classes, errors)
                        packageRedirector, enableChecker, classes, errors, stats)
                return
            }

@@ -354,6 +365,7 @@ class HostStubGen(val options: HostStubGenOptions) {
            enableChecker: Boolean,
            classes: ClassNodes,
            errors: HostStubGenErrors,
            stats: HostStubGenStats,
            ) {
        val classInternalName = entry.name.replaceFirst("\\.class$".toRegex(), "")
        val classPolicy = filter.getPolicyForClass(classInternalName)
@@ -370,7 +382,7 @@ class HostStubGen(val options: HostStubGenOptions) {
                    stubOutStream.putNextEntry(newEntry)
                    convertClass(classInternalName, /*forImpl=*/false, bis,
                            stubOutStream, filter, packageRedirector, enableChecker, classes,
                            errors)
                            errors, stats)
                    stubOutStream.closeEntry()
                }
            }
@@ -383,7 +395,7 @@ class HostStubGen(val options: HostStubGenOptions) {
                    implOutStream.putNextEntry(newEntry)
                    convertClass(classInternalName, /*forImpl=*/true, bis,
                            implOutStream, filter, packageRedirector, enableChecker, classes,
                            errors)
                            errors, stats)
                    implOutStream.closeEntry()
                }
            }
@@ -403,6 +415,7 @@ class HostStubGen(val options: HostStubGenOptions) {
            enableChecker: Boolean,
            classes: ClassNodes,
            errors: HostStubGenErrors,
            stats: HostStubGenStats,
            ) {
        val cr = ClassReader(input)

@@ -420,6 +433,7 @@ class HostStubGen(val options: HostStubGenOptions) {
                enablePostTrace = options.enablePostTrace.get,
                enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection.get,
                errors = errors,
                stats = stats,
        )
        outVisitor = BaseAdapter.getVisitor(classInternalName, classes, outVisitor, filter,
                packageRedirector, forImpl, visitorOptions)
+5 −0
Original line number Diff line number Diff line
@@ -108,6 +108,8 @@ class HostStubGenOptions(
        var enablePostTrace: SetOnce<Boolean> = SetOnce(false),

        var enableNonStubMethodCallDetection: SetOnce<Boolean> = SetOnce(false),

        var statsFile: SetOnce<String?> = SetOnce(null),
) {
    companion object {

@@ -252,6 +254,8 @@ class HostStubGenOptions(
                        "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
                        "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())

                        "--stats-file" -> ret.statsFile.setNextStringArg()

                        else -> throw ArgumentsException("Unknown option: $arg")
                    }
                } catch (e: SetOnce.SetMoreThanOnceException) {
@@ -387,6 +391,7 @@ class HostStubGenOptions(
              enablePreTrace=$enablePreTrace,
              enablePostTrace=$enablePostTrace,
              enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
              statsFile=$statsFile,
            }
            """.trimIndent()
    }
+74 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.hoststubgen

import com.android.hoststubgen.asm.toHumanReadableClassName
import com.android.hoststubgen.filters.FilterPolicyWithReason
import java.io.PrintWriter

open class HostStubGenStats {
    data class Stats(
            var supported: Int = 0,
            var total: Int = 0,
            val children: MutableMap<String, Stats> = mutableMapOf<String, Stats>(),
    )

    private val stats = mutableMapOf<String, Stats>()

    fun onVisitPolicyForMethod(fullClassName: String, policy: FilterPolicyWithReason) {
        if (policy.isIgnoredForStats) return

        val packageName = resolvePackageName(fullClassName)
        val className = resolveClassName(fullClassName)

        val packageStats = stats.getOrPut(packageName) { Stats() }
        val classStats = packageStats.children.getOrPut(className) { Stats() }

        if (policy.policy.isSupported) {
            packageStats.supported += 1
            classStats.supported += 1
        }
        packageStats.total += 1
        classStats.total += 1
    }

    fun dump(pw: PrintWriter) {
        pw.printf("PackageName,ClassName,SupportedMethods,TotalMethods\n")
        stats.forEach { (packageName, packageStats) ->
            if (packageStats.supported > 0) {
                packageStats.children.forEach { (className, classStats) ->
                    pw.printf("%s,%s,%d,%d\n", packageName, className,
                            classStats.supported, classStats.total)
                }
            }
        }
    }

    private fun resolvePackageName(fullClassName: String): String {
        val start = fullClassName.lastIndexOf('/')
        return fullClassName.substring(0, start).toHumanReadableClassName()
    }

    private fun resolveClassName(fullClassName: String): String {
        val start = fullClassName.lastIndexOf('/')
        val end = fullClassName.indexOf('$')
        if (end == -1) {
            return fullClassName.substring(start + 1)
        } else {
            return fullClassName.substring(start + 1, end)
        }
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -111,6 +111,16 @@ enum class FilterPolicy {
            }
        }

    /** Returns whether a policy is considered supported. */
    val isSupported: Boolean
        get() {
            return when (this) {
                // TODO: handle native method with no substitution as being unsupported
                Stub, StubClass, Keep, KeepClass, SubstituteAndStub, SubstituteAndKeep -> true
                else -> false
            }
        }

    fun getSubstitutionBasePolicy(): FilterPolicy {
        return when (this) {
            SubstituteAndKeep -> Keep
Loading