• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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