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