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

Commit 44fe3a9a authored by Pablo Gamito's avatar Pablo Gamito
Browse files

Update ProtoLogTool

Generate the impl classes and support writing the viewer config to a proto file

Flag: ACONFIG android.tracing.Flags.perfettoProtolog DEVELOPMENT
Bug: 276432490
Test: atest FrameworksServicesTests
Change-Id: I69956b5ef2b8b48a98860aa3b8579521480d7bc0
parent 329813a9
Loading
Loading
Loading
Loading
+10 −8
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.internal.protolog;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.util.Slog;
import android.util.Slog;


import com.android.internal.protolog.common.ILogger;

import org.json.JSONException;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONObject;


@@ -39,10 +41,10 @@ import java.util.zip.GZIPInputStream;
 */
 */
public class ProtoLogViewerConfigReader {
public class ProtoLogViewerConfigReader {
    private static final String TAG = "ProtoLogViewerConfigReader";
    private static final String TAG = "ProtoLogViewerConfigReader";
    private Map<Integer, String> mLogMessageMap = null;
    private Map<Long, String> mLogMessageMap = null;


    /** Returns message format string for its hash or null if unavailable. */
    /** Returns message format string for its hash or null if unavailable. */
    public synchronized String getViewerString(int messageHash) {
    public synchronized String getViewerString(long messageHash) {
        if (mLogMessageMap != null) {
        if (mLogMessageMap != null) {
            return mLogMessageMap.get(messageHash);
            return mLogMessageMap.get(messageHash);
        } else {
        } else {
@@ -53,19 +55,19 @@ public class ProtoLogViewerConfigReader {
    /**
    /**
     * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
     * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
     */
     */
    public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
    public synchronized void loadViewerConfig(ILogger logger, String viewerConfigFilename) {
        try {
        try {
            loadViewerConfig(new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
            loadViewerConfig(new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
            logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
            logger.log("Loaded " + mLogMessageMap.size()
                    + " log definitions from " + viewerConfigFilename);
                    + " log definitions from " + viewerConfigFilename);
        } catch (FileNotFoundException e) {
        } catch (FileNotFoundException e) {
            logAndPrintln(pw, "Unable to load log definitions: File "
            logger.log("Unable to load log definitions: File "
                    + viewerConfigFilename + " not found." + e);
                    + viewerConfigFilename + " not found." + e);
        } catch (IOException e) {
        } catch (IOException e) {
            logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
            logger.log("Unable to load log definitions: IOException while reading "
                    + viewerConfigFilename + ". " + e);
                    + viewerConfigFilename + ". " + e);
        } catch (JSONException e) {
        } catch (JSONException e) {
            logAndPrintln(pw, "Unable to load log definitions: JSON parsing exception while reading "
            logger.log("Unable to load log definitions: JSON parsing exception while reading "
                    + viewerConfigFilename + ". " + e);
                    + viewerConfigFilename + ". " + e);
        }
        }
    }
    }
@@ -95,7 +97,7 @@ public class ProtoLogViewerConfigReader {
        while (it.hasNext()) {
        while (it.hasNext()) {
            String key = (String) it.next();
            String key = (String) it.next();
            try {
            try {
                int hash = Integer.parseInt(key);
                long hash = Long.parseLong(key);
                JSONObject val = messages.getJSONObject(key);
                JSONObject val = messages.getJSONObject(key);
                String msg = val.getString("message");
                String msg = val.getString("message");
                mLogMessageMap.put(hash, msg);
                mLogMessageMap.put(hash, msg);
+28 −0
Original line number Original line 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.internal.protolog.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
    enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }

    Value value();
}
+2 −2
Original line number Original line Diff line number Diff line
@@ -98,7 +98,7 @@ public class ProtoLogViewerConfigReaderTest {


    @Test
    @Test
    public void loadViewerConfig() {
    public void loadViewerConfig() {
        mConfig.loadViewerConfig(null, mTestViewerConfig.getAbsolutePath());
        mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
        assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
        assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
        assertEquals("Test 2", mConfig.getViewerString(1352021864));
        assertEquals("Test 2", mConfig.getViewerString(1352021864));
        assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
        assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
@@ -107,7 +107,7 @@ public class ProtoLogViewerConfigReaderTest {


    @Test
    @Test
    public void loadViewerConfig_invalidFile() {
    public void loadViewerConfig_invalidFile() {
        mConfig.loadViewerConfig(null, "/tmp/unknown/file/does/not/exist");
        mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
        // No exception is thrown.
        // No exception is thrown.
        assertNull(mConfig.getViewerString(1));
        assertNull(mConfig.getViewerString(1));
    }
    }
+23 −0
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -16,37 +16,8 @@


package com.android.protolog.tool
package com.android.protolog.tool


import org.junit.Assert.assertEquals
import com.github.javaparser.ast.expr.MethodCallExpr
import org.junit.Test


class ProtoLogToolTest {
interface MethodCallVisitor {

    fun processCall(call: MethodCallExpr)
    @Test
    fun generateLogGroupCache() {
        val groups = mapOf(
                "GROUP1" to LogGroup("GROUP1", true, true, "TAG1"),
                "GROUP2" to LogGroup("GROUP2", true, true, "TAG2")
        )
        val code = ProtoLogTool.generateLogGroupCache("org.example", "ProtoLog\$Cache",
                groups, "org.example.ProtoLogImpl", "org.example.ProtoLogGroups")

        assertEquals("""
            package org.example;

            public class ProtoLog${'$'}Cache {
                public static boolean GROUP1_enabled = false;
                public static boolean GROUP2_enabled = false;

                static {
                    org.example.ProtoLogImpl.sCacheUpdater = ProtoLog${'$'}Cache::update;
                    update();
                }

                static void update() {
                    GROUP1_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP1);
                    GROUP2_enabled = org.example.ProtoLogImpl.isEnabled(org.example.ProtoLogGroups.GROUP2);
                }
            }
        """.trimIndent(), code)
    }
}
}
+7 −109
Original line number Original line Diff line number Diff line
/*
/*
 * Copyright (C) 2019 The Android Open Source Project
 * Copyright (C) 2024 The Android Open Source Project
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * you may not use this file except in compliance with the License.
@@ -16,115 +16,13 @@


package com.android.protolog.tool
package com.android.protolog.tool


import com.android.internal.protolog.common.LogLevel
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.Node
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.NameExpr


/**
interface ProtoLogCallProcessor {
 * Helper class for visiting all ProtoLog calls.
    fun process(
 * For every valid call in the given {@code CompilationUnit} a {@code ProtoLogCallVisitor} callback
        code: CompilationUnit,
 * is executed.
        logCallVisitor: ProtoLogCallVisitor?,
 */
        otherCallVisitor: MethodCallVisitor?,
open class ProtoLogCallProcessor(
    private val protoLogClassName: String,
    private val protoLogGroupClassName: String,
    private val groupMap: Map<String, LogGroup>
) {
    private val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.')
    private val protoLogGroupSimpleClassName = protoLogGroupClassName.substringAfterLast('.')

    private fun getLogGroupName(
        expr: Expression,
        isClassImported: Boolean,
        staticImports: Set<String>,
        fileName: String
        fileName: String
    ): String {
    ): CompilationUnit
        val context = ParsingContext(fileName, expr)
        return when (expr) {
            is NameExpr -> when {
                expr.nameAsString in staticImports -> expr.nameAsString
                else ->
                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
                            context)
            }
            is FieldAccessExpr -> when {
                expr.scope.toString() == protoLogGroupClassName
                        || isClassImported &&
                        expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
                else ->
                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
                            context)
            }
            else -> throw InvalidProtoLogCallException("Invalid group argument " +
                    "- must be ProtoLogGroup enum member reference: $expr", context)
        }
    }

    private fun isProtoCall(
        call: MethodCallExpr,
        isLogClassImported: Boolean,
        staticLogImports: Collection<String>
    ): Boolean {
        return call.scope.isPresent && call.scope.get().toString() == protoLogClassName ||
                isLogClassImported && call.scope.isPresent &&
                call.scope.get().toString() == protoLogSimpleClassName ||
                !call.scope.isPresent && staticLogImports.contains(call.name.toString())
    }

    open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String):
            CompilationUnit {
        CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
        CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)

        val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
        val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
        val isGroupClassImported = CodeUtils.isClassImportedOrSamePackage(code,
                protoLogGroupClassName)
        val staticGroupImports = CodeUtils.staticallyImportedMethods(code, protoLogGroupClassName)

        code.findAll(MethodCallExpr::class.java)
                .filter { call ->
                    isProtoCall(call, isLogClassImported, staticLogImports)
                }.forEach { call ->
                    val context = ParsingContext(fileName, call)
                    if (call.arguments.size < 2) {
                        throw InvalidProtoLogCallException("Method signature does not match " +
                                "any ProtoLog method: $call", context)
                    }

                    val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
                            context)
                    val groupNameArg = call.getArgument(0)
                    val groupName =
                            getLogGroupName(groupNameArg, isGroupClassImported,
                                    staticGroupImports, fileName)
                    if (groupName !in groupMap) {
                        throw InvalidProtoLogCallException("Unknown group argument " +
                                "- not a ProtoLogGroup enum member: $call", context)
                    }

                    callVisitor?.processCall(call, messageString, getLevelForMethodName(
                            call.name.toString(), call, context), groupMap.getValue(groupName))
                }
        return code
    }

    companion object {
        fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel {
            return when (name) {
                "d" -> LogLevel.DEBUG
                "v" -> LogLevel.VERBOSE
                "i" -> LogLevel.INFO
                "w" -> LogLevel.WARN
                "e" -> LogLevel.ERROR
                "wtf" -> LogLevel.WTF
                else ->
                    throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
            }
        }
    }
}
}
Loading