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.device.metric;
17 
18 import com.android.annotations.Nullable;
19 import com.android.annotations.VisibleForTesting;
20 import com.android.tradefed.build.IBuildInfo;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.device.LargeOutputReceiver;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
28 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
29 import com.android.tradefed.result.FileInputStreamSource;
30 import com.android.tradefed.result.InputStreamSource;
31 import com.android.tradefed.result.LogDataType;
32 import com.android.tradefed.util.CommandResult;
33 import com.android.tradefed.util.CommandStatus;
34 import com.android.tradefed.util.FileUtil;
35 import com.android.tradefed.util.Pair;
36 import com.android.tradefed.util.RunUtil;
37 import com.android.tradefed.util.StreamUtil;
38 import com.android.tradefed.util.ZipUtil;
39 
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.FileOutputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.List;
50 import java.util.concurrent.TimeUnit;
51 
52 /**
53  * Base implementation of {@link FilePullerDeviceMetricCollector} that allows
54  * pulling the perfetto files from the device and collect the metrics from it.
55  * Also used for converting the raw trace file into perfetto metric file.
56  */
57 @OptionClass(alias = "perfetto-metric-collector")
58 public class PerfettoPullerMetricCollector extends FilePullerDeviceMetricCollector {
59 
60     private static final String LINE_SEPARATOR = "\\r?\\n";
61     private static final char KEY_VALUE_SEPARATOR = ':';
62     private static final String EXTRACTOR_STATUS = "trace_extractor_status";
63     private static final String PROCESSOR_STATUS = "trace_processor_status";
64     private static final String STATUS_SUCCESS = "1";
65     private static final String STATUS_FAILURE = "0";
66     private static final String EXTRACTOR_RUNTIME = "trace_extractor_runtime";
67     private static final String PROCESSOR_RUNTIME = "trace_processor_runtime";
68     private static final String RAW_TRACE_FILE_SIZE = "perfetto_trace_file_size_bytes";
69     private static final String NSS_CACHE_ERROR = "base/nsscache-inl.h failed to lookup";
70 
71     public enum METRIC_FILE_FORMAT {
72         text,
73         binary,
74         json,
75     }
76 
77     @Option(name = "compress-perfetto",
78             description = "If enabled retrieves the perfetto compressed content,"
79                     + "decompress for processing and upload the compressed file. If"
80                     + "this flag is not enabled uncompressed version of perfetto file is"
81                     + "pulled, processed and uploaded.")
82     private boolean mCompressPerfetto = false;
83 
84     @Option(name = "max-compressed-file-size", description = "Max size of the compressed"
85             + " perfetto file. If the compressed file size exceeds the max then"
86             + " post processing and uploading the compressed file will not happen.")
87     private long mMaxCompressedFileSize = 10000L * 1024 * 1024;
88 
89     @Option(
90             name = "compressed-trace-shell-timeout",
91             description = "Timeout for retrieving compressed trace content through shell",
92             isTimeVal = true)
93     private long mCompressedTimeoutMs = TimeUnit.MINUTES.toMillis(20);
94 
95     @Option(
96             name = "compress-response-timeout",
97             description = "Timeout to receive the shell response when running the gzip command.",
98             isTimeVal = true)
99     private long mCompressResponseTimeoutMs = TimeUnit.SECONDS.toMillis(30);
100 
101     @Option(
102             name = "decompress-perfetto-timeout",
103             description = "Timeout to decompress perfetto compressed file.",
104             isTimeVal = true)
105     private long mDecompressTimeoutMs = TimeUnit.MINUTES.toMillis(20);
106 
107     @Option(
108             name = "perfetto-binary-path",
109             description = "Path to the script files used to analyze the trace files."
110                     + "Used for collecting the key value metrics from the perfetto"
111                     + "trace file.")
112     @Deprecated
113     private List<File> mScriptFiles = new ArrayList<>();
114 
115     @Option(
116             name = "perfetto-binary-args",
117             description = "Extra arguments to be passed to the binaries.")
118     @Deprecated
119     private List<String> mPerfettoBinaryArgs = new ArrayList<>();
120 
121     @Option(
122             name = "perfetto-metric-prefix",
123             description = "Prefix to be used with the metrics collected from perfetto.")
124     @Deprecated
125     private String mMetricPrefix = "perfetto";
126 
127     // List of process names passed to perfetto binary.
128     @Option(
129             name = "process-name",
130             description =
131             "Process names to be passed in perfetto script.")
132     @Deprecated
133     private Collection<String> mProcessNames = new ArrayList<String>();
134 
135     // Timeout for the script to process the trace files.
136     // The default is arbitarily chosen to be 5 mins to prevent the test spending more time in
137     // processing the files.
138     @Option(
139             name = "perfetto-script-timeout",
140             description = "Timeout for the perfetto script.",
141             isTimeVal = true)
142     @Deprecated
143     private long mScriptTimeoutMs = TimeUnit.MINUTES.toMillis(5);
144 
145     @Option(name = "convert-metric-file",
146             description = "Convert the raw trace file to perfetto metric file.")
147     private boolean mConvertToMetricFile = true;
148 
149     @Option(name = "collect-perfetto-file-size",
150             description = "Set it to true to collect the perfetto file size as part"
151                     + " of the metrics.")
152     private boolean mCollectPerfettoFileSize = false;
153 
154     @Option(
155             name = "trace-processor-binary",
156             description = "Path to the trace processor shell. This will"
157                     + " override the trace-processor-name which is used to "
158                     + " lookup in build artifacts. Used for converting the raw"
159                     + " trace into perfetto metric file.")
160     private File mTraceProcessorBinary = null;
161 
162     @Option(
163             name = "trace-processor-name",
164             description = "Trace processor name to look up from the test artifacts"
165                     + " or module artifacts.")
166     private String mTraceProcessorName = "trace_processor_shell";
167 
168     @Option(
169             name = "trace-processor-run-metrics",
170             description = "Comma separated list of metrics to extract from raw trace file."
171                     + " For example android_mem.")
172     private String mTraceProcessorMetrics = "android_mem";
173 
174     @Option(
175             name = "trace-processor-output-format",
176             description = "Trace processor output format. [binary|text|json]")
177     private METRIC_FILE_FORMAT mTraceProcessorOutputFormat = METRIC_FILE_FORMAT.text;
178 
179     @Option(
180             name = "trace-processor-timeout",
181             description = "Timeout to convert the raw trace file to metric proto file.",
182             isTimeVal = true)
183     private long mTraceConversionTimeout = TimeUnit.MINUTES.toMillis(20);
184 
185 
186     /**
187      * Process the perfetto trace file for the additional metrics and add it to final metrics.
188      * Decompress the perfetto file for processing if the compression was enabled.
189      *
190      * @param key the option key associated to the file that was pulled from the device.
191      * @param metricFile the {@link File} pulled from the device matching the option key.
192      * @param data where metrics will be stored.
193      */
194     @Override
processMetricFile(String key, File metricFile, DeviceMetricData data)195     public void processMetricFile(String key, File metricFile,
196             DeviceMetricData data) {
197         File processSrcFile = metricFile;
198         if (mCompressPerfetto) {
199             processSrcFile = decompressFile(metricFile);
200         }
201 
202         // Update the file size metrics.
203         if (processSrcFile != null && mCollectPerfettoFileSize) {
204             double perfettoFileSizeInBytes = processSrcFile.length();
205             Metric.Builder metricDurationBuilder = Metric.newBuilder();
206             metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(
207                     perfettoFileSizeInBytes);
208             data.addMetric(RAW_TRACE_FILE_SIZE, metricDurationBuilder.setType(DataType.RAW));
209         }
210 
211         // Convert to perfetto metric format.
212         if (mConvertToMetricFile) {
213             TraceProcessorResult result = convertToMetricProto(processSrcFile);
214             File convertedMetricFile = result.file;
215             if (convertedMetricFile != null) {
216                 try (InputStreamSource source = new FileInputStreamSource(convertedMetricFile,
217                         true)) {
218                     testLog(convertedMetricFile.getName(), getLogDataType(), source);
219                 }
220             }
221 
222             Metric.Builder processorRuntimeBuilder = Metric.newBuilder();
223             processorRuntimeBuilder
224                     .getMeasurementsBuilder()
225                     .setSingleDouble((double) result.runtime);
226             data.addMetric(
227                     String.format("%s_%s", mMetricPrefix, PROCESSOR_RUNTIME),
228                     processorRuntimeBuilder.setType(DataType.RAW));
229 
230             Metric.Builder processorStatusBuilder = Metric.newBuilder();
231             processorStatusBuilder.getMeasurementsBuilder().setSingleString(result.status);
232             data.addMetric(
233                     String.format("%s_%s", mMetricPrefix, PROCESSOR_STATUS),
234                     processorStatusBuilder.setType(DataType.RAW));
235         }
236 
237         if (processSrcFile != null) {
238             // Extract the metrics from the trace file.
239             for (File scriptFile : mScriptFiles) {
240                 // Apply necessary execute permissions to the script.
241                 FileUtil.chmodGroupRWX(scriptFile);
242 
243                 List<String> commandArgsList = new ArrayList<String>();
244                 commandArgsList.add(scriptFile.getAbsolutePath());
245                 commandArgsList.addAll(mPerfettoBinaryArgs);
246                 commandArgsList.add("-trace_file");
247                 commandArgsList.add(processSrcFile.getAbsolutePath());
248 
249                 if (!mProcessNames.isEmpty()) {
250                     commandArgsList.add("-process_names");
251                     commandArgsList.add(String.join(",", mProcessNames));
252                 }
253 
254                 String traceExtractorStatus = STATUS_SUCCESS;
255 
256                 double scriptDuration = 0;
257                 double scriptStartTime = System.currentTimeMillis();
258                 CommandResult cr = runHostCommand(mScriptTimeoutMs,
259                         commandArgsList.toArray(new String[commandArgsList.size()]), null,
260                         null);
261                 scriptDuration = System.currentTimeMillis() - scriptStartTime;
262 
263                 // Update the script duration metrics.
264                 Metric.Builder metricDurationBuilder = Metric.newBuilder();
265                 metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(scriptDuration);
266                 data.addMetric(
267                         String.format("%s_%s", mMetricPrefix, EXTRACTOR_RUNTIME),
268                         metricDurationBuilder.setType(DataType.RAW));
269 
270                 // Adding temporary workaround to handle the NSS cache error.
271                 // TODO: Revert the NSS cache error handling after b/156924255 is fixed.
272                 if (CommandStatus.SUCCESS.equals(cr.getStatus()) ||
273                         (CommandStatus.FAILED.equals(cr.getStatus()) &&
274                                 cr.getStdout().contains(NSS_CACHE_ERROR))) {
275                     String[] metrics = cr.getStdout().split(LINE_SEPARATOR);
276 
277                     boolean isMetricStarted = false;
278                     for (String metric : metrics) {
279                         // Skip until the first metric line is parsed.
280                         // Usually "trace-durations-ms" is the first metric from the output.
281                         if(isMetricStarted || metric.contains("trace-duration-ms")) {
282                             isMetricStarted = true;
283                         } else {
284                             continue;
285                         }
286 
287                         Pair<String, String> kv = splitKeyValue(metric);
288 
289                         if (kv != null) {
290                             Metric.Builder metricBuilder = Metric.newBuilder();
291                             metricBuilder.getMeasurementsBuilder().setSingleString(kv.second);
292                             data.addMetric(
293                                     String.format("%s_%s", mMetricPrefix, kv.first),
294                                     metricBuilder.setType(DataType.RAW));
295                         } else {
296                             CLog.e("Output %s not in the expected format.", metric);
297                         }
298                     }
299                     CLog.i(cr.getStdout());
300                 } else {
301                     traceExtractorStatus = STATUS_FAILURE;
302                     CLog.e("Unable to parse the trace file %s due to %s - Status - %s ",
303                             processSrcFile.getName(), cr.getStderr(), cr.getStatus());
304                 }
305 
306                 if (mCompressPerfetto) {
307                     processSrcFile.delete();
308                 }
309                 Metric.Builder metricStatusBuilder = Metric.newBuilder();
310                 metricStatusBuilder.getMeasurementsBuilder()
311                         .setSingleString(traceExtractorStatus);
312                 data.addMetric(
313                         String.format("%s_%s", mMetricPrefix, EXTRACTOR_STATUS),
314                         metricStatusBuilder.setType(DataType.RAW));
315             }
316         }
317 
318         // Upload and delete the host trace file.
319         try (InputStreamSource source = new FileInputStreamSource(metricFile, true)) {
320             if (mCompressPerfetto) {
321                 if (processSrcFile != null) {
322                     testLog(metricFile.getName(), LogDataType.GZIP, source);
323                 } else {
324                     metricFile.delete();
325                 }
326 
327             } else {
328                 testLog(metricFile.getName(), LogDataType.PERFETTO, source);
329             }
330         }
331 
332     }
333 
334     private static class TraceProcessorResult {
335         public final File file;
336         // runtime is set to -1 if the trace processor was never run.
337         public final long runtime;
338         public final String status;
339 
TraceProcessorResult(File file, long runtime, String status)340         public TraceProcessorResult(File file, long runtime, String status) {
341             this.file = file;
342             this.runtime = runtime;
343             this.status = status;
344         }
345     }
346 
347     /**
348      * Converts the raw trace file into perfetto metric file.
349      *
350      * @param perfettoRawTraceFile Raw perfetto trace file that needs to be converted.
351      * @return The result of the conversion.
352      */
convertToMetricProto(File perfettoRawTraceFile)353     private TraceProcessorResult convertToMetricProto(File perfettoRawTraceFile) {
354 
355         // Use absolute path to the trace file if it is available otherwise
356         // resolve the trace processor name from the test or module artifacts.
357         if (mTraceProcessorBinary == null || !mTraceProcessorBinary.exists()) {
358             mTraceProcessorBinary = getFileFromTestArtifacts(mTraceProcessorName);
359         }
360 
361         File metricOutputFile = null;
362         long runtime = -1L;
363 
364         if (mTraceProcessorBinary == null) {
365             CLog.e("Failed to locate the trace processor shell binary file.");
366             return new TraceProcessorResult(metricOutputFile, runtime, STATUS_FAILURE);
367         }
368 
369         FileUtil.chmodGroupRWX(mTraceProcessorBinary);
370         List<String> commandArgsList = new ArrayList<String>();
371         commandArgsList.add(mTraceProcessorBinary.getAbsolutePath());
372 
373         // Comma separated list of metrics to extract.
374         if (!mTraceProcessorMetrics.isEmpty()) {
375             commandArgsList.add("--run-metrics");
376             commandArgsList.add(mTraceProcessorMetrics);
377         }
378         // Metric file output format.
379         commandArgsList.add("--metrics-output=" + mTraceProcessorOutputFormat);
380         commandArgsList.add(perfettoRawTraceFile.getAbsolutePath());
381 
382         try {
383             metricOutputFile = FileUtil.createTempFile(
384                     "metric_" + getRawTraceFileName(perfettoRawTraceFile.getName()), "");
385         } catch (IOException e) {
386             CLog.e("Not able to create metric perfetto output file.");
387             CLog.e(e);
388             return new TraceProcessorResult(null, runtime, STATUS_FAILURE);
389         }
390 
391         // Running the trace conversion.
392         CLog.i("Run the trace convertor.");
393         boolean isConversionSuccess = true;
394         try (FileOutputStream outStream = new FileOutputStream(metricOutputFile);
395                 ByteArrayOutputStream errStream = new ByteArrayOutputStream()) {
396             long startTime = System.currentTimeMillis();
397             CommandResult conversionResult = runHostCommand(mTraceConversionTimeout,
398                     commandArgsList.toArray(new String[commandArgsList
399                             .size()]),
400                     outStream, errStream);
401             runtime = System.currentTimeMillis() - startTime;
402             if (!CommandStatus.SUCCESS.equals(conversionResult.getStatus())) {
403                 CLog.e("Unable to convert the raw trace - %s to metric file due to"
404                         + " %s - Status - %s ", perfettoRawTraceFile.getName(),
405                         errStream.toString(), conversionResult.getStatus());
406                 isConversionSuccess = false;
407             } else if (mTraceProcessorOutputFormat.equals(METRIC_FILE_FORMAT.text) ||
408                     mTraceProcessorOutputFormat.equals(METRIC_FILE_FORMAT.json)) {
409                 File compressedFile = getCompressedFile(metricOutputFile);
410                 metricOutputFile.delete();
411                 return new TraceProcessorResult(compressedFile, runtime, STATUS_SUCCESS);
412             }
413         } catch (FileNotFoundException e) {
414             CLog.e("Not able to find the result metric file to write the "
415                     + "metric output.");
416             CLog.e(e);
417             isConversionSuccess = false;
418         } catch (IOException e1) {
419             CLog.e("Unable to close the streams.");
420             CLog.e(e1);
421             isConversionSuccess = false;
422         } finally {
423             if (!isConversionSuccess) {
424                 metricOutputFile.delete();
425                 return new TraceProcessorResult(null, runtime, STATUS_FAILURE);
426             }
427         }
428         return new TraceProcessorResult(metricOutputFile, runtime, STATUS_SUCCESS);
429     }
430 
431     /**
432      * Pull the file from the specified path in the device. Pull the compressed content of the
433      * perfetto file if the compress perfetto option is enabled.
434      *
435      * @param device which has the file.
436      * @param remoteFilePath location in the device.
437      * @return compressed or decompressed version of perfetto file based on mCompressPerfetto option
438      *     is set or not.
439      * @throws DeviceNotAvailableException
440      */
441     @Override
retrieveFile(ITestDevice device, String remoteFilePath, int userId)442     protected File retrieveFile(ITestDevice device, String remoteFilePath, int userId)
443             throws DeviceNotAvailableException {
444         if (!mCompressPerfetto) {
445             return super.retrieveFile(device, remoteFilePath, userId);
446         }
447         File perfettoCompressedFile = null;
448         String filePathInDevice = remoteFilePath;
449         CLog.i("Retrieving the compressed perfetto trace content from device.");
450         LargeOutputReceiver compressedOutputReceiver =
451                 new LargeOutputReceiver(
452                         "perfetto_compressed_temp",
453                         device.getSerialNumber(),
454                         mMaxCompressedFileSize);
455         device.executeShellCommand(
456                 String.format("gzip -c %s", filePathInDevice),
457                 compressedOutputReceiver,
458                 mCompressedTimeoutMs,
459                 mCompressResponseTimeoutMs,
460                 TimeUnit.MILLISECONDS,
461                 1);
462         compressedOutputReceiver.flush();
463         compressedOutputReceiver.cancel();
464 
465         // Copy to temp file which will be used for decompression, perfetto
466         // metrics extraction and uploading the file later.
467         try (InputStreamSource largeStreamSrc = compressedOutputReceiver.getData();
468                 InputStream inputStream = largeStreamSrc.createInputStream()) {
469             perfettoCompressedFile = FileUtil.createTempFile("perfetto_compressed", ".gz");
470             FileOutputStream outStream = new FileOutputStream(perfettoCompressedFile);
471             byte[] buffer = new byte[4096];
472             int bytesRead = -1;
473             while ((bytesRead = inputStream.read(buffer)) > -1) {
474                 outStream.write(buffer, 0, bytesRead);
475             }
476             StreamUtil.close(outStream);
477             CLog.i("Successfully copied the compressed content from device to" + " host.");
478         } catch (IOException e) {
479             if (perfettoCompressedFile != null) {
480                 perfettoCompressedFile.delete();
481             }
482             CLog.e("Failed to copy compressed perfetto to temporary file.");
483             CLog.e(e);
484         } finally {
485             compressedOutputReceiver.delete();
486         }
487         return perfettoCompressedFile;
488     }
489 
490     /**
491      * Decompress the file to a temporary file in the host.
492      *
493      * @param compressedFile file to be decompressed.
494      * @return decompressed file used for postprocessing.
495      */
decompressFile(File compressedFile)496     private File decompressFile(File compressedFile) {
497         File decompressedFile = null;
498         try {
499             decompressedFile = FileUtil.createTempFile("perfetto_decompressed", ".pb");
500         } catch (IOException e) {
501             CLog.e("Not able to create decompressed perfetto file.");
502             CLog.e(e);
503             return null;
504         }
505         // Keep the original file for uploading.
506         List<String> decompressArgsList = new ArrayList<String>();
507         decompressArgsList.add("gzip");
508         decompressArgsList.add("-k");
509         decompressArgsList.add("-c");
510         decompressArgsList.add("-d");
511         decompressArgsList.add(compressedFile.getAbsolutePath());
512 
513         // Decompress perfetto trace file.
514         CLog.i("Start decompressing the perfetto trace file.");
515         try (FileOutputStream outStream = new FileOutputStream(decompressedFile);
516                 ByteArrayOutputStream errStream = new ByteArrayOutputStream()) {
517             CommandResult decompressResult = runHostCommand(mDecompressTimeoutMs,
518                     decompressArgsList.toArray(new String[decompressArgsList
519                             .size()]), outStream, errStream);
520 
521             if (!CommandStatus.SUCCESS.equals(decompressResult.getStatus())) {
522                 CLog.e("Unable decompress the metric file %s due to %s - Status - %s ",
523                         compressedFile.getName(), errStream.toString(),
524                         decompressResult.getStatus());
525                 decompressedFile.delete();
526                 return null;
527             }
528         } catch (FileNotFoundException e) {
529             CLog.e("Not able to find the decompressed file to copy the"
530                     + " decompressed contents.");
531             CLog.e(e);
532             return null;
533         } catch (IOException e1) {
534             CLog.e("Unable to close the streams.");
535             CLog.e(e1);
536         }
537         CLog.i("Successfully decompressed the perfetto trace file.");
538         return decompressedFile;
539     }
540 
541     @Override
processMetricDirectory(String key, File metricDirectory, DeviceMetricData runData)542     public void processMetricDirectory(String key, File metricDirectory, DeviceMetricData runData) {
543         // Implement if all the files under specific directory have to be post processed.
544     }
545 
546     /**
547      * Run a host command with the given array of command args.
548      *
549      * @param commandArgs args to be used to construct the host command.
550      * @param stdout output of the command.
551      * @param stderr error message if any from the command.
552      * @return return the command results.
553      */
554     @VisibleForTesting
runHostCommand(long timeOut, String[] commandArgs, OutputStream stdout, OutputStream stderr)555     CommandResult runHostCommand(long timeOut, String[] commandArgs, OutputStream stdout,
556             OutputStream stderr) {
557         if (stdout != null && stderr != null) {
558             return RunUtil.getDefault().runTimedCmd(timeOut, stdout, stderr, commandArgs);
559         }
560         return RunUtil.getDefault().runTimedCmd(timeOut, commandArgs);
561     }
562 
563     @VisibleForTesting
564     @Nullable
splitKeyValue(String s)565     static Pair<String, String> splitKeyValue(String s) {
566         // Expected script test output format.
567         // Key1:Value1
568         // Key2:Value2
569         int separatorIdx = s.lastIndexOf(KEY_VALUE_SEPARATOR);
570         if (separatorIdx > 0 && separatorIdx + 1 < s.length()) {
571             return new Pair<>(s.substring(0, separatorIdx), s.substring(separatorIdx + 1));
572         }
573         return null;
574     }
575 
576     /**
577      * Get the log data type based on the output metric perfetto file.
578      *
579      * @return LogDataType type of the file used for uploading the artifacts.
580      */
getLogDataType()581     private LogDataType getLogDataType() {
582         // text option in perfetto trace processor means text proto.
583         if(mTraceProcessorOutputFormat.equals(METRIC_FILE_FORMAT.text)) {
584             return LogDataType.ZIP;
585         } else if(mTraceProcessorOutputFormat.equals(METRIC_FILE_FORMAT.binary)) {
586             return LogDataType.PB;
587         } else {
588             return LogDataType.TEXT;
589         }
590     }
591 
592     /**
593      * Extract the raw trace file name used for constructing the output
594      * perfetto metric file name
595      *
596      * @param rawTraceFileName
597      * @return String name of the raw trace file name excluding the UUID.
598      */
getRawTraceFileName(String rawTraceFileName)599     private String getRawTraceFileName(String rawTraceFileName) {
600         // For example return perfetto_<test_name>-1_ from
601         // perfetto_<test_name>-1_13388308985625987330.pb excluding the UID.
602         int lastIndex = rawTraceFileName.lastIndexOf("_");
603         if (lastIndex != -1) {
604             return rawTraceFileName.substring(0, lastIndex + 1);
605         }
606         return rawTraceFileName;
607     }
608 
609     /**
610      * Retrieve the current build info.
611      *
612      * @return BuildInfo which has access to test artifacts directory.
613      */
614     @VisibleForTesting
getCurrentBuildInfo()615     IBuildInfo getCurrentBuildInfo() {
616         return getBuildInfos().get(0);
617     }
618 
619     /**
620      * Compress the given file.
621      *
622      * @return File compressed version of the file.
623      */
624     @VisibleForTesting
getCompressedFile(File metricOutputFile)625     File getCompressedFile(File metricOutputFile) throws IOException {
626         return ZipUtil.createZip(metricOutputFile,
627                 metricOutputFile.getName());
628     }
629 }
630