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.result.proto; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.invoker.IInvocationContext; 20 import com.android.tradefed.invoker.InvocationContext; 21 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 22 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 23 import com.android.tradefed.invoker.logger.TfObjectTracker; 24 import com.android.tradefed.invoker.proto.InvocationContext.Context; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 27 import com.android.tradefed.result.ActionInProgress; 28 import com.android.tradefed.result.FailureDescription; 29 import com.android.tradefed.result.FileInputStreamSource; 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.error.ErrorIdentifier; 37 import com.android.tradefed.result.proto.LogFileProto.LogFileInfo; 38 import com.android.tradefed.result.proto.TestRecordProto.ChildReference; 39 import com.android.tradefed.result.proto.TestRecordProto.DebugInfo; 40 import com.android.tradefed.result.proto.TestRecordProto.DebugInfoContext; 41 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 42 import com.android.tradefed.result.proto.TestRecordProto.TestRecord; 43 import com.android.tradefed.testtype.suite.ModuleDefinition; 44 import com.android.tradefed.util.MultiMap; 45 import com.android.tradefed.util.SerializationUtil; 46 import com.android.tradefed.util.proto.TestRecordProtoUtil; 47 48 import com.google.common.base.Splitter; 49 import com.google.common.base.Strings; 50 import com.google.protobuf.Any; 51 import com.google.protobuf.InvalidProtocolBufferException; 52 import com.google.protobuf.Timestamp; 53 54 import java.io.File; 55 import java.io.IOException; 56 import java.lang.reflect.InvocationTargetException; 57 import java.lang.reflect.Method; 58 import java.util.HashMap; 59 import java.util.List; 60 import java.util.Map.Entry; 61 62 /** Parser for the Tradefed results proto format. */ 63 public class ProtoResultParser { 64 65 private ITestInvocationListener mListener; 66 private String mCurrentRunName = null; 67 /** 68 * We don't always want to report the invocation level events again. If we are within an 69 * invocation scope we should not report it again. 70 */ 71 private boolean mReportInvocation = false; 72 /** Prefix that will be added to the files logged through the parser. */ 73 private String mFilePrefix; 74 /** The context from the invocation in progress, not the proto one. */ 75 private IInvocationContext mMainContext; 76 77 private boolean mQuietParsing = true; 78 79 private boolean mInvocationStarted = false; 80 private boolean mInvocationEnded = false; 81 private boolean mFirstModule = true; 82 /** Track the name of the module in progress. */ 83 private String mModuleInProgress = null; 84 85 /** Ctor. */ ProtoResultParser( ITestInvocationListener listener, IInvocationContext context, boolean reportInvocation)86 public ProtoResultParser( 87 ITestInvocationListener listener, 88 IInvocationContext context, 89 boolean reportInvocation) { 90 this(listener, context, reportInvocation, "subprocess-"); 91 } 92 93 /** Ctor. */ ProtoResultParser( ITestInvocationListener listener, IInvocationContext context, boolean reportInvocation, String prefixForFile)94 public ProtoResultParser( 95 ITestInvocationListener listener, 96 IInvocationContext context, 97 boolean reportInvocation, 98 String prefixForFile) { 99 mListener = listener; 100 mMainContext = context; 101 mReportInvocation = reportInvocation; 102 mFilePrefix = prefixForFile; 103 } 104 105 /** Enumeration representing the current level of the proto being processed. */ 106 public enum TestLevel { 107 INVOCATION, 108 MODULE, 109 TEST_RUN, 110 TEST_CASE 111 } 112 113 /** Sets whether or not to print when events are received. */ setQuiet(boolean quiet)114 public void setQuiet(boolean quiet) { 115 mQuietParsing = quiet; 116 } 117 118 /** 119 * Main entry function that takes the finalized completed proto and replay its results. 120 * 121 * @param finalProto The final {@link TestRecord} to be parsed. 122 */ processFinalizedProto(TestRecord finalProto)123 public void processFinalizedProto(TestRecord finalProto) { 124 if (!finalProto.getParentTestRecordId().isEmpty()) { 125 throw new IllegalArgumentException("processFinalizedProto only expect a root proto."); 126 } 127 128 // Invocation Start 129 handleInvocationStart(finalProto); 130 131 evalChildrenProto(finalProto.getChildrenList(), false); 132 // Invocation End 133 handleInvocationEnded(finalProto); 134 } 135 136 /** 137 * Main entry function where each proto is presented to get parsed into Tradefed events. 138 * 139 * @param currentProto The current {@link TestRecord} to be parsed. 140 * @return True if the proto processed was a module. 141 */ processNewProto(TestRecord currentProto)142 public TestLevel processNewProto(TestRecord currentProto) { 143 // Handle initial root proto 144 if (currentProto.getParentTestRecordId().isEmpty()) { 145 handleRootProto(currentProto); 146 return TestLevel.INVOCATION; 147 } else if (currentProto.hasDescription()) { 148 // If it has a Any Description with Context then it's a module 149 handleModuleProto(currentProto); 150 return TestLevel.MODULE; 151 } else if (mCurrentRunName == null 152 || currentProto.getTestRecordId().equals(mCurrentRunName)) { 153 // Need to track the parent test run id to make sure we need testRunEnd or testRunFail 154 handleTestRun(currentProto); 155 return TestLevel.TEST_RUN; 156 } else { 157 // Test cases handling 158 handleTestCase(currentProto); 159 return TestLevel.TEST_CASE; 160 } 161 } 162 163 /** 164 * In case of parsing proto files directly, handle direct parsing of them as a sequence. 165 * Associated with {@link FileProtoResultReporter} when reporting a sequence of files. 166 * 167 * @param protoFile The proto file to be parsed. 168 * @throws IOException 169 */ processFileProto(File protoFile)170 public void processFileProto(File protoFile) throws IOException { 171 TestRecord record = null; 172 try { 173 record = TestRecordProtoUtil.readFromFile(protoFile); 174 } catch (InvalidProtocolBufferException e) { 175 // Log the proto that failed to parse 176 try (FileInputStreamSource protoFail = new FileInputStreamSource(protoFile, true)) { 177 mListener.testLog("failed-result-protobuf", LogDataType.PB, protoFail); 178 } 179 throw e; 180 } 181 if (!mInvocationStarted) { 182 handleInvocationStart(record); 183 mInvocationStarted = true; 184 } else if (record.getParentTestRecordId().isEmpty()) { 185 handleInvocationEnded(record); 186 } else { 187 evalProto(record, false); 188 } 189 } 190 191 /** Returns whether or not the parsing reached an invocation ended. */ invocationEndedReached()192 public boolean invocationEndedReached() { 193 return mInvocationEnded; 194 } 195 196 /** Returns the id of the module in progress. Returns null if none in progress. */ getModuleInProgress()197 public String getModuleInProgress() { 198 return mModuleInProgress; 199 } 200 201 /** If needed to ensure consistent reporting, complete the events of the module. */ completeModuleEvents()202 public void completeModuleEvents() { 203 if (getModuleInProgress() == null) { 204 return; 205 } 206 mListener.testRunStarted(getModuleInProgress(), 0); 207 FailureDescription failure = 208 FailureDescription.create( 209 "Module was interrupted after starting, results are incomplete.", 210 FailureStatus.INFRA_FAILURE); 211 mListener.testRunFailed(failure); 212 mListener.testRunEnded(0L, new HashMap<String, Metric>()); 213 mListener.testModuleEnded(); 214 } 215 evalChildrenProto(List<ChildReference> children, boolean isInRun)216 private void evalChildrenProto(List<ChildReference> children, boolean isInRun) { 217 for (ChildReference child : children) { 218 TestRecord childProto = child.getInlineTestRecord(); 219 evalProto(childProto, isInRun); 220 } 221 } 222 evalProto(TestRecord childProto, boolean isInRun)223 private void evalProto(TestRecord childProto, boolean isInRun) { 224 if (isInRun) { 225 // test case 226 String[] info = childProto.getTestRecordId().split("#"); 227 TestDescription description = new TestDescription(info[0], info[1]); 228 mListener.testStarted(description, timeStampToMillis(childProto.getStartTime())); 229 handleTestCaseEnd(description, childProto); 230 } else { 231 boolean inRun = false; 232 if (childProto.hasDescription()) { 233 // Module start 234 handleModuleStart(childProto); 235 } else { 236 // run start 237 handleTestRunStart(childProto); 238 inRun = true; 239 } 240 evalChildrenProto(childProto.getChildrenList(), inRun); 241 if (childProto.hasDescription()) { 242 // Module end 243 handleModuleProto(childProto); 244 } else { 245 // run end 246 handleTestRunEnd(childProto); 247 } 248 } 249 } 250 251 /** Handles the root of the invocation: They have no parent record id. */ handleRootProto(TestRecord rootProto)252 private void handleRootProto(TestRecord rootProto) { 253 if (rootProto.hasEndTime()) { 254 handleInvocationEnded(rootProto); 255 } else { 256 handleInvocationStart(rootProto); 257 } 258 } 259 handleInvocationStart(TestRecord startInvocationProto)260 private void handleInvocationStart(TestRecord startInvocationProto) { 261 // invocation starting 262 Any anyDescription = startInvocationProto.getDescription(); 263 if (!anyDescription.is(Context.class)) { 264 throw new RuntimeException("Expected Any description of type Context"); 265 } 266 IInvocationContext receivedContext; 267 try { 268 receivedContext = InvocationContext.fromProto(anyDescription.unpack(Context.class)); 269 mergeInvocationContext(mMainContext, receivedContext); 270 } catch (InvalidProtocolBufferException e) { 271 throw new RuntimeException(e); 272 } 273 274 log("Invocation started proto"); 275 if (!mReportInvocation) { 276 CLog.d("Skipping invocation start reporting."); 277 return; 278 } 279 // Only report invocation start if enabled 280 mListener.invocationStarted(receivedContext); 281 } 282 handleInvocationEnded(TestRecord endInvocationProto)283 private void handleInvocationEnded(TestRecord endInvocationProto) { 284 // Still report the logs even if not reporting the invocation level. 285 handleLogs(endInvocationProto); 286 287 // Get final context in case it changed. 288 Any anyDescription = endInvocationProto.getDescription(); 289 if (!anyDescription.is(Context.class)) { 290 throw new RuntimeException( 291 String.format( 292 "Expected Any description of type Context, was %s", anyDescription)); 293 } 294 try { 295 IInvocationContext context = 296 InvocationContext.fromProto(anyDescription.unpack(Context.class)); 297 mergeInvocationContext(mMainContext, context); 298 } catch (InvalidProtocolBufferException e) { 299 throw new RuntimeException(e); 300 } 301 302 if (endInvocationProto.hasDebugInfo()) { 303 DebugInfo debugInfo = endInvocationProto.getDebugInfo(); 304 FailureDescription failure = FailureDescription.create(debugInfo.getErrorMessage()); 305 if (!TestRecordProto.FailureStatus.UNSET.equals( 306 endInvocationProto.getDebugInfo().getFailureStatus())) { 307 failure.setFailureStatus(debugInfo.getFailureStatus()); 308 } 309 parseDebugInfoContext(endInvocationProto.getDebugInfo(), failure); 310 if (endInvocationProto.getDebugInfo().hasDebugInfoContext()) { 311 String errorType = 312 endInvocationProto.getDebugInfo().getDebugInfoContext().getErrorType(); 313 if (!Strings.isNullOrEmpty(errorType)) { 314 try { 315 Throwable invocationError = 316 (Throwable) SerializationUtil.deserialize(errorType); 317 failure.setCause(invocationError); 318 } catch (IOException e) { 319 CLog.e("Failed to deserialize the invocation exception:"); 320 CLog.e(e); 321 } 322 } 323 } 324 mListener.invocationFailed(failure); 325 } 326 327 log("Invocation ended proto"); 328 mInvocationEnded = true; 329 if (!mReportInvocation) { 330 CLog.d("Skipping invocation ended reporting."); 331 return; 332 } 333 // Only report invocation ended if enabled 334 long elapsedTime = 335 timeStampToMillis(endInvocationProto.getEndTime()) 336 - timeStampToMillis(endInvocationProto.getStartTime()); 337 mListener.invocationEnded(elapsedTime); 338 } 339 340 /** Handles module level of the invocation: They have a Description for the module context. */ handleModuleProto(TestRecord moduleProto)341 private void handleModuleProto(TestRecord moduleProto) { 342 if (moduleProto.hasEndTime()) { 343 handleModuleEnded(moduleProto); 344 } else { 345 handleModuleStart(moduleProto); 346 } 347 } 348 handleModuleStart(TestRecord moduleProto)349 private void handleModuleStart(TestRecord moduleProto) { 350 Any anyDescription = moduleProto.getDescription(); 351 if (!anyDescription.is(Context.class)) { 352 throw new RuntimeException("Expected Any description of type Context"); 353 } 354 try { 355 IInvocationContext moduleContext = 356 InvocationContext.fromProto(anyDescription.unpack(Context.class)); 357 String message = "Test module started proto"; 358 if (moduleContext.getAttributes().containsKey(ModuleDefinition.MODULE_ID)) { 359 String moduleId = 360 moduleContext 361 .getAttributes() 362 .getUniqueMap() 363 .get(ModuleDefinition.MODULE_ID); 364 message += (": " + moduleId); 365 mModuleInProgress = moduleId; 366 } 367 log(message); 368 mListener.testModuleStarted(moduleContext); 369 if (mFirstModule) { 370 mFirstModule = false; 371 // Parse the build attributes once after invocation start to update the BuildInfo 372 mergeBuildInfo(mMainContext, moduleContext); 373 } 374 } catch (InvalidProtocolBufferException e) { 375 throw new RuntimeException(e); 376 } 377 } 378 handleModuleEnded(TestRecord moduleProto)379 private void handleModuleEnded(TestRecord moduleProto) { 380 handleLogs(moduleProto); 381 log("Test module ended proto"); 382 mListener.testModuleEnded(); 383 mModuleInProgress = null; 384 } 385 386 /** Handles the test run level of the invocation. */ handleTestRun(TestRecord runProto)387 private void handleTestRun(TestRecord runProto) { 388 // If the proto end-time is present we are evaluating the end of a test run. 389 if (runProto.hasEndTime()) { 390 handleTestRunEnd(runProto); 391 mCurrentRunName = null; 392 } else { 393 // If the end-time is not populated yet we are dealing with the start of a run. 394 mCurrentRunName = runProto.getTestRecordId(); 395 handleTestRunStart(runProto); 396 } 397 } 398 handleTestRunStart(TestRecord runProto)399 private void handleTestRunStart(TestRecord runProto) { 400 String id = runProto.getTestRecordId(); 401 log( 402 "Test run started proto: %s. Expected tests: %s. Attempt: %s", 403 id, runProto.getNumExpectedChildren(), runProto.getAttemptId()); 404 mListener.testRunStarted( 405 id, 406 (int) runProto.getNumExpectedChildren(), 407 (int) runProto.getAttemptId(), 408 timeStampToMillis(runProto.getStartTime())); 409 } 410 handleTestRunEnd(TestRecord runProto)411 private void handleTestRunEnd(TestRecord runProto) { 412 // If we find debugging information, the test run failed and we reflect it. 413 if (runProto.hasDebugInfo()) { 414 DebugInfo debugInfo = runProto.getDebugInfo(); 415 FailureDescription failure = FailureDescription.create(debugInfo.getErrorMessage()); 416 if (!TestRecordProto.FailureStatus.UNSET.equals( 417 runProto.getDebugInfo().getFailureStatus())) { 418 failure.setFailureStatus(debugInfo.getFailureStatus()); 419 } 420 421 parseDebugInfoContext(debugInfo, failure); 422 423 mListener.testRunFailed(failure); 424 log("Test run failure proto: %s", failure.toString()); 425 } 426 handleLogs(runProto); 427 log("Test run ended proto: %s", runProto.getTestRecordId()); 428 long elapsedTime = 429 timeStampToMillis(runProto.getEndTime()) 430 - timeStampToMillis(runProto.getStartTime()); 431 HashMap<String, Metric> metrics = new HashMap<>(runProto.getMetricsMap()); 432 mListener.testRunEnded(elapsedTime, metrics); 433 } 434 435 /** Handles the test cases level of the invocation. */ handleTestCase(TestRecord testcaseProto)436 private void handleTestCase(TestRecord testcaseProto) { 437 String[] info = testcaseProto.getTestRecordId().split("#"); 438 TestDescription description = new TestDescription(info[0], info[1]); 439 if (testcaseProto.hasEndTime()) { 440 handleTestCaseEnd(description, testcaseProto); 441 } else { 442 log("Test case started proto: %s", description.toString()); 443 mListener.testStarted(description, timeStampToMillis(testcaseProto.getStartTime())); 444 } 445 } 446 handleTestCaseEnd(TestDescription description, TestRecord testcaseProto)447 private void handleTestCaseEnd(TestDescription description, TestRecord testcaseProto) { 448 DebugInfo debugInfo = testcaseProto.getDebugInfo(); 449 switch (testcaseProto.getStatus()) { 450 case FAIL: 451 FailureDescription failure = 452 FailureDescription.create(testcaseProto.getDebugInfo().getErrorMessage()); 453 if (!TestRecordProto.FailureStatus.UNSET.equals( 454 testcaseProto.getDebugInfo().getFailureStatus())) { 455 failure.setFailureStatus(testcaseProto.getDebugInfo().getFailureStatus()); 456 } 457 458 parseDebugInfoContext(debugInfo, failure); 459 460 mListener.testFailed(description, failure); 461 log("Test case failed proto: %s - %s", description.toString(), failure.toString()); 462 break; 463 case ASSUMPTION_FAILURE: 464 FailureDescription assumption = 465 FailureDescription.create(testcaseProto.getDebugInfo().getErrorMessage()); 466 if (!TestRecordProto.FailureStatus.UNSET.equals( 467 testcaseProto.getDebugInfo().getFailureStatus())) { 468 assumption.setFailureStatus(testcaseProto.getDebugInfo().getFailureStatus()); 469 } 470 471 parseDebugInfoContext(debugInfo, assumption); 472 473 mListener.testAssumptionFailure(description, assumption); 474 log( 475 "Test case assumption failure proto: %s - %s", 476 description.toString(), testcaseProto.getDebugInfo().getTrace()); 477 break; 478 case IGNORED: 479 mListener.testIgnored(description); 480 log("Test case ignored proto: %s", description.toString()); 481 break; 482 case PASS: 483 break; 484 default: 485 throw new RuntimeException( 486 String.format( 487 "Received unexpected test status %s.", testcaseProto.getStatus())); 488 } 489 handleLogs(testcaseProto); 490 HashMap<String, Metric> metrics = new HashMap<>(testcaseProto.getMetricsMap()); 491 log("Test case ended proto: %s", description.toString()); 492 mListener.testEnded(description, timeStampToMillis(testcaseProto.getEndTime()), metrics); 493 } 494 timeStampToMillis(Timestamp stamp)495 private long timeStampToMillis(Timestamp stamp) { 496 return stamp.getSeconds() * 1000L + (stamp.getNanos() / 1000000L); 497 } 498 handleLogs(TestRecord proto)499 private void handleLogs(TestRecord proto) { 500 if (!(mListener instanceof ILogSaverListener)) { 501 return; 502 } 503 ILogSaverListener logger = (ILogSaverListener) mListener; 504 for (Entry<String, Any> entry : proto.getArtifactsMap().entrySet()) { 505 try { 506 LogFileInfo info = entry.getValue().unpack(LogFileInfo.class); 507 LogFile file = 508 new LogFile( 509 info.getPath(), 510 info.getUrl(), 511 info.getIsCompressed(), 512 LogDataType.valueOf(info.getLogType()), 513 info.getSize()); 514 if (Strings.isNullOrEmpty(file.getPath())) { 515 CLog.e("Log '%s' was registered but without a path.", entry.getKey()); 516 return; 517 } 518 File path = new File(file.getPath()); 519 if (Strings.isNullOrEmpty(file.getUrl()) && path.exists()) { 520 try (InputStreamSource source = new FileInputStreamSource(path)) { 521 LogDataType type = file.getType(); 522 // File might have already been compressed 523 if (file.getPath().endsWith(LogDataType.ZIP.getFileExt())) { 524 type = LogDataType.ZIP; 525 } 526 log("Logging %s from subprocess: %s ", entry.getKey(), file.getPath()); 527 logger.testLog(mFilePrefix + entry.getKey(), type, source); 528 } 529 } else { 530 log( 531 "Logging %s from subprocess. url: %s, path: %s", 532 entry.getKey(), file.getUrl(), file.getPath()); 533 logger.logAssociation(mFilePrefix + entry.getKey(), file); 534 } 535 } catch (InvalidProtocolBufferException e) { 536 CLog.e("Couldn't unpack %s as a LogFileInfo", entry.getKey()); 537 CLog.e(e); 538 } 539 } 540 } 541 mergeBuildInfo( IInvocationContext receiverContext, IInvocationContext endInvocationContext)542 private void mergeBuildInfo( 543 IInvocationContext receiverContext, IInvocationContext endInvocationContext) { 544 if (receiverContext == null) { 545 return; 546 } 547 // Gather attributes of build infos 548 for (IBuildInfo info : receiverContext.getBuildInfos()) { 549 String name = receiverContext.getBuildInfoName(info); 550 IBuildInfo endInvocationInfo = endInvocationContext.getBuildInfo(name); 551 if (endInvocationInfo == null) { 552 CLog.e("No build info named: %s", name); 553 continue; 554 } 555 info.addBuildAttributes(endInvocationInfo.getBuildAttributes()); 556 } 557 } 558 559 /** 560 * Copy the build info and invocation attributes from the proto context to the current 561 * invocation context 562 * 563 * @param receiverContext The context receiving the attributes 564 * @param endInvocationContext The context providing the attributes 565 */ mergeInvocationContext( IInvocationContext receiverContext, IInvocationContext endInvocationContext)566 private void mergeInvocationContext( 567 IInvocationContext receiverContext, IInvocationContext endInvocationContext) { 568 if (receiverContext == null) { 569 return; 570 } 571 mergeBuildInfo(receiverContext, endInvocationContext); 572 573 try { 574 Method unlock = InvocationContext.class.getDeclaredMethod("unlock"); 575 unlock.setAccessible(true); 576 unlock.invoke(receiverContext); 577 unlock.setAccessible(false); 578 } catch (NoSuchMethodException 579 | SecurityException 580 | IllegalAccessException 581 | IllegalArgumentException 582 | InvocationTargetException e) { 583 CLog.e("Couldn't unlock the main context. Skip copying attributes"); 584 return; 585 } 586 // Copy invocation attributes 587 MultiMap<String, String> attributes = endInvocationContext.getAttributes(); 588 for (InvocationMetricKey key : InvocationMetricKey.values()) { 589 if (!attributes.containsKey(key.toString())) { 590 continue; 591 } 592 List<String> values = attributes.get(key.toString()); 593 attributes.remove(key.toString()); 594 595 for (String val : values) { 596 if (key.shouldAdd()) { 597 try { 598 InvocationMetricLogger.addInvocationMetrics(key, Long.parseLong(val)); 599 } catch (NumberFormatException e) { 600 CLog.d("Key %s doesn't have a number value, was: %s.", key, val); 601 InvocationMetricLogger.addInvocationMetrics(key, val); 602 } 603 } else { 604 InvocationMetricLogger.addInvocationMetrics(key, val); 605 } 606 } 607 } 608 if (attributes.containsKey(TfObjectTracker.TF_OBJECTS_TRACKING_KEY)) { 609 List<String> values = attributes.get(TfObjectTracker.TF_OBJECTS_TRACKING_KEY); 610 for (String val : values) { 611 for (String pair : Splitter.on(",").split(val)) { 612 if (!pair.contains("=")) { 613 continue; 614 } 615 String[] pairSplit = pair.split("="); 616 try { 617 TfObjectTracker.directCount(pairSplit[0], Long.parseLong(pairSplit[1])); 618 } catch (NumberFormatException e) { 619 CLog.e(e); 620 continue; 621 } 622 } 623 } 624 attributes.remove(TfObjectTracker.TF_OBJECTS_TRACKING_KEY); 625 } 626 receiverContext.addInvocationAttributes(attributes); 627 } 628 log(String format, Object... obj)629 private void log(String format, Object... obj) { 630 if (!mQuietParsing) { 631 CLog.d(format, obj); 632 } 633 } 634 parseDebugInfoContext(DebugInfo debugInfo, FailureDescription failure)635 private void parseDebugInfoContext(DebugInfo debugInfo, FailureDescription failure) { 636 if (!debugInfo.hasDebugInfoContext()) { 637 return; 638 } 639 DebugInfoContext debugContext = debugInfo.getDebugInfoContext(); 640 if (!Strings.isNullOrEmpty(debugContext.getActionInProgress())) { 641 try { 642 ActionInProgress value = 643 ActionInProgress.valueOf(debugContext.getActionInProgress()); 644 failure.setActionInProgress(value); 645 } catch (IllegalArgumentException parseError) { 646 CLog.e(parseError); 647 } 648 } 649 if (!Strings.isNullOrEmpty(debugContext.getDebugHelpMessage())) { 650 failure.setDebugHelpMessage(debugContext.getDebugHelpMessage()); 651 } 652 if (!Strings.isNullOrEmpty(debugContext.getOrigin())) { 653 failure.setOrigin(debugContext.getOrigin()); 654 } 655 String errorName = debugContext.getErrorName(); 656 long errorCode = debugContext.getErrorCode(); 657 if (!Strings.isNullOrEmpty(errorName)) { 658 // Most of the implementations will be Enums which represent the name/code. 659 // But since there might be several Enum implementation of ErrorIdentifier, we can't 660 // parse back the name to the Enum so instead we stub create a pre-populated 661 // ErrorIdentifier to carry the infos. 662 ErrorIdentifier errorId = 663 new ErrorIdentifier() { 664 @Override 665 public String name() { 666 return errorName; 667 } 668 669 @Override 670 public long code() { 671 return errorCode; 672 } 673 674 @Override 675 public FailureStatus status() { 676 return failure.getFailureStatus(); 677 } 678 }; 679 failure.setErrorIdentifier(errorId); 680 } 681 } 682 } 683