• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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