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