1 /* 2 * Copyright (C) 2024 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 17 package com.android.xts.apimapper.helper; 18 19 import android.util.Log; 20 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.TreeMap; 26 27 /** 28 * A class to record and dump Android API method calls. 29 */ 30 public final class DeviceMethodCallStats { 31 DeviceMethodCallStats()32 public DeviceMethodCallStats() {} 33 34 // A map to record the data of called class -> called method -> called method description 35 // -> called times. 36 private final Map<String, Map<String, Map<String, Integer>>> mStats = new TreeMap<>(); 37 38 /** Clear all records. */ clear()39 public void clear() { 40 mStats.clear(); 41 } 42 43 /** Record the given method call. */ onMethodCalled(String className, String methodName, String methodDesc)44 public void onMethodCalled(String className, String methodName, String methodDesc) { 45 var classStats = mStats.computeIfAbsent(className, k -> new TreeMap<>()); 46 var methodStats = classStats.computeIfAbsent(methodName, k -> new TreeMap<>()); 47 int count = methodStats.getOrDefault(methodDesc, 0); 48 methodStats.put(methodDesc, count + 1); 49 } 50 51 /** 52 * Dump all API calls to the output stream. Each API call is formatted as a csv record 53 * containing a prefix, a test class name, a test method name, a called class, a called method, 54 * a called method description, and a called times. 55 */ dump(String tag, String prefix, String testClassName, String testMethodName)56 public void dump(String tag, String prefix, String testClassName, String testMethodName) { 57 String logPrefix = String.format( 58 "%s %s:%s", 59 prefix, 60 testClassName, 61 removeMethodParameters(testMethodName)); 62 List<String> classes = new ArrayList<>(mStats.keySet()); 63 Collections.sort(classes); 64 65 for (String className : classes) { 66 Map<String, Map<String, Integer>> methodStats = mStats.get(className); 67 List<String> methodNames = new ArrayList<>(methodStats.keySet()); 68 69 for (String methodName : methodNames) { 70 Map<String, Integer> methodDescStats = methodStats.get(methodName); 71 List<String> methodDescs = new ArrayList<>(methodDescStats.keySet()); 72 73 for (String methodDesc : methodDescs) { 74 String logMessage = String.format( 75 "%s:%s:%s:%s:%s", 76 logPrefix, 77 getPackageClass(className), 78 methodName, 79 methodDesc, 80 methodDescStats.get(methodDesc) 81 82 ); 83 Log.i(tag, logMessage); 84 } 85 } 86 } 87 } 88 89 /** 90 * Split package name and class name from the given string. 91 * 92 * @return a csv style string with the format "{packageName}:{className}" 93 */ getPackageClass(String packageClass)94 public static String getPackageClass(String packageClass) { 95 int lastDot = packageClass.lastIndexOf('.'); 96 if (lastDot < 0) { 97 return ":" + packageClass; 98 } else { 99 return packageClass.substring(0, lastDot) + ":" 100 + packageClass.substring(lastDot + 1); 101 } 102 } 103 removeMethodParameters(String methodName)104 private static String removeMethodParameters(String methodName) { 105 int pos = methodName.indexOf('['); 106 if (pos < 0) { 107 return methodName; 108 } 109 return methodName.substring(0, pos); 110 } 111 } 112