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.invoker.ExecutionFiles; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.result.ActionInProgress; 22 import com.android.tradefed.result.FailureDescription; 23 import com.android.tradefed.result.error.ErrorIdentifier; 24 25 import java.io.File; 26 import java.lang.StackWalker.Option; 27 import java.util.HashMap; 28 import java.util.Map; 29 import java.util.Optional; 30 import java.util.concurrent.ConcurrentHashMap; 31 32 import javax.annotation.Nullable; 33 34 /** 35 * A class that tracks and provides the current invocation information useful anywhere inside the 36 * invocation. 37 */ 38 public class CurrentInvocation { 39 40 /** Some special named key that we will always populate for the invocation. */ 41 public enum InvocationInfo { 42 WORK_FOLDER("work_folder"); 43 44 private final String mKeyName; 45 InvocationInfo(String key)46 private InvocationInfo(String key) { 47 mKeyName = key; 48 } 49 50 @Override toString()51 public String toString() { 52 return mKeyName; 53 } 54 } 55 56 /** Describes the level of isolation */ 57 public enum IsolationGrade { 58 NOT_ISOLATED, // No action were taken to isolate the test. 59 REBOOT_ISOLATED, // Reboot was done before the test. 60 FULLY_ISOLATED; // Test received a fresh device. 61 } 62 CurrentInvocation()63 private CurrentInvocation() {} 64 65 /** Internal storage of the invocation values. */ 66 private static class InternalInvocationTracking { 67 public Map<InvocationInfo, File> mInvocationInfoFiles = new HashMap<>(); 68 public ExecutionFiles mExecutionFiles; 69 public ActionInProgress mActionInProgress = ActionInProgress.UNSET; 70 public IsolationGrade mIsModuleIsolated = IsolationGrade.FULLY_ISOLATED; 71 public IsolationGrade mIsRunIsolated = IsolationGrade.FULLY_ISOLATED; 72 public IInvocationContext mContext; 73 } 74 75 /** 76 * Track info per ThreadGroup as a proxy to invocation since an invocation run within one 77 * threadgroup. 78 */ 79 private static final Map<ThreadGroup, InternalInvocationTracking> mPerGroupInfo = 80 new ConcurrentHashMap<ThreadGroup, CurrentInvocation.InternalInvocationTracking>(); 81 82 private static final Map<ThreadGroup, Map<InvocationLocal<?>, Optional<?>>> mInvocationLocals = 83 new ConcurrentHashMap<>(); 84 85 private static ThreadLocal<ThreadGroup> sLocal = new ThreadLocal<>(); 86 87 /** Tracks a localized context when using the properties inside the gRPC server */ setLocalGroup(ThreadGroup tg)88 public static void setLocalGroup(ThreadGroup tg) { 89 sLocal.set(tg); 90 } 91 92 /** Resets the localized context. */ resetLocalGroup()93 public static void resetLocalGroup() { 94 sLocal.remove(); 95 } 96 97 /** 98 * Add one key-value to be tracked at the invocation level. 99 * 100 * @param key The key under which the invocation info will be tracked. 101 * @param value The value of the invocation metric. 102 */ addInvocationInfo(InvocationInfo key, File value)103 public static void addInvocationInfo(InvocationInfo key, File value) { 104 ThreadGroup group = Thread.currentThread().getThreadGroup(); 105 synchronized (mPerGroupInfo) { 106 if (mPerGroupInfo.get(group) == null) { 107 mPerGroupInfo.put(group, new InternalInvocationTracking()); 108 } 109 mPerGroupInfo.get(group).mInvocationInfoFiles.put(key, value); 110 } 111 } 112 113 /** Returns the Map of invocation metrics for the invocation in progress. */ getInfo(InvocationInfo key)114 public static File getInfo(InvocationInfo key) { 115 ThreadGroup group = Thread.currentThread().getThreadGroup(); 116 synchronized (mPerGroupInfo) { 117 if (sLocal.get() != null) { 118 group = sLocal.get(); 119 } 120 if (mPerGroupInfo.get(group) == null) { 121 mPerGroupInfo.put(group, new InternalInvocationTracking()); 122 } 123 return mPerGroupInfo.get(group).mInvocationInfoFiles.get(key); 124 } 125 } 126 127 /** Returns the current work folder for the invocation or null if none set yet. */ getWorkFolder()128 public static File getWorkFolder() { 129 File workfolder = getInfo(InvocationInfo.WORK_FOLDER); 130 if (workfolder == null || !workfolder.exists()) { 131 return null; 132 } 133 return workfolder; 134 } 135 136 /** Clear the invocation info for an invocation. */ clearInvocationInfos()137 public static void clearInvocationInfos() { 138 ThreadGroup group = Thread.currentThread().getThreadGroup(); 139 synchronized (mPerGroupInfo) { 140 mPerGroupInfo.remove(group); 141 } 142 mInvocationLocals.remove(group); 143 } 144 145 /** 146 * One-time registration of the {@link ExecutionFiles}. This is done by the Test Harness. 147 * 148 * @param invocFiles The registered {@link ExecutionFiles}. 149 */ registerExecutionFiles(ExecutionFiles invocFiles)150 public static void registerExecutionFiles(ExecutionFiles invocFiles) { 151 ThreadGroup group = Thread.currentThread().getThreadGroup(); 152 synchronized (mPerGroupInfo) { 153 if (mPerGroupInfo.get(group) == null) { 154 mPerGroupInfo.put(group, new InternalInvocationTracking()); 155 } 156 if (mPerGroupInfo.get(group).mExecutionFiles == null) { 157 mPerGroupInfo.get(group).mExecutionFiles = invocFiles; 158 } else { 159 CLog.w( 160 "CurrentInvocation#registerExecutionFiles should only be called once " 161 + "per invocation."); 162 } 163 } 164 } 165 166 /** Returns the {@link ExecutionFiles} for the invocation. */ getInvocationFiles()167 public static ExecutionFiles getInvocationFiles() { 168 ThreadGroup group = Thread.currentThread().getThreadGroup(); 169 synchronized (mPerGroupInfo) { 170 if (mPerGroupInfo.get(group) == null) { 171 mPerGroupInfo.put(group, new InternalInvocationTracking()); 172 } 173 return mPerGroupInfo.get(group).mExecutionFiles; 174 } 175 } 176 177 /** Sets the {@link ActionInProgress} for the invocation. */ setActionInProgress(ActionInProgress action)178 public static void setActionInProgress(ActionInProgress action) { 179 ThreadGroup group = Thread.currentThread().getThreadGroup(); 180 synchronized (mPerGroupInfo) { 181 if (mPerGroupInfo.get(group) == null) { 182 mPerGroupInfo.put(group, new InternalInvocationTracking()); 183 } 184 mPerGroupInfo.get(group).mActionInProgress = action; 185 } 186 } 187 188 /** Returns the current {@link ActionInProgress} for the invocation. Can be null. */ getActionInProgress()189 public static @Nullable ActionInProgress getActionInProgress() { 190 ThreadGroup group = Thread.currentThread().getThreadGroup(); 191 synchronized (mPerGroupInfo) { 192 if (mPerGroupInfo.get(group) == null) { 193 return null; 194 } 195 return mPerGroupInfo.get(group).mActionInProgress; 196 } 197 } 198 199 /** Sets the {@link IInvocationContext} for the invocation. */ setInvocationContext(IInvocationContext context)200 public static void setInvocationContext(IInvocationContext context) { 201 ThreadGroup group = Thread.currentThread().getThreadGroup(); 202 synchronized (mPerGroupInfo) { 203 if (mPerGroupInfo.get(group) == null) { 204 mPerGroupInfo.put(group, new InternalInvocationTracking()); 205 } 206 mPerGroupInfo.get(group).mContext = context; 207 } 208 } 209 210 /** Returns the current {@link IInvocationContext} for the invocation. Can be null. */ getInvocationContext()211 public static @Nullable IInvocationContext getInvocationContext() { 212 ThreadGroup group = Thread.currentThread().getThreadGroup(); 213 synchronized (mPerGroupInfo) { 214 if (mPerGroupInfo.get(group) == null) { 215 return null; 216 } 217 return mPerGroupInfo.get(group).mContext; 218 } 219 } 220 221 /** Returns whether the current suite module executed was isolated or not. */ moduleCurrentIsolation()222 public static IsolationGrade moduleCurrentIsolation() { 223 ThreadGroup group = Thread.currentThread().getThreadGroup(); 224 synchronized (mPerGroupInfo) { 225 if (mPerGroupInfo.get(group) == null) { 226 return IsolationGrade.NOT_ISOLATED; 227 } 228 return mPerGroupInfo.get(group).mIsModuleIsolated; 229 } 230 } 231 232 /** Update whether the suite module is isolated or not. */ setModuleIsolation(IsolationGrade isolation)233 public static void setModuleIsolation(IsolationGrade isolation) { 234 ThreadGroup group = Thread.currentThread().getThreadGroup(); 235 synchronized (mPerGroupInfo) { 236 if (mPerGroupInfo.get(group) == null) { 237 mPerGroupInfo.put(group, new InternalInvocationTracking()); 238 } 239 mPerGroupInfo.get(group).mIsModuleIsolated = isolation; 240 } 241 } 242 243 /** Returns whether the current test run executed was isolated or not. */ runCurrentIsolation()244 public static IsolationGrade runCurrentIsolation() { 245 ThreadGroup group = Thread.currentThread().getThreadGroup(); 246 synchronized (mPerGroupInfo) { 247 if (mPerGroupInfo.get(group) == null) { 248 return IsolationGrade.NOT_ISOLATED; 249 } 250 return mPerGroupInfo.get(group).mIsRunIsolated; 251 } 252 } 253 254 /** Update whether the test run is isolated or not. */ setRunIsolation(IsolationGrade isolation)255 public static void setRunIsolation(IsolationGrade isolation) { 256 ThreadGroup group = Thread.currentThread().getThreadGroup(); 257 synchronized (mPerGroupInfo) { 258 if (mPerGroupInfo.get(group) == null) { 259 mPerGroupInfo.put(group, new InternalInvocationTracking()); 260 } 261 mPerGroupInfo.get(group).mIsRunIsolated = isolation; 262 } 263 } 264 265 /** 266 * Create a failure associated with the invocation action in progress. Convenience utility to 267 * avoid calling {@link FailureDescription#setActionInProgress(ActionInProgress)}. 268 */ createFailure( String errorMessage, ErrorIdentifier errorIdentifier)269 public static FailureDescription createFailure( 270 String errorMessage, ErrorIdentifier errorIdentifier) { 271 FailureDescription failure = FailureDescription.create(errorMessage); 272 ActionInProgress action = getActionInProgress(); 273 if (action != null) { 274 failure.setActionInProgress(action); 275 } 276 if (errorIdentifier != null) { 277 failure.setErrorIdentifier(errorIdentifier); 278 failure.setFailureStatus(errorIdentifier.status()); 279 } 280 // Automatically populate the origin 281 Class<?> clazz = StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass(); 282 failure.setOrigin(clazz.getCanonicalName()); 283 return failure; 284 } 285 getLocal(InvocationLocal<T> local)286 static <T> T getLocal(InvocationLocal<T> local) { 287 ThreadGroup group = Thread.currentThread().getThreadGroup(); 288 Map<InvocationLocal<?>, Optional<?>> locals = 289 mInvocationLocals.computeIfAbsent(group, unused -> new ConcurrentHashMap<>()); 290 291 // Note that ConcurrentHashMap guarantees that the function is atomic and called at-most 292 // once. 293 Optional<?> holder = 294 locals.computeIfAbsent(local, unused -> Optional.ofNullable(local.initialValue())); 295 296 @SuppressWarnings("unchecked") 297 T value = (T) holder.orElse(null); 298 return value; 299 } 300 } 301