1 /* Copyright 2018 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 package ovic.demo.app; 16 17 import android.app.Activity; 18 import android.content.res.AssetFileDescriptor; 19 import android.content.res.AssetManager; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.os.Bundle; 23 import android.os.Process; 24 import android.os.SystemClock; 25 import android.util.Log; 26 import android.view.View; 27 import android.widget.TextView; 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileReader; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.nio.MappedByteBuffer; 35 import java.nio.channels.FileChannel; 36 import java.text.DecimalFormat; 37 import org.tensorflow.ovic.OvicBenchmarker; 38 import org.tensorflow.ovic.OvicClassifierBenchmarker; 39 import org.tensorflow.ovic.OvicDetectorBenchmarker; 40 41 /** Class that benchmark image classifier models. */ 42 public class OvicBenchmarkerActivity extends Activity { 43 /** Tag for the {@link Log}. */ 44 private static final String TAG = "OvicBenchmarkerActivity"; 45 46 /** Name of the task-dependent data files stored in Assets. */ 47 private static String labelPath = null; 48 49 private static String testImagePath = null; 50 private static String modelPath = null; 51 /** 52 * Each bottom press will launch a benchmarking experiment. The experiment stops when either the 53 * total native latency reaches WALL_TIME or the number of iterations reaches MAX_ITERATIONS, 54 * whichever comes first. 55 */ 56 /** Wall time for each benchmarking experiment. */ 57 private static final double WALL_TIME = 3000; 58 /** Maximum number of iterations in each benchmarking experiment. */ 59 private static final int MAX_ITERATIONS = 100; 60 /** Mask for binding to a single big core. Pixel 1 (4), Pixel 2 (16). */ 61 private static final int BIG_CORE_MASK = 16; 62 /** Amount of time in milliseconds to wait for affinity to set. */ 63 private static final int WAIT_TIME_FOR_AFFINITY = 1000; 64 65 /* The model to be benchmarked. */ 66 private MappedByteBuffer model = null; 67 private InputStream labelInputStream = null; 68 private OvicBenchmarker benchmarker; 69 70 private TextView textView = null; 71 // private Button startButton = null; 72 private static final DecimalFormat df2 = new DecimalFormat(".##"); 73 74 @Override onCreate(Bundle savedInstanceState)75 protected void onCreate(Bundle savedInstanceState) { 76 super.onCreate(savedInstanceState); 77 setContentView(R.layout.activity_main); 78 79 // TextView used to display the progress, for information purposes only. 80 textView = (TextView) findViewById(R.id.textView); 81 } 82 loadTestBitmap()83 private Bitmap loadTestBitmap() throws IOException { 84 InputStream imageStream = getAssets().open(testImagePath); 85 return BitmapFactory.decodeStream(imageStream); 86 } 87 initializeTest(boolean benchmarkClassification)88 public void initializeTest(boolean benchmarkClassification) throws IOException { 89 Log.i(TAG, "Initializing benchmarker."); 90 if (benchmarkClassification) { 91 benchmarker = new OvicClassifierBenchmarker(WALL_TIME); 92 labelPath = "labels.txt"; 93 testImagePath = "test_image_224.jpg"; 94 modelPath = "quantized_model.lite"; 95 } else { // Benchmarking detection. 96 benchmarker = new OvicDetectorBenchmarker(WALL_TIME); 97 labelPath = "coco_labels.txt"; 98 testImagePath = "test_image_224.jpg"; 99 modelPath = "detect.lite"; 100 } 101 AssetManager am = getAssets(); 102 AssetFileDescriptor fileDescriptor = am.openFd(modelPath); 103 FileInputStream modelInputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); 104 FileChannel fileChannel = modelInputStream.getChannel(); 105 long startOffset = fileDescriptor.getStartOffset(); 106 long declaredLength = fileDescriptor.getDeclaredLength(); 107 model = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); 108 labelInputStream = am.open(labelPath); 109 } 110 doTestIteration()111 public Boolean doTestIteration() throws IOException, InterruptedException { 112 if (benchmarker == null) { 113 throw new RuntimeException("Benchmarker has not been initialized."); 114 } 115 if (benchmarker.shouldStop()) { 116 return false; 117 } 118 if (!benchmarker.readyToTest()) { 119 Log.i(TAG, "getting ready to test."); 120 benchmarker.getReadyToTest(labelInputStream, model); 121 if (!benchmarker.readyToTest()) { 122 throw new RuntimeException("Failed to get the benchmarker ready."); 123 } 124 } 125 Log.i(TAG, "Going to do test iter."); 126 // Start testing. 127 Bitmap testImageBitmap = loadTestBitmap(); 128 try { 129 if (!benchmarker.processBitmap(testImageBitmap)) { 130 throw new RuntimeException("Failed to run test."); 131 } 132 } catch (Exception e) { 133 e.printStackTrace(); 134 throw e; 135 } finally { 136 testImageBitmap.recycle(); 137 } 138 String iterResultString = benchmarker.getLastResultString(); 139 if (iterResultString == null) { 140 throw new RuntimeException("Inference failed to produce a result."); 141 } 142 Log.i(TAG, iterResultString); 143 return true; 144 } 145 detectPressed(View view)146 public void detectPressed(View view) throws IOException { 147 benchmarkSession(false); 148 } 149 classifyPressed(View view)150 public void classifyPressed(View view) throws IOException { 151 benchmarkSession(true); 152 } 153 benchmarkSession(boolean benchmarkClassification)154 private void benchmarkSession(boolean benchmarkClassification) throws IOException { 155 try { 156 initializeTest(benchmarkClassification); 157 } catch (IOException e) { 158 Log.e(TAG, "Can't initialize benchmarker.", e); 159 throw e; 160 } 161 String displayText = ""; 162 if (benchmarkClassification) { 163 displayText = "Classification benchmark: "; 164 } else { 165 displayText = "Detection benchmark: "; 166 } 167 try { 168 setProcessorAffinity(BIG_CORE_MASK); 169 } catch (IOException e) { 170 Log.e(TAG, e.getMessage()); 171 displayText = e.getMessage() + "\n"; 172 } 173 Log.i(TAG, "Successfully initialized benchmarker."); 174 int testIter = 0; 175 Boolean iterSuccess = false; 176 while (testIter < MAX_ITERATIONS) { 177 try { 178 iterSuccess = doTestIteration(); 179 } catch (IOException e) { 180 Log.e(TAG, "Error during iteration " + testIter); 181 throw e; 182 } catch (InterruptedException e) { 183 Log.e(TAG, "Interrupted at iteration " + testIter); 184 displayText += e.getMessage() + "\n"; 185 } 186 if (!iterSuccess) { 187 break; 188 } 189 testIter++; 190 } 191 Log.i(TAG, "Benchmarking finished"); 192 193 if (textView != null) { 194 if (testIter > 0) { 195 textView.setText( 196 displayText 197 + modelPath 198 + ": Average latency=" 199 + df2.format(benchmarker.getTotalRuntimeNano() * 1.0e-6 / testIter) 200 + "ms after " 201 + testIter 202 + " runs."); 203 } else { 204 textView.setText("Benchmarker failed to run on more than one images."); 205 } 206 } 207 } 208 209 // TODO(b/153429929) Remove with resolution of issue (see below). 210 @SuppressWarnings("RuntimeExec") setProcessorAffinity(int mask)211 private static void setProcessorAffinity(int mask) throws IOException { 212 int myPid = Process.myPid(); 213 Log.i(TAG, String.format("Setting processor affinity to 0x%02x", mask)); 214 215 String command = String.format("taskset -a -p %x %d", mask, myPid); 216 try { 217 // TODO(b/153429929) This is deprecated, but updating is not safe while verification is hard. 218 Runtime.getRuntime().exec(command).waitFor(); 219 } catch (InterruptedException e) { 220 throw new IOException("Interrupted: " + e); 221 } 222 223 // Make sure set took effect - try for a second to confirm the change took. If not then fail. 224 long startTimeMs = SystemClock.elapsedRealtime(); 225 while (true) { 226 int readBackMask = readCpusAllowedMask(); 227 if (readBackMask == mask) { 228 Log.i(TAG, String.format("Successfully set affinity to 0x%02x", mask)); 229 return; 230 } 231 if (SystemClock.elapsedRealtime() > startTimeMs + WAIT_TIME_FOR_AFFINITY) { 232 throw new IOException( 233 String.format( 234 "Core-binding failed: affinity set to 0x%02x but read back as 0x%02x\n" 235 + "please root device.", 236 mask, readBackMask)); 237 } 238 239 try { 240 Thread.sleep(50); 241 } catch (InterruptedException e) { 242 // Ignore sleep interrupted, will sleep again and compare is final cross-check. 243 } 244 } 245 } 246 readCpusAllowedMask()247 public static int readCpusAllowedMask() throws IOException { 248 // Determine how many CPUs there are total 249 final String pathname = "/proc/self/status"; 250 final String resultPrefix = "Cpus_allowed:"; 251 File file = new File(pathname); 252 String line = "<NO LINE READ>"; 253 String allowedCPU = ""; 254 Integer allowedMask = null; 255 BufferedReader bufReader = null; 256 try { 257 bufReader = new BufferedReader(new FileReader(file)); 258 while ((line = bufReader.readLine()) != null) { 259 if (line.startsWith(resultPrefix)) { 260 allowedMask = Integer.valueOf(line.substring(resultPrefix.length()).trim(), 16); 261 allowedCPU = bufReader.readLine(); 262 break; 263 } 264 } 265 } catch (RuntimeException e) { 266 throw new IOException( 267 "Invalid number in " + pathname + " line: \"" + line + "\": " + e.getMessage()); 268 } finally { 269 if (bufReader != null) { 270 bufReader.close(); 271 } 272 } 273 if (allowedMask == null) { 274 throw new IOException(pathname + " missing " + resultPrefix + " line"); 275 } 276 Log.i(TAG, allowedCPU); 277 return allowedMask; 278 } 279 } 280