1 /* 2 * Copyright (C) 2018 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.postprocessor; 17 18 import com.android.tradefed.config.Option; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.invoker.logger.CurrentInvocation; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 22 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 23 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; 26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 27 import com.android.tradefed.result.FailureDescription; 28 import com.android.tradefed.result.FileInputStreamSource; 29 import com.android.tradefed.result.ILogSaver; 30 import com.android.tradefed.result.ILogSaverListener; 31 import com.android.tradefed.result.ITestInvocationListener; 32 import com.android.tradefed.result.InputStreamSource; 33 import com.android.tradefed.result.LogDataType; 34 import com.android.tradefed.result.LogFile; 35 import com.android.tradefed.result.TestDescription; 36 import com.android.tradefed.result.skipped.SkipReason; 37 import com.android.tradefed.util.FileUtil; 38 import com.android.tradefed.util.proto.TfMetricProtoUtil; 39 40 import com.google.common.base.Strings; 41 import com.google.common.collect.ArrayListMultimap; 42 import com.google.common.collect.ListMultimap; 43 44 import java.io.File; 45 import java.io.IOException; 46 import java.util.HashMap; 47 import java.util.HashSet; 48 import java.util.LinkedHashMap; 49 import java.util.Map; 50 import java.util.Map.Entry; 51 import java.util.Set; 52 53 /** 54 * The base {@link IPostProcessor} that every implementation should extend. Ensure that the post 55 * processing methods are called before the final result reporters. 56 */ 57 public abstract class BasePostProcessor implements IPostProcessor { 58 59 @Option(name = "disable", description = "disables the post processor.") 60 private boolean mDisable = false; 61 62 private ITestInvocationListener mForwarder; 63 private ArrayListMultimap<String, Metric> storedTestMetrics = ArrayListMultimap.create(); 64 private Map<TestDescription, Map<String, LogFile>> mTestLogs = new LinkedHashMap<>(); 65 private Map<String, LogFile> mRunLogs = new HashMap<>(); 66 67 private Map<String, LogFile> mCopiedLogs = new HashMap<>(); 68 private Set<LogFile> mToDelete = new HashSet<>(); 69 70 // Keeps track of the current test; takes null value when the post processor is not in the scope 71 // of any test (i.e. before the first test, in-between tests and after the last test). 72 private TestDescription mCurrentTest = null; 73 74 private ILogSaver mLogSaver = null; 75 76 // Marks whether the post processor is actively post processing, which changes some event 77 // forwarding behavior. 78 private boolean mIsPostProcessing = false; 79 80 private String mRunName; 81 82 /** {@inheritDoc} */ 83 @Override processRunMetricsAndLogs( HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs)84 public abstract Map<String, Metric.Builder> processRunMetricsAndLogs( 85 HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs); 86 87 /** {@inheritDoc} */ 88 @Override processTestMetricsAndLogs( TestDescription testDescription, HashMap<String, Metric> testMetrics, Map<String, LogFile> testLogs)89 public Map<String, Metric.Builder> processTestMetricsAndLogs( 90 TestDescription testDescription, 91 HashMap<String, Metric> testMetrics, 92 Map<String, LogFile> testLogs) { 93 return new HashMap<String, Metric.Builder>(); 94 } 95 96 /** {@inheritDoc} */ 97 @Override processAllTestMetricsAndLogs( ListMultimap<String, Metric> allTestMetrics, Map<TestDescription, Map<String, LogFile>> allTestLogs)98 public Map<String, Metric.Builder> processAllTestMetricsAndLogs( 99 ListMultimap<String, Metric> allTestMetrics, 100 Map<TestDescription, Map<String, LogFile>> allTestLogs) { 101 return new HashMap<String, Metric.Builder>(); 102 } 103 104 /** =================================== */ 105 /** {@inheritDoc} */ 106 @Override init(ITestInvocationListener listener)107 public final ITestInvocationListener init(ITestInvocationListener listener) { 108 long start = System.currentTimeMillis(); 109 setUp(); 110 InvocationMetricLogger.addInvocationMetrics( 111 InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start); 112 mForwarder = listener; 113 return this; 114 } 115 116 /** =================================== */ 117 /** {@inheritDoc} */ 118 @Override isDisabled()119 public final boolean isDisabled() { 120 return mDisable; 121 } 122 123 /** =================================== */ 124 /** Invocation Listeners for forwarding */ 125 @Override invocationStarted(IInvocationContext context)126 public final void invocationStarted(IInvocationContext context) { 127 mForwarder.invocationStarted(context); 128 } 129 130 @Override invocationFailed(Throwable cause)131 public final void invocationFailed(Throwable cause) { 132 mForwarder.invocationFailed(cause); 133 } 134 135 @Override invocationFailed(FailureDescription failure)136 public final void invocationFailed(FailureDescription failure) { 137 mForwarder.invocationFailed(failure); 138 } 139 140 @Override invocationSkipped(SkipReason reason)141 public void invocationSkipped(SkipReason reason) { 142 mForwarder.invocationSkipped(reason); 143 } 144 145 @Override invocationEnded(long elapsedTime)146 public final void invocationEnded(long elapsedTime) { 147 mForwarder.invocationEnded(elapsedTime); 148 } 149 150 /** Use this method to log a file from the PostProcessor implementation. */ 151 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)152 public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 153 // If currently post processing, the file should be saved; otherwise, the file should have 154 // been logged elsewhere and this call only needs to be forwarded. 155 if (mIsPostProcessing) { 156 CLog.i("Saving file with data name %s in post processor.", dataName); 157 if (mLogSaver != null) { 158 try { 159 LogFile log = null; 160 if (dataStream instanceof FileInputStreamSource) { 161 log = 162 mLogSaver.saveLogFile( 163 dataName, 164 dataType, 165 ((FileInputStreamSource) dataStream).getFile()); 166 } else { 167 log = 168 mLogSaver.saveLogData( 169 dataName, dataType, dataStream.createInputStream()); 170 } 171 testLogSaved(dataName, dataType, dataStream, log); 172 logAssociation(dataName, log); 173 // do not forward if log saved 174 return; 175 } catch (IOException e) { 176 CLog.e("Failed to save log file %s.", dataName); 177 CLog.e(e); 178 } 179 } else { 180 CLog.e("Attempting to save log in post processor when its log saver is not set."); 181 } 182 } 183 mForwarder.testLog(dataName, dataType, dataStream); 184 } 185 186 @Override testModuleStarted(IInvocationContext moduleContext)187 public final void testModuleStarted(IInvocationContext moduleContext) { 188 mForwarder.testModuleStarted(moduleContext); 189 } 190 191 @Override testModuleEnded()192 public final void testModuleEnded() { 193 mForwarder.testModuleEnded(); 194 } 195 196 /** Test run callbacks */ 197 @Override testRunStarted(String runName, int testCount)198 public final void testRunStarted(String runName, int testCount) { 199 testRunStarted(runName, testCount, 0, System.currentTimeMillis()); 200 } 201 202 @Override testRunStarted(String runName, int testCount, int attemptNumber)203 public final void testRunStarted(String runName, int testCount, int attemptNumber) { 204 testRunStarted(runName, testCount, attemptNumber, System.currentTimeMillis()); 205 } 206 207 @Override testRunStarted( String runName, int testCount, int attemptNumber, long startTime)208 public final void testRunStarted( 209 String runName, int testCount, int attemptNumber, long startTime) { 210 mRunName = runName; 211 mForwarder.testRunStarted(runName, testCount, attemptNumber, startTime); 212 } 213 214 @Override testRunFailed(String errorMessage)215 public final void testRunFailed(String errorMessage) { 216 mForwarder.testRunFailed(errorMessage); 217 } 218 219 @Override testRunFailed(FailureDescription failure)220 public final void testRunFailed(FailureDescription failure) { 221 mForwarder.testRunFailed(failure); 222 } 223 224 @Override testRunStopped(long elapsedTime)225 public final void testRunStopped(long elapsedTime) { 226 mForwarder.testRunStopped(elapsedTime); 227 } 228 229 @Override testRunEnded(long elapsedTime, Map<String, String> runMetrics)230 public final void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { 231 testRunEnded(elapsedTime, TfMetricProtoUtil.upgradeConvert(runMetrics)); 232 } 233 234 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)235 public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 236 long start = System.currentTimeMillis(); 237 mIsPostProcessing = true; 238 try (CloseableTraceScope ignored = 239 new CloseableTraceScope("run_processor_" + this.getClass().getSimpleName())) { 240 HashMap<String, Metric> rawValues = getRawMetricsOnly(runMetrics); 241 // Add post-processed run metrics. 242 Map<String, Metric.Builder> postprocessedResults = 243 processRunMetricsAndLogs(rawValues, mRunLogs); 244 addProcessedMetricsToExistingMetrics(postprocessedResults, runMetrics); 245 // Add aggregated test metrics (results from post-processing all test metrics). 246 Map<String, Metric.Builder> aggregateResults = 247 processAllTestMetricsAndLogs(storedTestMetrics, mTestLogs); 248 addProcessedMetricsToExistingMetrics(aggregateResults, runMetrics); 249 } catch (RuntimeException e) { 250 // Prevent exception from messing up the status reporting. 251 CLog.e(e); 252 } finally { 253 // Clear out the stored test metrics. 254 storedTestMetrics.clear(); 255 // Clear out the stored test and run logs. 256 mTestLogs.clear(); 257 mRunLogs.clear(); 258 // Delete all the copies 259 cleanUp(); 260 InvocationMetricLogger.addInvocationMetrics( 261 InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start); 262 } 263 mIsPostProcessing = false; 264 mForwarder.testRunEnded(elapsedTime, runMetrics); 265 } 266 267 /** Test cases callbacks */ 268 @Override testStarted(TestDescription test)269 public final void testStarted(TestDescription test) { 270 testStarted(test, System.currentTimeMillis()); 271 } 272 273 @Override testStarted(TestDescription test, long startTime)274 public final void testStarted(TestDescription test, long startTime) { 275 mCurrentTest = test; 276 if (mTestLogs.containsKey(test)) { 277 mTestLogs.get(test).clear(); 278 } else { 279 mTestLogs.put(test, new HashMap<String, LogFile>()); 280 } 281 mForwarder.testStarted(test, startTime); 282 } 283 284 @Override testFailed(TestDescription test, String trace)285 public final void testFailed(TestDescription test, String trace) { 286 mForwarder.testFailed(test, trace); 287 } 288 289 @Override testFailed(TestDescription test, FailureDescription failure)290 public final void testFailed(TestDescription test, FailureDescription failure) { 291 mForwarder.testFailed(test, failure); 292 } 293 294 @Override testEnded(TestDescription test, Map<String, String> testMetrics)295 public final void testEnded(TestDescription test, Map<String, String> testMetrics) { 296 testEnded(test, System.currentTimeMillis(), testMetrics); 297 } 298 299 @Override testEnded( TestDescription test, long endTime, Map<String, String> testMetrics)300 public final void testEnded( 301 TestDescription test, long endTime, Map<String, String> testMetrics) { 302 testEnded(test, endTime, TfMetricProtoUtil.upgradeConvert(testMetrics)); 303 } 304 305 @Override testEnded(TestDescription test, HashMap<String, Metric> testMetrics)306 public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) { 307 testEnded(test, System.currentTimeMillis(), testMetrics); 308 } 309 310 @Override testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)311 public final void testEnded( 312 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 313 mIsPostProcessing = true; 314 long start = System.currentTimeMillis(); 315 try { 316 HashMap<String, Metric> rawValues = getRawMetricsOnly(testMetrics); 317 // Store the raw metrics from the test in storedTestMetrics for potential aggregation. 318 for (Map.Entry<String, Metric> entry : rawValues.entrySet()) { 319 storedTestMetrics.put(entry.getKey(), entry.getValue()); 320 } 321 Map<String, Metric.Builder> results = 322 processTestMetricsAndLogs( 323 test, 324 rawValues, 325 mTestLogs.containsKey(test) 326 ? mTestLogs.get(test) 327 : new HashMap<String, LogFile>()); 328 for (Entry<String, Metric.Builder> newEntry : results.entrySet()) { 329 String newKey = newEntry.getKey(); 330 if (testMetrics.containsKey(newKey)) { 331 CLog.e( 332 "Key '%s' is already asssociated with a metric and will not be " 333 + "replaced.", 334 newKey); 335 continue; 336 } 337 // Force the metric to 'processed' since generated in a post-processor. 338 Metric newMetric = 339 newEntry.getValue() 340 .setType(getMetricType()) 341 .build(); 342 testMetrics.put(newKey, newMetric); 343 } 344 } catch (RuntimeException e) { 345 // Prevent exception from messing up the status reporting. 346 CLog.e(e); 347 } finally { 348 InvocationMetricLogger.addInvocationMetrics( 349 InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start); 350 } 351 mIsPostProcessing = false; 352 mCurrentTest = null; 353 mForwarder.testEnded(test, endTime, testMetrics); 354 } 355 356 @Override testAssumptionFailure(TestDescription test, String trace)357 public final void testAssumptionFailure(TestDescription test, String trace) { 358 mForwarder.testAssumptionFailure(test, trace); 359 } 360 361 @Override testAssumptionFailure(TestDescription test, FailureDescription failure)362 public final void testAssumptionFailure(TestDescription test, FailureDescription failure) { 363 mForwarder.testAssumptionFailure(test, failure); 364 } 365 366 @Override testIgnored(TestDescription test)367 public final void testIgnored(TestDescription test) { 368 mForwarder.testIgnored(test); 369 } 370 371 @Override testSkipped(TestDescription test, SkipReason reason)372 public final void testSkipped(TestDescription test, SkipReason reason) { 373 mForwarder.testSkipped(test, reason); 374 } 375 376 /** 377 * Override this method in the child post processors to initialize before the test runs. 378 */ setUp()379 public void setUp() { 380 // NO-OP by default 381 } 382 383 @Override setLogSaver(ILogSaver logSaver)384 public final void setLogSaver(ILogSaver logSaver) { 385 mLogSaver = logSaver; 386 if (mForwarder instanceof ILogSaverListener) { 387 ((ILogSaverListener) mForwarder).setLogSaver(logSaver); 388 } 389 } 390 391 @Override testLogSaved( String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile)392 public final void testLogSaved( 393 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) { 394 if (!mIsPostProcessing) { 395 mCopiedLogs.put(dataName, copyLogFile(logFile)); 396 } 397 if (mForwarder instanceof ILogSaverListener) { 398 ((ILogSaverListener) mForwarder).testLogSaved(dataName, dataType, dataStream, logFile); 399 } 400 } 401 402 /** 403 * {@inheritDoc} 404 * 405 * <p>Updates the log-to-test assocation. If this method is called during a test, then the log 406 * belongs to the test; otherwise it will be a run log. 407 */ 408 @Override logAssociation(String dataName, LogFile logFile)409 public final void logAssociation(String dataName, LogFile logFile) { 410 // Only associate files created outside of the current post processor. 411 if (!mIsPostProcessing) { 412 LogFile copyFile = mCopiedLogs.remove(dataName); 413 if (copyFile != null) { 414 mToDelete.add(copyFile); 415 // mCurrentTest is null only outside the scope of a test. 416 if (mCurrentTest != null) { 417 mTestLogs.get(mCurrentTest).put(dataName, copyFile); 418 } else { 419 mRunLogs.put(dataName, copyFile); 420 } 421 } 422 } 423 424 if (mForwarder instanceof ILogSaverListener) { 425 ((ILogSaverListener) mForwarder).logAssociation(dataName, logFile); 426 } 427 } 428 429 // Internal utilities 430 431 /** 432 * We only allow post-processing of raw values. Already processed values will not be considered. 433 */ getRawMetricsOnly(HashMap<String, Metric> runMetrics)434 private HashMap<String, Metric> getRawMetricsOnly(HashMap<String, Metric> runMetrics) { 435 HashMap<String, Metric> rawMetrics = new HashMap<>(); 436 for (Entry<String, Metric> entry : runMetrics.entrySet()) { 437 if (DataType.RAW.equals(entry.getValue().getType())) { 438 rawMetrics.put(entry.getKey(), entry.getValue()); 439 } 440 } 441 return rawMetrics; 442 } 443 444 /** Add processed metrics to the metrics to be reported. */ addProcessedMetricsToExistingMetrics( Map<String, Metric.Builder> processed, Map<String, Metric> existing)445 private void addProcessedMetricsToExistingMetrics( 446 Map<String, Metric.Builder> processed, Map<String, Metric> existing) { 447 for (Entry<String, Metric.Builder> newEntry : processed.entrySet()) { 448 String newKey = newEntry.getKey(); 449 if (existing.containsKey(newKey)) { 450 CLog.e( 451 "Key '%s' is already asssociated with a metric and will not be " 452 + "replaced.", 453 newKey); 454 continue; 455 } 456 // Force the metric to 'processed' since generated in a post-processor. 457 Metric newMetric = 458 newEntry.getValue() 459 .setType(getMetricType()) 460 .build(); 461 existing.put(newKey, newMetric); 462 } 463 } 464 465 /** 466 * Override this method to change the metric type if needed. By default metric is set to 467 * processed type. 468 */ getMetricType()469 protected DataType getMetricType() { 470 return DataType.PROCESSED; 471 } 472 473 /* 474 * TODO: b/191168103 Remove this method after run name dependency is removed from metric file 475 * post processor. 476 */ getRunName()477 protected String getRunName() { 478 return mRunName; 479 } 480 cleanUp()481 protected void cleanUp() { 482 // Delete all the copies 483 for (LogFile f : mToDelete) { 484 FileUtil.deleteFile(new File(f.getPath())); 485 } 486 mToDelete.clear(); 487 } 488 copyLogFile(LogFile original)489 private LogFile copyLogFile(LogFile original) { 490 if (Strings.isNullOrEmpty(original.getPath())) { 491 CLog.d("%s doesn't exist and can't be copied for post processor.", original); 492 return null; 493 } 494 File originalFile = new File(original.getPath()); 495 if (!originalFile.exists()) { 496 CLog.d("%s doesn't exist and can't be copied for post processor.", original.getPath()); 497 return null; 498 } 499 try { 500 File copy = 501 FileUtil.createTempFile( 502 originalFile.getName(), "", CurrentInvocation.getWorkFolder()); 503 copy.delete(); 504 FileUtil.hardlinkFile(originalFile, copy); 505 return new LogFile( 506 copy.getAbsolutePath(), 507 original.getUrl(), 508 original.isCompressed(), 509 original.getType(), 510 original.getSize()); 511 } catch (IOException e) { 512 CLog.e(e); 513 } 514 return null; 515 } 516 } 517