1 /*
2  * Copyright (C) 2020 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 com.android.tradefed.invoker.logger;
17 
18 import com.android.tradefed.build.IBuildProvider;
19 import com.android.tradefed.device.metric.IMetricCollector;
20 import com.android.tradefed.postprocessor.IPostProcessor;
21 import com.android.tradefed.targetprep.ITargetPreparer;
22 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
23 import com.android.tradefed.testtype.IRemoteTest;
24 
25 import com.google.common.collect.ImmutableSet;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.concurrent.ConcurrentHashMap;
31 
32 /** A utility to track the usage of the different Trade Fedederation objects. */
33 public class TfObjectTracker {
34 
35     public static final String TF_OBJECTS_TRACKING_KEY = "tf_objects_tracking";
36     private static final Set<Class<?>> TRACKED_CLASSES =
37             ImmutableSet.of(
38                     IBuildProvider.class,
39                     IMetricCollector.class,
40                     IMultiTargetPreparer.class,
41                     IPostProcessor.class,
42                     IRemoteTest.class,
43                     ITargetPreparer.class);
44 
TfObjectTracker()45     private TfObjectTracker() {}
46 
47     private static final Map<ThreadGroup, Map<String, Long>> mPerGroupUsage =
48             new ConcurrentHashMap<ThreadGroup, Map<String, Long>>();
49 
50     /** Count the occurrence of a give class and its super classes until the Tradefed interface. */
countWithParents(Class<?> object)51     public static void countWithParents(Class<?> object) {
52         if (!count(object)) {
53             return;
54         }
55         // Track all the super class until not a TF interface to get a full picture.
56         countWithParents(object.getSuperclass());
57     }
58 
59     /**
60      * Count explicitly one class and its occurrences
61      *
62      * @param className The object to track
63      * @param occurrences current num of known occurrences
64      */
directCount(String className, long occurrences)65     public static void directCount(String className, long occurrences) {
66         ThreadGroup group = Thread.currentThread().getThreadGroup();
67         if (mPerGroupUsage.get(group) == null) {
68             mPerGroupUsage.put(group, new ConcurrentHashMap<>());
69         }
70         Map<String, Long> countMap = mPerGroupUsage.get(group);
71         long count = 0;
72         if (countMap.get(className) != null) {
73             count = countMap.get(className);
74         }
75         count += occurrences;
76         countMap.put(className, count);
77     }
78 
79     /**
80      * Count the current occurrence only if it's part of the tracked objects.
81      *
82      * @param object The object to track
83      * @return True if the object was tracked, false otherwise.
84      */
count(Class<?> object)85     private static boolean count(Class<?> object) {
86         ThreadGroup group = Thread.currentThread().getThreadGroup();
87         String qualifiedName = object.getName();
88 
89         boolean tracked = false;
90         for (Class<?> classTracked : TRACKED_CLASSES) {
91             if (classTracked.isAssignableFrom(object)) {
92                 tracked = true;
93                 break;
94             }
95         }
96         if (!tracked) {
97             return false;
98         }
99         // Don't track internal classes for now but return true to track subclass if needed.
100         if (qualifiedName.contains("$")) {
101             return true;
102         }
103         if (mPerGroupUsage.get(group) == null) {
104             mPerGroupUsage.put(group, new ConcurrentHashMap<>());
105         }
106         Map<String, Long> countMap = mPerGroupUsage.get(group);
107         long count = 0;
108         if (countMap.get(qualifiedName) != null) {
109             count = countMap.get(qualifiedName);
110         }
111         count++;
112         countMap.put(qualifiedName, count);
113         return true;
114     }
115 
116     /** Returns the usage of the tracked objects. */
getUsage()117     public static Map<String, Long> getUsage() {
118         ThreadGroup group = Thread.currentThread().getThreadGroup();
119         if (mPerGroupUsage.get(group) == null) {
120             mPerGroupUsage.put(group, new ConcurrentHashMap<>());
121         }
122         return new HashMap<>(mPerGroupUsage.get(group));
123     }
124 
125     /** Stop tracking the current invocation. This is called automatically by the harness. */
clearTracking()126     public static void clearTracking() {
127         ThreadGroup group = Thread.currentThread().getThreadGroup();
128         mPerGroupUsage.remove(group);
129     }
130 }
131