Files
VibeCoding/.agents/skills/r8-analyzer/references/CONFIGURATION-ANALYZER.md

17 KiB

On each step, keep the user informed of the progress by displaying the output.

1. Requirements

  • R8 Version: 9.3.7-dev or later

2. Generate proto

The report and files must be generated at {project_root}/tmp/r8analysis. If the folder is not present, create it. For example:

mkdir -p "$PWD/tmp/r8analysis"

3. Remove existing files

To make sure that this invocation doesn't source data from previous runs, remove the intermediate files keepruleradius.json and analysis_result.txt and remove the proto files in the {project_root}/tmp/r8analysis folder. Example bash commands:

# Remove the intermediate JSON and the directory containing protobuf files
rm tmp/r8analysis/keepruleradius.json
rm tmp/r8analysis/*.pb

# Copy the previous result to history before deleting the analysis
if [ -f tmp/r8analysis/analysis_result.txt ];
then cat tmp/r8analysis/analysis_result.txt > tmp/r8analysis/history.txt &&
 rm tmp/r8analysis/analysis_result.txt; fi

4. Generate the Configuration Analyzer report

Run the R8 enabled build with the system property "-Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis" to generate Configuration Analyzer report

./gradlew assembleRelease \
-Dcom.android.tools.r8.dumpkeepradiustodirectory=$PWD/tmp/r8analysis

5. Convert to JSON

To convert the generated protobuf files in {project_root}/tmp/r8analysis into json, run the following script. The json must be generated in {project_root}/tmp/r8analysis. Ensure keep_radius_pb2.py (from Step 10) is in the same directory.

import sys
import os
import glob
from google.protobuf import json_format
import keep_radius_pb2

def convert_pb_to_json(input_pb_path, output_json_path):
    bundle = keep_radius_pb2.BlastRadiusContainer()

    try:
        with open(input_pb_path, "rb") as pb_file:
            binary_data = pb_file.read()
    except Exception as e:
        print(f"Error reading file {input_pb_path}: {e}", file=sys.stderr)
        return False

    try:
        bundle.ParseFromString(binary_data)
    except Exception as e:
        print(f"Error parsing protobuf: {e}", file=sys.stderr)
        return False

    try:
        json_string = json_format.MessageToJson(
            bundle,
            always_print_fields_with_no_presence=True,
            preserving_proto_field_name=True,
            indent=4
        )
        with open(output_json_path, "w", encoding="utf-8") as json_file:
            json_file.write(json_string)
        return True
    except Exception as e:
        print(f"Error writing JSON: {e}", file=sys.stderr)
        return False

if __name__ == "__main__":
 input_pb = sys.argv[1] if len(sys.argv) > 1 else None
    if not input_pb:
        pb_files = glob.glob("tmp/r8analysis/*.pb")
        if not pb_files:
            print("Error: No .pb file found in tmp/r8analysis", file=sys.stderr)
            sys.exit(1)
        input_pb = sorted(pb_files)[-1] # Use the most recent one
    output_json = sys.argv[2] if len(sys.argv) > 2 else "tmp/r8analysis/keepruleradius.json"
    if not convert_pb_to_json(input_pb, output_json):
        sys.exit(1)

6. Analyze

Run the following analysis script on the generated JSON to get the impact of the keep rules and sort it.

import json, sys

def analyze(path):
    try:
        with open(path, 'r') as f:
            d = json.load(f)
    except Exception as e:
        print(f"Error loading JSON: {e}")
        return

    # Build reference map
    c_map = {c.get('id'): set(c.get('constraints', [])) for c in d.get('keep_constraints_table', [])}
    r_map = {r.get('id'): c_map.get(r.get('constraints_id'), set()) for r in d.get('keep_rule_blast_radius_table', [])}

    tot_opt = tot_obf = tot_shr = tot_items = 0

    # Tally constraints across all kept items
    for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table'):
        for i in d.get(tbl, []):
            tot_items += 1
            kb = i.get('kept_by', [])
            if any('DONT_OPTIMIZE' in r_map.get(r, set()) for r in kb): tot_opt += 1
            if any('DONT_OBFUSCATE' in r_map.get(r, set()) for r in kb): tot_obf += 1
            if any('DONT_SHRINK' in r_map.get(r, set()) for r in kb): tot_shr += 1

    # Find denominator
    bi = d.get('build_info', {})
    live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count'))
    denom = live if live > 0 else tot_items

    # Check for globals
    globals_src = [g.get('source', '').lower() for g in d.get('global_keep_rule_blast_radius_table', [])]
    def score(cnt, flag):
        if any(flag in src for src in globals_src): return 0.0
        return max(0.0, 100.0 - ((cnt / denom * 100) if denom > 0 else 0))

    result = [
        f"Optimization Score: {score(tot_opt, '-dontoptimize'):.2f}%",
        f"Obfuscation Score:  {score(tot_obf, '-dontobfuscate'):.2f}%",
        f"Shrinking Score:    {score(tot_shr, '-dontshrink'):.2f}%"
    ]
    for line in result:
        print(line)
    with open("tmp/r8analysis/analysis_result.txt", "w") as f:
        f.write("\n".join(result))

if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "tmp/r8analysis/keepruleradius.json"
    analyze(path)

Outputs analysis_result.txt containing scores and rule impacts.

7. Report impactful rules

Identify the keep rules with the highest impact and the subsumed rules using the following script.

import json, sys

def report(path):
    try:
        with open(path, 'r') as f:
            data = json.load(f)
    except Exception as e:
        print(f"Error loading JSON: {e}")
        return

    # Calculate denominator for percentage
    bi = data.get('build_info', {})
    live = sum(int(bi.get(k, 0)) for k in ('live_class_count', 'live_field_count', 'live_method_count'))
    denom = live if live > 0 else sum(len(data.get(tbl, [])) for tbl in ('kept_class_info_table', 'kept_field_info_table', 'kept_method_info_table'))

    processed = []
    for r in data.get('keep_rule_blast_radius_table', []):
        br = r.get('blast_radius', {})
        c, f, m = len(br.get('class_blast_radius', [])), len(br.get('field_blast_radius', [])), len(br.get('method_blast_radius', []))
        impact = c + f + m
        if impact == 0:
            continue
        impact_pct = (impact / denom * 100) if denom > 0 else 0.0
        processed.append({
            'id': r.get('id'),
            'source': r.get('source'),
            'impact': impact,
            'impact_pct': f"{impact_pct:.2f}%",
            'classes': c,
            'fields': f,
            'methods': m,
            'subsumed_by': br.get('subsumed_by', [])
        })

    processed.sort(key=lambda x: x['impact'], reverse=True)

    # Output JSON for the agent to fetch and process
    print(json.dumps({
        "top_5_impact_keep_rules": [r for r in processed if not r['subsumed_by']][:5],
        "subsumed": [r for r in processed if r['subsumed_by']]
    }, indent=2))

if __name__ == "__main__":
    report("tmp/r8analysis/keepruleradius.json")

Add this data to the analysis_result.txt with the top impactful rules and subsumed rules.

8. Compare with previous report

If {project_root}/tmp/r8analysis/history.txt exists, use the following script to compare the previous run. Use this to compare with the current values

9. Remove generated files

After the final report and analysis results are generated, remove the intermediate files keepruleradius.json and analysis_result.txt and remove the proto files in "{project_root}/tmp/r8analysis" folder

rm tmp/r8analysis/keepruleradius.json
rm tmp/r8analysis/*.pb

10. Protobuf Python bindings

The following script keep_radius_pb2.py is required by the conversion script in Step 5.

from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
    _runtime_version.Domain.PUBLIC,
    6,
    33,
    4,
    '',
    'keep_radius.proto'
)
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11keep_radius.proto\x12&com.android.tools.r8.blastradius.proto\"\x9f\x02\n\x13KeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x16\n\x0e\x63onstraints_id\x18\x03 \x01(\x05\x12\x46\n\x06origin\x18\x04 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\x12I\n\x0c\x62last_radius\x18\x05 \x01(\x0b\x32\x33.com.android.tools.r8.blastradius.proto.BlastRadius\x12\x41\n\x04tags\x18\x06 \x03(\x0e\x32\x33.com.android.tools.r8.blastradius.proto.KeepRuleTag\"w\n\x0b\x42lastRadius\x12\x13\n\x0bsubsumed_by\x18\x01 \x03(\x05\x12\x1a\n\x12\x63lass_blast_radius\x18\x02 \x03(\x05\x12\x1a\n\x12\x66ield_blast_radius\x18\x03 \x03(\x05\x12\x1b\n\x13method_blast_radius\x18\x04 \x03(\x05\"\x7f\n\x19GlobalKeepRuleBlastRadius\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x46\n\x06origin\x18\x03 \x01(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.TextFileOrigin\"j\n\x0fKeepConstraints\x12\n\n\x02id\x18\x01 \x01(\x05\x12K\n\x0b\x63onstraints\x18\x02 \x03(\x0e\x32\x36.com.android.tools.r8.blastradius.proto.KeepConstraint\"`\n\rKeptClassInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"`\n\rKeptFieldInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x66ield_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"b\n\x0eKeptMethodInfo\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1b\n\x13method_reference_id\x18\x02 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x03 \x01(\x05\x12\x0f\n\x07kept_by\x18\x04 \x03(\x05\"a\n\x0e\x46ieldReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x19\n\x11type_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"c\n\x0fMethodReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12\x63lass_reference_id\x18\x02 \x01(\x05\x12\x1a\n\x12proto_reference_id\x18\x03 \x01(\x05\x12\x0c\n\x04name\x18\x04 \x01(\t\"K\n\x0eProtoReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x15\n\rparameters_id\x18\x02 \x01(\x05\x12\x16\n\x0ereturn_type_id\x18\x03 \x01(\x05\"4\n\rTypeReference\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x17\n\x0fjava_descriptor\x18\x02 \x01(\t\";\n\x11TypeReferenceList\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x1a\n\x12type_reference_ids\x18\x02 \x03(\x05\"\x9f\x01\n\nFileOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08\x66ilename\x18\x02 \x01(\t\x12Q\n\x10maven_coordinate\x18\x03 \x01(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12 \n\x18provided_by_build_system\x18\x04 \x01(\x08\"I\n\x14\x43lassFileInJarOrigin\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x16\n\x0e\x66ile_origin_id\x18\x02 \x01(\x05\x12\r\n\x05\x65ntry\x18\x03 \x01(\t\"T\n\x0eTextFileOrigin\x12\x16\n\x0e\x66ile_origin_id\x18\x01 \x01(\x05\x12\x13\n\x0bline_number\x18\x02 \x01(\x05\x12\x15\n\rcolumn_number\x18\x03 \x01(\x05\"U\n\x0fMavenCoordinate\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x13\n\x0b\x61rtifact_id\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\"\x9a\x01\n\tBuildInfo\x12\x13\n\x0b\x63lass_count\x18\x01 \x01(\x05\x12\x13\n\x0b\x66ield_count\x18\x02 \x01(\x05\x12\x14\n\x0cmethod_count\x18\x03 \x01(\x05\x12\x18\n\x10live_class_count\x18\x04 \x01(\x05\x12\x18\n\x10live_field_count\x18\x05 \x01(\x05\x12\x19\n\x11live_method_count\x18\x06 \x01(\x05\"\xd5\n\n\x14\x42lastRadiusContainer\x12M\n\x11\x66ile_origin_table\x18\x01 \x03(\x0b\x32\x32.com.android.tools.r8.blastradius.proto.FileOrigin\x12\x64\n\x1e\x63lass_file_in_jar_origin_table\x18\x02 \x03(\x0b\x32<.com.android.tools.r8.blastradius.proto.ClassFileInJarOrigin\x12W\n\x16maven_coordinate_table\x18\x03 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MavenCoordinate\x12U\n\x15\x66ield_reference_table\x18\x04 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.FieldReference\x12W\n\x16method_reference_table\x18\x05 \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.MethodReference\x12U\n\x15proto_reference_table\x18\x06 \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.ProtoReference\x12S\n\x14type_reference_table\x18\x07 \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.TypeReference\x12\\\n\x19type_reference_list_table\x18\x08 \x03(\x0b\x32\x39.com.android.tools.r8.blastradius.proto.TypeReferenceList\x12T\n\x15kept_class_info_table\x18\t \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptClassInfo\x12T\n\x15kept_field_info_table\x18\n \x03(\x0b\x32\x35.com.android.tools.r8.blastradius.proto.KeptFieldInfo\x12V\n\x16kept_method_info_table\x18\x0b \x03(\x0b\x32\x36.com.android.tools.r8.blastradius.proto.KeptMethodInfo\x12W\n\x16keep_constraints_table\x18\x0c \x03(\x0b\x32\x37.com.android.tools.r8.blastradius.proto.KeepConstraints\x12\x61\n\x1ckeep_rule_blast_radius_table\x18\r \x03(\x0b\x32;.com.android.tools.r8.blastradius.proto.KeepRuleBlastRadius\x12n\n#global_keep_rule_blast_radius_table\x18\x0e \x03(\x0b\x32\x41.com.android.tools.r8.blastradius.proto.GlobalKeepRuleBlastRadius\x12\x45\n\nbuild_info\x18\x0f \x01(\x0b\x32\x31.com.android.tools.r8.blastradius.proto.BuildInfo*\x1f\n\x0bKeepRuleTag\x12\x10\n\x0cPACKAGE_WIDE\x10\x00*H\n\x0eKeepConstraint\x12\x12\n\x0e\x44ONT_OBFUSCATE\x10\x00\x12\x11\n\rDONT_OPTIMIZE\x10\x01\x12\x0f\n\x0b\x44ONT_SHRINK\x10\x02\x42\x45\n&com.android.tools.r8.blastradius.protoB\x19KeepRuleBlastRadiusProtosP\001\x62\x06proto3')

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'keep_radius_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
    _globals['DESCRIPTOR']._loaded_options = None
    _globals['DESCRIPTOR']._serialized_options = b'\n&com.android.tools.r8.blastradius.protoB\031KeepRuleBlastRadiusProtosP\001'
    _globals['_KEEPRULETAG']._serialized_start = 3332
    _globals['_KEEPRULETAG']._serialized_end = 3363
    _globals['_KEEPCONSTRAINT']._serialized_start = 3365
    _globals['_KEEPCONSTRAINT']._serialized_end = 3437
    _globals['_KEEPRULEBLASTRADIUS']._serialized_start = 62
    _globals['_KEEPRULEBLASTRADIUS']._serialized_end = 349
    _globals['_BLASTRADIUS']._serialized_start = 351
    _globals['_BLASTRADIUS']._serialized_end = 470
    _globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_start = 472
    _globals['_GLOBALKEEPRULEBLASTRADIUS']._serialized_end = 599
    _globals['_KEEPCONSTRAINTS']._serialized_start = 601
    _globals['_KEEPCONSTRAINTS']._serialized_end = 707
    _globals['_KEPTCLASSINFO']._serialized_start = 709
    _globals['_KEPTCLASSINFO']._serialized_end = 805
    _globals['_KEPTFIELDINFO']._serialized_start = 807
    _globals['_KEPTFIELDINFO']._serialized_end = 903
    _globals['_KEPTMETHODINFO']._serialized_start = 905
    _globals['_KEPTMETHODINFO']._serialized_end = 1003
    _globals['_FIELDREFERENCE']._serialized_start = 1005
    _globals['_FIELDREFERENCE']._serialized_end = 1102
    _globals['_METHODREFERENCE']._serialized_start = 1104
    _globals['_METHODREFERENCE']._serialized_end = 1203
    _globals['_PROTOREFERENCE']._serialized_start = 1205
    _globals['_PROTOREFERENCE']._serialized_end = 1280
    _globals['_TYPEREFERENCE']._serialized_start = 1282
    _globals['_TYPEREFERENCE']._serialized_end = 1334
    _globals['_TYPEREFERENCELIST']._serialized_start = 1336
    _globals['_TYPEREFERENCELIST']._serialized_end = 1395
    _globals['_FILEORIGIN']._serialized_start = 1398
    _globals['_FILEORIGIN']._serialized_end = 1557
    _globals['_CLASSFILEINJARORIGIN']._serialized_start = 1559
    _globals['_CLASSFILEINJARORIGIN']._serialized_end = 1632
    _globals['_TEXTFILEORIGIN']._serialized_start = 1634
    _globals['_TEXTFILEORIGIN']._serialized_end = 1718
    _globals['_MAVENCOORDINATE']._serialized_start = 1720
    _globals['_MAVENCOORDINATE']._serialized_end = 1805
    _globals['_BUILDINFO']._serialized_start = 1808
    _globals['_BUILDINFO']._serialized_end = 1962
    _globals['_BLASTRADIUSCONTAINER']._serialized_start = 1965
    _globals['_BLASTRADIUSCONTAINER']._serialized_end = 3330
# @@protoc_insertion_point(module_scope)