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