Create the structure that will track the usage of our objects
Test: unit tests
Bug: 148783241
Change-Id: I44eadd26a3a8f363276103becb53b77c364081a6
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
index 42eec08..4427fe5 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
@@ -87,7 +87,9 @@
/** Clear the invocation info for an invocation. */
public static void clearInvocationInfos() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
- mPerGroupInfo.remove(group);
+ synchronized (mPerGroupInfo) {
+ mPerGroupInfo.remove(group);
+ }
}
/**
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index f9718cb..8b8b802 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -137,6 +137,8 @@
/** Clear the invocation metrics for an invocation. */
public static void clearInvocationMetrics() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
- mPerGroupMetrics.remove(group);
+ synchronized (mPerGroupMetrics) {
+ mPerGroupMetrics.remove(group);
+ }
}
}
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index b84a2ed..b384bc9 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -38,6 +38,7 @@
import com.android.tradefed.invoker.logger.CurrentInvocation.InvocationInfo;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecution;
import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
import com.android.tradefed.invoker.shard.LastShardDetector;
@@ -402,6 +403,7 @@
}
}
} finally {
+ TfObjectTracker.clearTracking();
CurrentInvocation.clearInvocationInfos();
invocationPath.cleanUpBuilds(context, config);
}
diff --git a/src/com/android/tradefed/invoker/logger/TfObjectTracker.java b/src/com/android/tradefed/invoker/logger/TfObjectTracker.java
new file mode 100644
index 0000000..cdb79ef
--- /dev/null
+++ b/src/com/android/tradefed/invoker/logger/TfObjectTracker.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 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.tradefed.invoker.logger;
+
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.device.metric.IMetricCollector;
+import com.android.tradefed.postprocessor.IPostProcessor;
+import com.android.tradefed.suite.checker.ISystemStatusChecker;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** A utility to track the usage of the different Trade Fedederation objects. */
+public class TfObjectTracker {
+
+ public static final String TF_OBJECTS_TRACKING_KEY = "tf_objects_tracking";
+ private static final Set<Class<?>> TRACKED_CLASSES =
+ ImmutableSet.of(
+ IBuildProvider.class,
+ IMetricCollector.class,
+ IMultiTargetPreparer.class,
+ IPostProcessor.class,
+ IRemoteTest.class,
+ ISystemStatusChecker.class,
+ ITargetPreparer.class);
+
+ private TfObjectTracker() {}
+
+ private static final Map<ThreadGroup, Map<String, Long>> mPerGroupUsage =
+ new ConcurrentHashMap<ThreadGroup, Map<String, Long>>();
+
+ /** Count the occurrence of a give class and its super classes until the Tradefed interface. */
+ public static void countWithParents(Class<?> object) {
+ if (!count(object)) {
+ return;
+ }
+ // Track all the super class until not a TF interface to get a full picture.
+ countWithParents(object.getSuperclass());
+ }
+
+ /**
+ * Count the current occurrence only if it's part of the tracked objects.
+ *
+ * @param object The object to track
+ * @return True if the object was tracked, false otherwise.
+ */
+ public static boolean count(Class<?> object) {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ String qualifiedName = object.getName();
+
+ boolean tracked = false;
+ for (Class<?> classTracked : TRACKED_CLASSES) {
+ if (classTracked.isAssignableFrom(object)) {
+ tracked = true;
+ break;
+ }
+ }
+ if (!tracked) {
+ return false;
+ }
+ if (mPerGroupUsage.get(group) == null) {
+ mPerGroupUsage.put(group, new ConcurrentHashMap<>());
+ }
+ Map<String, Long> countMap = mPerGroupUsage.get(group);
+ long count = 0;
+ if (countMap.get(qualifiedName) != null) {
+ count = countMap.get(qualifiedName);
+ }
+ count++;
+ countMap.put(qualifiedName, count);
+ return true;
+ }
+
+ /** Returns the usage of the tracked objects. */
+ public static Map<String, Long> getUsage() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ if (mPerGroupUsage.get(group) == null) {
+ mPerGroupUsage.put(group, new ConcurrentHashMap<>());
+ }
+ return new HashMap<>(mPerGroupUsage.get(group));
+ }
+
+ /** Stop tracking the current invocation. This is called automatically by the harness. */
+ public static void clearTracking() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ mPerGroupUsage.remove(group);
+ }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index c299b28..77813ab 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -135,6 +135,7 @@
import com.android.tradefed.invoker.TestInvocationTest;
import com.android.tradefed.invoker.UnexecutedTestReporterThreadTest;
import com.android.tradefed.invoker.logger.InvocationMetricLoggerTest;
+import com.android.tradefed.invoker.logger.TfObjectTrackerTest;
import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecutionTest;
import com.android.tradefed.invoker.shard.ShardHelperTest;
import com.android.tradefed.invoker.shard.StrictShardHelperTest;
@@ -552,6 +553,7 @@
// invoker.logger
InvocationMetricLoggerTest.class,
+ TfObjectTrackerTest.class,
// invoker.shard
ShardHelperTest.class,
diff --git a/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java b/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java
new file mode 100644
index 0000000..a170e8e
--- /dev/null
+++ b/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 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.tradefed.invoker.logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.command.CommandOptions;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
+import com.android.tradefed.targetprep.TestAppInstallSetup;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Map;
+import java.util.UUID;
+
+/** Unit tests for {@link TfObjectTracker}. */
+@RunWith(JUnit4.class)
+public class TfObjectTrackerTest {
+
+ @Test
+ public void testNotTracking() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, true, CommandOptions.class);
+ assertTrue(metrics.isEmpty());
+ }
+
+ @Test
+ public void testTrackingWithParents() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, true, SuiteApkInstaller.class);
+ assertEquals(3, metrics.size());
+ assertEquals(1, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertEquals(1, metrics.get(TestAppInstallSetup.class.getName()).longValue());
+ assertEquals(1, metrics.get(BaseTargetPreparer.class.getName()).longValue());
+ // Add a second time
+ metrics = logMetric(testGroup, true, SuiteApkInstaller.class);
+ assertEquals(3, metrics.size());
+ assertEquals(2, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertEquals(2, metrics.get(TestAppInstallSetup.class.getName()).longValue());
+ assertEquals(2, metrics.get(BaseTargetPreparer.class.getName()).longValue());
+ }
+
+ @Test
+ public void testTracking() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, false, SuiteApkInstaller.class);
+ assertEquals(1, metrics.size());
+ assertEquals(1, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertNull(metrics.get(TestAppInstallSetup.class.getName()));
+ assertNull(metrics.get(BaseTargetPreparer.class.getName()));
+ // Add a second time
+ metrics = logMetric(testGroup, false, SuiteApkInstaller.class);
+ assertEquals(1, metrics.size());
+ assertEquals(2, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertNull(metrics.get(TestAppInstallSetup.class.getName()));
+ assertNull(metrics.get(BaseTargetPreparer.class.getName()));
+ }
+
+ private Map<String, Long> logMetric(
+ ThreadGroup testGroup, boolean trackWithParents, Class<?> toTrack) throws Exception {
+ TestRunnable runnable = new TestRunnable(toTrack, trackWithParents);
+ Thread testThread = new Thread(testGroup, runnable);
+ testThread.setName("TfObjectTrackerTest-test-thread");
+ testThread.setDaemon(true);
+ testThread.start();
+ testThread.join(10000);
+ return runnable.getUsageMap();
+ }
+
+ private class TestRunnable implements Runnable {
+
+ private Class<?> mToTrack;
+ private Map<String, Long> mResultMap;
+ private boolean mTrackWithParents;
+
+ public TestRunnable(Class<?> toTrack, boolean trackWithParents) {
+ mToTrack = toTrack;
+ mTrackWithParents = trackWithParents;
+ }
+
+ @Override
+ public void run() {
+ if (mTrackWithParents) {
+ TfObjectTracker.countWithParents(mToTrack);
+ } else {
+ TfObjectTracker.count(mToTrack);
+ }
+ mResultMap = TfObjectTracker.getUsage();
+ }
+
+ public Map<String, Long> getUsageMap() {
+ return mResultMap;
+ }
+ }
+}