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 private static String testImagePath = null; 49 private static String modelPath = null; 50 /** 51 * Each bottom press will launch a benchmarking experiment. The experiment stops when either the 52 * total native latency reaches WALL_TIME or the number of iterations reaches MAX_ITERATIONS, 53 * whichever comes first. 54 */ 55 /** Wall time for each benchmarking experiment. */ 56 private static final double WALL_TIME = 3000; 57 /** Maximum number of iterations in each benchmarking experiment. */ 58 private static final int MAX_ITERATIONS = 100; 59 /** Mask for binding to a single big core. Pixel 1 (4), Pixel 2 (16). */ 60 private static final int BIG_CORE_MASK = 16; 61 /** Amount of time in milliseconds to wait for affinity to set. */ 62 private static final int WAIT_TIME_FOR_AFFINITY = 1000; 63 64 /* The model to be benchmarked. */ 65 private MappedByteBuffer model = null; 66 private InputStream labelInputStream = null; 67 private OvicBenchmarker benchmarker; 68 69 private TextView textView = null; 70 // private Button startButton = null; 71 private static final DecimalFormat df2 = new DecimalFormat(".##"); 72 73 @Override onCreate(Bundle savedInstanceState)74 protected void onCreate(Bundle savedInstanceState) { 75 super.onCreate(savedInstanceState); 76 setContentView(R.layout.activity_main); 77 78 // TextView used to display the progress, for information purposes only. 79 textView = (TextView) findViewById(R.id.textView); 80 } 81 loadTestBitmap()82 private Bitmap loadTestBitmap() throws IOException { 83 InputStream imageStream = getAssets().open(testImagePath); 84 return BitmapFactory.decodeStream(imageStream); 85 } 86 initializeTest(boolean benchmarkClassification)87 public void initializeTest(boolean benchmarkClassification) throws IOException { 88 Log.i(TAG, "Initializing benchmarker."); 89 if (benchmarkClassification) { 90 benchmarker = new OvicClassifierBenchmarker(WALL_TIME); 91 labelPath = "labels.txt"; 92 testImagePath = "test_image_224.jpg"; 93 modelPath = "quantized_model.lite"; 94 } else { // Benchmarking detection. 95 benchmarker = new OvicDetectorBenchmarker(WALL_TIME); 96 labelPath = "coco_labels.txt"; 97 testImagePath = "test_image_224.jpg"; 98 modelPath = "detect.lite"; 99 } 100 AssetManager am = getAssets(); 101 AssetFileDescriptor fileDescriptor = am.openFd(modelPath); 102 FileInputStream modelInputStream = new FileInputStream(fileDescriptor.getFileDescriptor()); 103 FileChannel fileChannel = modelInputStream.getChannel(); 104 long startOffset = fileDescriptor.getStartOffset(); 105 long declaredLength = fileDescriptor.getDeclaredLength(); 106 model = fileChannel.map(FileChannel.MapMode.READ_ONLY, startOffset, declaredLength); 107 labelInputStream = am.open(labelPath); 108 } 109 doTestIteration()110 public Boolean doTestIteration() throws IOException, InterruptedException { 111 if (benchmarker == null) { 112 throw new RuntimeException("Benchmarker has not been initialized."); 113 } 114 if (benchmarker.shouldStop()) { 115 return false; 116 } 117 if (!benchmarker.readyToTest()) { 118 Log.i(TAG, "getting ready to test."); 119 benchmarker.getReadyToTest(labelInputStream, model); 120 if (!benchmarker.readyToTest()) { 121 throw new RuntimeException("Failed to get the benchmarker ready."); 122 } 123 } 124 Log.i(TAG, "Going to do test iter."); 125 // Start testing. 126 Bitmap testImageBitmap = loadTestBitmap(); 127 try { 128 if (!benchmarker.processBitmap(testImageBitmap)) { 129 throw new RuntimeException("Failed to run test."); 130 } 131 } catch (Exception e) { 132 e.printStackTrace(); 133 throw e; 134 } finally { 135 testImageBitmap.recycle(); 136 } 137 String iterResultString = benchmarker.getLastResultString(); 138 if (iterResultString == null) { 139 throw new RuntimeException("Inference failed to produce a result."); 140 } 141 Log.i(TAG, iterResultString); 142 return true; 143 } 144 detectPressed(View view)145 public void detectPressed(View view) throws IOException { 146 benchmarkSession(false); 147 } classifyPressed(View view)148 public void classifyPressed(View view) throws IOException { 149 benchmarkSession(true); 150 } 151 benchmarkSession(boolean benchmarkClassification)152 private void benchmarkSession(boolean benchmarkClassification) throws IOException { 153 try { 154 initializeTest(benchmarkClassification); 155 } catch (IOException e) { 156 Log.e(TAG, "Can't initialize benchmarker.", e); 157 throw e; 158 } 159 String displayText = ""; 160 if (benchmarkClassification) { 161 displayText = "Classification benchmark: "; 162 } else { 163 displayText = "Detection benchmark: "; 164 } 165 try { 166 setProcessorAffinity(BIG_CORE_MASK); 167 } catch (IOException e) { 168 Log.e(TAG, e.getMessage()); 169 displayText = e.getMessage() + "\n"; 170 } 171 Log.i(TAG, "Successfully initialized benchmarker."); 172 int testIter = 0; 173 Boolean iterSuccess = false; 174 while (testIter < MAX_ITERATIONS) { 175 try { 176 iterSuccess = doTestIteration(); 177 } catch (IOException e) { 178 Log.e(TAG, "Error during iteration " + testIter); 179 throw e; 180 } catch (InterruptedException e) { 181 Log.e(TAG, "Interrupted at iteration " + testIter); 182 displayText += e.getMessage() + "\n"; 183 } 184 if (!iterSuccess) { 185 break; 186 } 187 testIter++; 188 } 189 Log.i(TAG, "Benchmarking finished"); 190 191 if (textView != null) { 192 if (testIter > 0) { 193 textView.setText( 194 displayText 195 + modelPath 196 + ": Average latency=" 197 + df2.format(benchmarker.getTotalRunTime() / testIter) 198 + "ms after " 199 + testIter 200 + " runs."); 201 } else { 202 textView.setText("Benchmarker failed to run on more than one images."); 203 } 204 } 205 } 206 setProcessorAffinity(int mask)207 private static void setProcessorAffinity(int mask) throws IOException { 208 int myPid = Process.myPid(); 209 Log.i(TAG, String.format("Setting processor affinity to 0x%02x", mask)); 210 211 String command = String.format("taskset -a -p %x %d", mask, myPid); 212 try { 213 Runtime.getRuntime().exec(command).waitFor(); 214 } catch (InterruptedException e) { 215 throw new IOException("Interrupted: " + e); 216 } 217 218 // Make sure set took effect - try for a second to confirm the change took. If not then fail. 219 long startTimeMs = SystemClock.elapsedRealtime(); 220 while (true) { 221 int readBackMask = readCpusAllowedMask(); 222 if (readBackMask == mask) { 223 Log.i(TAG, String.format("Successfully set affinity to 0x%02x", mask)); 224 return; 225 } 226 if (SystemClock.elapsedRealtime() > startTimeMs + WAIT_TIME_FOR_AFFINITY) { 227 throw new IOException( 228 String.format( 229 "Core-binding failed: affinity set to 0x%02x but read back as 0x%02x\n" 230 + "please root device.", 231 mask, readBackMask)); 232 } 233 234 try { 235 Thread.sleep(50); 236 } catch (InterruptedException e) { 237 // Ignore sleep interrupted, will sleep again and compare is final cross-check. 238 } 239 } 240 } 241 readCpusAllowedMask()242 public static int readCpusAllowedMask() throws IOException { 243 // Determine how many CPUs there are total 244 final String pathname = "/proc/self/status"; 245 final String resultPrefix = "Cpus_allowed:"; 246 File file = new File(pathname); 247 String line = "<NO LINE READ>"; 248 String allowedCPU = ""; 249 Integer allowedMask = null; 250 BufferedReader bufReader = null; 251 try { 252 bufReader = new BufferedReader(new FileReader(file)); 253 while ((line = bufReader.readLine()) != null) { 254 if (line.startsWith(resultPrefix)) { 255 allowedMask = Integer.valueOf(line.substring(resultPrefix.length()).trim(), 16); 256 allowedCPU = bufReader.readLine(); 257 break; 258 } 259 } 260 } catch (RuntimeException e) { 261 throw new IOException( 262 "Invalid number in " + pathname + " line: \"" + line + "\": " + e.getMessage()); 263 } finally { 264 if (bufReader != null) { 265 bufReader.close(); 266 } 267 } 268 if (allowedMask == null) { 269 throw new IOException(pathname + " missing " + resultPrefix + " line"); 270 } 271 Log.i(TAG, allowedCPU); 272 return allowedMask; 273 } 274 } 275