• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.platform.test.ravenwood;
17 
18 import com.android.ravenwood.RavenwoodRuntimeNative;
19 
20 import java.io.PrintStream;
21 import java.util.HashSet;
22 import java.util.Objects;
23 
24 /**
25  * Provides a method call hook that prints almost all (see below) the framework methods being
26  * called with indentation.
27  *
28  * We don't log methods that are trivial, uninteresting, or would be too noisy.
29  * e.g. we don't want to log any logging related methods, or collection APIs.
30  *
31  */
32 public class RavenwoodMethodCallLogger {
RavenwoodMethodCallLogger()33     private RavenwoodMethodCallLogger() {
34     }
35 
36     /** We don't want to log anything before ravenwood is initialized. This flag controls it.*/
37     private static volatile boolean sEnabled = false;
38 
39     private static volatile PrintStream sOut = System.out;
40 
41     /** Return the current thread's call nest level. */
getNestLevel()42     private static int getNestLevel() {
43         return Thread.currentThread().getStackTrace().length;
44     }
45 
46     private static class ThreadInfo {
47         /**
48          * We save the current thread's nest call level here and use that as the initial level.
49          * We do it because otherwise the nest level would be too deep by the time test
50          * starts.
51          */
52         public final int mInitialNestLevel = getNestLevel();
53 
54         /**
55          * A nest level where shouldLog() returned false.
56          * Once it's set, we ignore all calls deeper than this.
57          */
58         public int mDisabledNestLevel = Integer.MAX_VALUE;
59     }
60 
61     private static final ThreadLocal<ThreadInfo> sThreadInfo = new ThreadLocal<>() {
62         @Override
63         protected ThreadInfo initialValue() {
64             return new ThreadInfo();
65         }
66     };
67 
68     /** Classes that should be logged. Uses a map for fast lookup. */
69     private static final HashSet<Class> sIgnoreClasses = new HashSet<>();
70     static {
71         // The following classes are not interesting...
72         sIgnoreClasses.add(android.util.Log.class);
73         sIgnoreClasses.add(android.util.Slog.class);
74         sIgnoreClasses.add(android.util.EventLog.class);
75         sIgnoreClasses.add(android.util.TimingsTraceLog.class);
76 
77         sIgnoreClasses.add(android.util.SparseArray.class);
78         sIgnoreClasses.add(android.util.SparseIntArray.class);
79         sIgnoreClasses.add(android.util.SparseLongArray.class);
80         sIgnoreClasses.add(android.util.SparseBooleanArray.class);
81         sIgnoreClasses.add(android.util.SparseDoubleArray.class);
82         sIgnoreClasses.add(android.util.SparseSetArray.class);
83         sIgnoreClasses.add(android.util.SparseArrayMap.class);
84         sIgnoreClasses.add(android.util.LongSparseArray.class);
85         sIgnoreClasses.add(android.util.LongSparseLongArray.class);
86         sIgnoreClasses.add(android.util.LongArray.class);
87 
88         sIgnoreClasses.add(android.text.FontConfig.class);
89 
90         sIgnoreClasses.add(android.os.SystemClock.class);
91         sIgnoreClasses.add(android.os.Trace.class);
92         sIgnoreClasses.add(android.os.LocaleList.class);
93         sIgnoreClasses.add(android.os.Build.class);
94         sIgnoreClasses.add(android.os.SystemProperties.class);
95 
96         sIgnoreClasses.add(com.android.internal.util.Preconditions.class);
97 
98         sIgnoreClasses.add(android.graphics.FontListParser.class);
99         sIgnoreClasses.add(android.graphics.ColorSpace.class);
100 
101         sIgnoreClasses.add(android.graphics.fonts.FontStyle.class);
102         sIgnoreClasses.add(android.graphics.fonts.FontVariationAxis.class);
103 
104         sIgnoreClasses.add(com.android.internal.compat.CompatibilityChangeInfo.class);
105         sIgnoreClasses.add(com.android.internal.os.LoggingPrintStream.class);
106 
107         sIgnoreClasses.add(android.os.ThreadLocalWorkSource.class);
108 
109         // Following classes *may* be interesting for some purposes, but the initialization is
110         // too noisy...
111         sIgnoreClasses.add(android.graphics.fonts.SystemFonts.class);
112 
113     }
114 
115     /**
116      * Return if a class should be ignored. Uses {link #sIgnoreCladsses}, but
117      * we ignore more classes.
118      */
shouldIgnoreClass(Class<?> clazz)119     private static boolean shouldIgnoreClass(Class<?> clazz) {
120         if (sIgnoreClasses.contains(clazz)) {
121             return true;
122         }
123         // Let's also ignore collection-ish classes in android.util.
124         if (java.util.Collection.class.isAssignableFrom(clazz)
125                 || java.util.Map.class.isAssignableFrom(clazz)
126         ) {
127             if ("android.util".equals(clazz.getPackageName())) {
128                 return true;
129             }
130             return false;
131         }
132 
133         switch (clazz.getSimpleName()) {
134             case "EventLogTags":
135                 return false;
136         }
137 
138         // Following are classes that can't be referred to here directly.
139         // e.g. AndroidPrintStream is package-private, so we can't use its "class" here.
140         switch (clazz.getName()) {
141             case "com.android.internal.os.AndroidPrintStream":
142                 return false;
143         }
144         return false;
145     }
146 
shouldLog( Class<?> methodClass, String methodName, @SuppressWarnings("UnusedVariable") String methodDescriptor )147     private static boolean shouldLog(
148             Class<?> methodClass,
149             String methodName,
150             @SuppressWarnings("UnusedVariable") String methodDescriptor
151     ) {
152         // Should we ignore this class?
153         if (shouldIgnoreClass(methodClass)) {
154             return false;
155         }
156         // Is it a nested class in a class that should be ignored?
157         var host = methodClass.getNestHost();
158         if (host != methodClass && shouldIgnoreClass(host)) {
159             return false;
160         }
161 
162         var className = methodClass.getName();
163 
164         // Ad-hoc ignore list. They'd be too noisy.
165         if ("create".equals(methodName)
166                 // We may apply jarjar, so use endsWith().
167                 && className.endsWith("com.android.server.compat.CompatConfig")) {
168             return false;
169         }
170 
171         var pkg = methodClass.getPackageName();
172         if (pkg.startsWith("android.icu")) {
173             return false;
174         }
175 
176         return true;
177     }
178 
179     /**
180      * Call this to enable logging.
181      */
enable(PrintStream out)182     public static void enable(PrintStream out) {
183         sEnabled = true;
184         sOut = Objects.requireNonNull(out);
185 
186         // It's called from the test thread (Java's main thread). Because we're already
187         // in deep nest calls, we initialize the initial nest level here.
188         sThreadInfo.get();
189     }
190 
191     /** Actual method hook entry point.*/
logMethodCall( Class<?> methodClass, String methodName, String methodDescriptor )192     public static void logMethodCall(
193             Class<?> methodClass,
194             String methodName,
195             String methodDescriptor
196     ) {
197         if (!sEnabled) {
198             return;
199         }
200         final var ti = sThreadInfo.get();
201         final int nestLevel = getNestLevel() - ti.mInitialNestLevel;
202 
203         // Once shouldLog() returns false, we just ignore all deeper calls.
204         if (ti.mDisabledNestLevel < nestLevel) {
205             return; // Still ignore.
206         }
207         final boolean shouldLog = shouldLog(methodClass, methodName, methodDescriptor);
208 
209         if (!shouldLog) {
210             ti.mDisabledNestLevel = nestLevel;
211             return;
212         }
213         ti.mDisabledNestLevel = Integer.MAX_VALUE;
214 
215         var out = sOut;
216         out.print("# [");
217         out.print(RavenwoodRuntimeNative.gettid());
218         out.print(": ");
219         out.print(Thread.currentThread().getName());
220         out.print("]: ");
221         out.print("[@");
222         out.printf("%2d", nestLevel);
223         out.print("] ");
224         for (int i = 0; i < nestLevel; i++) {
225             out.print("  ");
226         }
227         out.println(methodClass.getName() + "." + methodName + methodDescriptor);
228     }
229 }
230