1 /*
2  * Copyright (C) 2017 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.tradefed.config.Option;
19 import com.android.tradefed.device.DeviceNotAvailableException;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.log.LogUtil.CLog;
22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
23 import com.android.tradefed.util.FileUtil;
24 import com.android.tradefed.util.proto.TfMetricProtoUtil;
25 
26 import java.io.File;
27 import java.io.IOException;
28 import java.util.AbstractMap.SimpleEntry;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.regex.Pattern;
34 
35 /**
36  * A {@link BaseDeviceMetricCollector} that listen for metrics key coming from the device and pull
37  * them as a file from the device. Can be extended for extra-processing of the file.
38  */
39 public abstract class FilePullerDeviceMetricCollector extends BaseDeviceMetricCollector {
40 
41     @Option(
42         name = "pull-pattern-keys",
43         description = "The pattern key name to be pull from the device as a file. Can be repeated."
44     )
45     private List<String> mKeys = new ArrayList<>();
46 
47     @Option(
48         name = "directory-keys",
49         description = "Path to the directory on the device that contains the metrics."
50         )
51     protected List<String> mDirectoryKeys = new ArrayList<>();
52 
53     @Option(
54         name = "clean-up",
55         description = "Whether to delete the file from the device after pulling it or not."
56     )
57     private boolean mCleanUp = true;
58 
59     @Override
onTestRunEnd( DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)60     public void onTestRunEnd(
61             DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
62         processMetricRequest(runData, TfMetricProtoUtil.compatibleConvert(currentRunMetrics));
63     }
64 
65     @Override
onTestEnd(DeviceMetricData testData, Map<String, Metric> currentTestCaseMetrics)66     public void onTestEnd(DeviceMetricData testData, Map<String, Metric> currentTestCaseMetrics) {
67         processMetricRequest(testData, TfMetricProtoUtil.compatibleConvert(currentTestCaseMetrics));
68     }
69 
70     /**
71      * Implementation of the method should allow to log the file, parse it for metrics to be put in
72      * {@link DeviceMetricData}.
73      *
74      * @param key the option key associated to the file that was pulled.
75      * @param metricFile the {@link File} pulled from the device matching the option key.
76      * @param runData the run {@link DeviceMetricData} where metrics can be stored.
77      */
processMetricFile(String key, File metricFile, DeviceMetricData runData)78     public abstract void processMetricFile(String key, File metricFile, DeviceMetricData runData);
79 
80     /**
81      * Implementation of the method should allow to log the directory, parse it for metrics to be
82      * put in {@link DeviceMetricData}.
83      *
84      * @param key the option key associated to the directory that was pulled.
85      * @param metricDirectory the {@link File} pulled from the device matching the option key.
86      * @param runData the run {@link DeviceMetricData} where metrics can be stored.
87      */
processMetricDirectory( String key, File metricDirectory, DeviceMetricData runData)88     public abstract void processMetricDirectory(
89             String key, File metricDirectory, DeviceMetricData runData);
90 
processMetricRequest(DeviceMetricData data, Map<String, String> currentMetrics)91     private void processMetricRequest(DeviceMetricData data, Map<String, String> currentMetrics) {
92         if (mKeys.isEmpty() && mDirectoryKeys.isEmpty()) {
93             return;
94         }
95         for (String key : mKeys) {
96             Entry<String, File> pulledMetrics = pullMetricFile(key, currentMetrics);
97             if (pulledMetrics != null) {
98                 processMetricFile(pulledMetrics.getKey(), pulledMetrics.getValue(), data);
99             }
100         }
101 
102         for (String key : mDirectoryKeys) {
103             Entry<String, File> pulledMetrics = pullMetricDirectory(key);
104             if (pulledMetrics != null) {
105                 processMetricDirectory(pulledMetrics.getKey(), pulledMetrics.getValue(), data);
106             }
107         }
108 
109     }
110 
pullMetricFile( String pattern, final Map<String, String> currentRunMetrics)111     private Entry<String, File> pullMetricFile(
112             String pattern, final Map<String, String> currentRunMetrics) {
113         Pattern p = Pattern.compile(pattern);
114         for (Entry<String, String> entry : currentRunMetrics.entrySet()) {
115             if (p.matcher(entry.getKey()).find()) {
116                 for (ITestDevice device : getDevices()) {
117                     try {
118                         File attemptPull = device.pullFile(entry.getValue());
119                         if (attemptPull != null) {
120                             if (mCleanUp) {
121                                 device.executeShellCommand(
122                                         String.format("rm -f %s", entry.getValue()));
123                             }
124                             // Return the actual key and the file associated
125                             return new SimpleEntry<String, File>(entry.getKey(), attemptPull);
126                         }
127                     } catch (DeviceNotAvailableException e) {
128                         CLog.e(
129                                 "Exception when pulling metric file '%s' from %s",
130                                 entry.getValue(), device.getSerialNumber());
131                         CLog.e(e);
132                     }
133                 }
134             }
135         }
136         CLog.e("Could not find a device file associated to pattern '%s'.", pattern);
137         return null;
138     }
139 
140     /**
141      * Pulls the directory and all its content from the device and save it in the
142      * host under the host_tmp folder.
143      *
144      * @param keyDirectory path to the source directory in the device.
145      * @return Key,value pair of the directory name and path to the directory in the
146      * local host.
147      */
pullMetricDirectory(String keyDirectory)148     private Entry<String, File> pullMetricDirectory(String keyDirectory) {
149         try {
150             File tmpDestDir = FileUtil.createTempDir("host_tmp");
151             for (ITestDevice device : getDevices()) {
152                 try {
153                     if (device.pullDir(keyDirectory, tmpDestDir)) {
154                         if (mCleanUp) {
155                             device.executeShellCommand(
156                                     String.format("rm -rf %s", keyDirectory));
157                         }
158                         return new SimpleEntry<String, File>(keyDirectory, tmpDestDir);
159                     }
160                 } catch (DeviceNotAvailableException e) {
161                     CLog.e(
162                             "Exception when pulling directory '%s' from %s",
163                             keyDirectory, device.getSerialNumber());
164                     CLog.e(e);
165                 }
166             }
167         } catch (IOException ioe) {
168             CLog.e("Exception while creating the local directory");
169             CLog.e(ioe);
170         }
171         CLog.e("Could not find a device directory associated to path '%s'.", keyDirectory);
172         return null;
173     }
174 
175 }
176