1 /*
2  * Copyright (C) 2013 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 
17 package com.android.performance.tests;
18 
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.config.Option.Importance;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.result.ITestInvocationListener;
25 import com.android.tradefed.result.TestDescription;
26 import com.android.tradefed.testtype.IDeviceTest;
27 import com.android.tradefed.testtype.IRemoteTest;
28 import com.android.tradefed.util.RunUtil;
29 import com.android.tradefed.util.proto.TfMetricProtoUtil;
30 
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.NodeList;
35 import org.xml.sax.SAXException;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.Map;
42 
43 import javax.xml.parsers.DocumentBuilder;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.parsers.ParserConfigurationException;
46 
47 /**
48  * A harness that launches GLBenchmark and reports result. Requires GLBenchmark
49  * custom XML. Assumes GLBenchmark app is already installed on device.
50  */
51 public class GLBenchmarkTest implements IDeviceTest, IRemoteTest {
52 
53     private static final String RUN_KEY = "glbenchmark";
54     private static final long TIMEOUT_MS = 30 * 60 * 1000;
55     private static final long POLLING_INTERVAL_MS = 5 * 1000;
56     private static final Map<String, String> METRICS_KEY_MAP = createMetricsKeyMap();
57 
58     private ITestDevice mDevice;
59 
60     @Option(name = "custom-xml-path", description = "local path for GLBenchmark custom.xml",
61             importance = Importance.ALWAYS)
62     private File mGlbenchmarkCustomXmlLocal = new File("/tmp/glbenchmark_custom.xml");
63 
64     @Option(name = "gl-package-name", description = "GLBenchmark package name")
65     private String mGlbenchmarkPackageName = "com.glbenchmark.glbenchmark25";
66 
67     @Option(name = "gl-version", description = "GLBenchmark version (e.g. 2.5.1_b306a5)")
68     private String mGlbenchmarkVersion = "2.5.1_b306a5";
69 
70     private String mGlbenchmarkCacheDir =
71             "${EXTERNAL_STORAGE}/Android/data/" + mGlbenchmarkPackageName + "/cache/";
72     private String mGlbenchmarkCustomXmlPath =
73             mGlbenchmarkCacheDir + "custom.xml";
74     private String mGlbenchmarkResultXmlPath =
75             mGlbenchmarkCacheDir + "last_results_" + mGlbenchmarkVersion + ".xml";
76     private String mGlbenchmarkExcelResultXmlPath =
77             mGlbenchmarkCacheDir + "results_%s_0.xml";
78     private String mGlbenchmarkAllResultXmlPath =
79             mGlbenchmarkCacheDir + "results*.xml";
80 
createMetricsKeyMap()81     private static Map<String, String> createMetricsKeyMap() {
82         Map<String, String> result = new HashMap<String, String>();
83         result.put("Fill rate - C24Z16", "fill-rate");
84         result.put("Fill rate - C24Z16 Offscreen", "fill-rate-offscreen");
85         result.put("Triangle throughput: Textured - C24Z16", "triangle-c24z16");
86         result.put("Triangle throughput: Textured - C24Z16 Offscreen",
87                 "triangle-c24z16-offscreen");
88         result.put("Triangle throughput: Textured - C24Z16 Vertex lit",
89                 "triangle-c24z16-vertex-lit");
90         result.put("Triangle throughput: Textured - C24Z16 Offscreen Vertex lit",
91                 "triangle-c24z16-offscreen-vertex-lit");
92         result.put("Triangle throughput: Textured - C24Z16 Fragment lit",
93                 "triangle-c24z16-fragment-lit");
94         result.put("Triangle throughput: Textured - C24Z16 Offscreen Fragment lit",
95                 "triangle-c24z16-offscreen-fragment-lit");
96         result.put("GLBenchmark 2.5 Egypt HD - C24Z16", "egypt-hd-c24z16");
97         result.put("GLBenchmark 2.5 Egypt HD - C24Z16 Offscreen", "egypt-hd-c24z16-offscreen");
98         result.put("GLBenchmark 2.5 Egypt HD PVRTC4 - C24Z16", "egypt-hd-pvrtc4-c24z16");
99         result.put("GLBenchmark 2.5 Egypt HD PVRTC4 - C24Z16 Offscreen",
100                 "egypt-hd-pvrtc4-c24z16-offscreen");
101         result.put("GLBenchmark 2.5 Egypt HD - C24Z24MS4", "egypt-hd-c24z24ms4");
102         result.put("GLBenchmark 2.5 Egypt HD - C24Z16 Fixed timestep",
103                 "egypt-hd-c24z16-fixed-timestep");
104         result.put("GLBenchmark 2.5 Egypt HD - C24Z16 Fixed timestep Offscreen",
105                 "egypt-hd-c24z16-fixed-timestep-offscreen");
106         result.put("GLBenchmark 2.1 Egypt Classic - C16Z16", "egypt-classic-c16z16");
107         result.put("GLBenchmark 2.1 Egypt Classic - C16Z16 Offscreen",
108                 "egypt-classic-c16z16-offscreen");
109         return Collections.unmodifiableMap(result);
110     }
111 
112     /**
113      * {@inheritDoc}
114      */
115     @Override
setDevice(ITestDevice device)116     public void setDevice(ITestDevice device) {
117         mDevice = device;
118     }
119 
120     /**
121      * {@inheritDoc}
122      */
123     @Override
getDevice()124     public ITestDevice getDevice() {
125         return mDevice;
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
run(ITestInvocationListener listener)132     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
133         TestDescription testId = new TestDescription(getClass().getCanonicalName(), RUN_KEY);
134         ITestDevice device = getDevice();
135 
136         // delete old result
137         device.executeShellCommand(String.format("rm %s", mGlbenchmarkResultXmlPath));
138         device.executeShellCommand(String.format("rm %s", mGlbenchmarkAllResultXmlPath));
139 
140         // push glbenchmark custom xml to device
141         device.pushFile(mGlbenchmarkCustomXmlLocal, mGlbenchmarkCustomXmlPath);
142 
143         listener.testRunStarted(RUN_KEY, 0);
144         listener.testStarted(testId);
145 
146         long testStartTime = System.currentTimeMillis();
147         boolean isRunningBenchmark;
148 
149         boolean isTimedOut = false;
150         boolean isResultGenerated = false;
151         Map<String, String> metrics = new HashMap<String, String>();
152         String errMsg = null;
153 
154         String deviceModel = device.executeShellCommand("getprop ro.product.model");
155         String resultExcelXmlPath = String.format(mGlbenchmarkExcelResultXmlPath,
156                 deviceModel.trim().replaceAll("[ -]", "_").toLowerCase());
157         CLog.i("Result excel xml path:" + resultExcelXmlPath);
158 
159         // start glbenchmark and wait for test to complete
160         isTimedOut = false;
161         long benchmarkStartTime = System.currentTimeMillis();
162 
163         device.executeShellCommand("am start -a android.intent.action.MAIN "
164                 + "-n com.glbenchmark.glbenchmark25/com.glbenchmark.activities.MainActivity "
165                 + "--ez custom true");
166         isRunningBenchmark = true;
167         while (isRunningBenchmark && !isResultGenerated && !isTimedOut) {
168             RunUtil.getDefault().sleep(POLLING_INTERVAL_MS);
169             isTimedOut = (System.currentTimeMillis() - benchmarkStartTime >= TIMEOUT_MS);
170             isResultGenerated = device.doesFileExist(resultExcelXmlPath);
171             isRunningBenchmark = device.executeShellCommand("ps").contains("glbenchmark");
172         }
173 
174         if (isTimedOut) {
175             errMsg = "GLBenchmark timed out.";
176         } else {
177             // pull result from device
178             File benchmarkReport = device.pullFile(mGlbenchmarkResultXmlPath);
179             if (benchmarkReport != null) {
180                 // parse result
181                 CLog.i("== GLBenchmark result ==");
182                 Map<String, String> benchmarkResult = parseResultXml(benchmarkReport);
183                 if (benchmarkResult == null) {
184                     errMsg = "Failed to parse GLBenchmark result XML.";
185                 } else {
186                     metrics = benchmarkResult;
187                 }
188                 // delete results from device and host
189                 device.executeShellCommand(String.format("rm %s", mGlbenchmarkResultXmlPath));
190                 device.executeShellCommand(String.format("rm %s", resultExcelXmlPath));
191                 benchmarkReport.delete();
192             } else {
193                 errMsg = "GLBenchmark report not found.";
194             }
195         }
196         if (errMsg != null) {
197             CLog.e(errMsg);
198             listener.testFailed(testId, errMsg);
199             listener.testEnded(testId, TfMetricProtoUtil.upgradeConvert(metrics));
200             listener.testRunFailed(errMsg);
201         } else {
202             long durationMs = System.currentTimeMillis() - testStartTime;
203             listener.testEnded(testId, TfMetricProtoUtil.upgradeConvert(metrics));
204             listener.testRunEnded(durationMs, TfMetricProtoUtil.upgradeConvert(metrics));
205         }
206     }
207 
208     /**
209      * Parse GLBenchmark result XML.
210      *
211      * @param resultXml GLBenchmark result XML {@link File}
212      * @return a {@link HashMap} that contains metrics key and result
213      */
parseResultXml(File resultXml)214     private Map<String, String> parseResultXml(File resultXml) {
215         Map<String, String> benchmarkResult = new HashMap<String, String>();
216         DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
217         Document doc = null;
218         try {
219             DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
220             doc = dBuilder.parse(resultXml);
221         } catch (ParserConfigurationException e) {
222             return null;
223         } catch (IOException e) {
224             return null;
225         } catch (SAXException e) {
226             return null;
227         } catch (IllegalArgumentException e) {
228             return null;
229         }
230         doc.getDocumentElement().normalize();
231 
232         NodeList nodes = doc.getElementsByTagName("test_result");
233         for (int i = 0; i < nodes.getLength(); i++) {
234             Node node = nodes.item(i);
235             if (node.getNodeType() == Node.ELEMENT_NODE) {
236                 Element testResult = (Element) node;
237                 String testTitle = getData(testResult, "title");
238                 String testType = getData(testResult, "type");
239                 String fps = getData(testResult, "fps");
240                 String score = getData(testResult, "score");
241                 String testName = String.format("%s - %s", testTitle, testType);
242                 if (METRICS_KEY_MAP.containsKey(testName)) {
243                     if (testName.contains("Fill") || testName.contains("Triangle")) {
244                         // Use Mtexels/sec or MTriangles/sec as unit
245                         score = String.valueOf((long)(Double.parseDouble(score) / 1.0E6));
246                     }
247                     CLog.i(String.format("%s: %s (fps=%s)", testName, score, fps));
248                     String testKey = METRICS_KEY_MAP.get(testName);
249                     if (score != null && !score.trim().equals("0")) {
250                         benchmarkResult.put(testKey, score);
251                         if (fps != null && !fps.trim().equals("0.0")) {
252                             try {
253                                 float fpsValue = Float.parseFloat(fps.replace("fps", ""));
254                                 benchmarkResult.put(testKey + "-fps", String.valueOf(fpsValue));
255                             } catch (NumberFormatException e) {
256                                 CLog.i(String.format("Got %s for fps value. Ignored.", fps));
257                             }
258                         }
259                     }
260                 }
261             }
262         }
263         return benchmarkResult;
264     }
265 
266     /**
267      * Get value in the first matching tag under the element
268      *
269      * @param element the parent {@link Element} of the tag
270      * @param tag {@link String} of the tag name
271      * @return a {@link String} that contains the value in the tag; returns null if not found.
272      */
getData(Element element, String tag)273     private String getData(Element element, String tag) {
274         NodeList tagNodes = element.getElementsByTagName(tag);
275         if (tagNodes.getLength() > 0) {
276             Node tagNode = tagNodes.item(0);
277             if (tagNode.getNodeType() == Node.ELEMENT_NODE) {
278                 Node node = tagNode.getChildNodes().item(0);
279                 if (node != null) {
280                     return node.getNodeValue();
281                 } else {
282                     return null;
283                 }
284             }
285         }
286         return null;
287     }
288 }
289