From fa61a4540473308c8d200e910ccceb401afadddc Mon Sep 17 00:00:00 2001 From: kuloud Date: Sun, 15 Mar 2020 19:51:59 +0800 Subject: [PATCH] #28 Add Performance Metrics statistics --- .../common/stat/PerformanceDataManager.java | 413 ++++++++++++++++++ .../library/common/stat/PerformanceMetrics.kt | 28 ++ .../didi/aoe/library/common/stat/StatInfo.kt | 11 +- .../didi/aoe/library/common/stat/TimeStat.kt | 4 +- .../didi/aoe/library/common/util/FileUtils.kt | 51 +++ .../com/didi/aoe/library/core/AoeClient.kt | 36 +- .../features/mnist/MnistFeatureFragment.java | 4 +- 7 files changed, 541 insertions(+), 6 deletions(-) create mode 100644 Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceDataManager.java create mode 100644 Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceMetrics.kt diff --git a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceDataManager.java b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceDataManager.java new file mode 100644 index 0000000..2d9e9b8 --- /dev/null +++ b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceDataManager.java @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. + * Copyright 2020 The AoE Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.didi.aoe.library.common.stat; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.*; +import android.support.annotation.RequiresApi; +import android.text.TextUtils; +import android.view.Choreographer; +import com.didi.aoe.library.common.util.FileUtils; +import com.didi.aoe.library.logging.Logger; +import com.didi.aoe.library.logging.LoggerFactory; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.lang.Process; + +/** + * Created by wanglikun on 2018/9/13. + *

+ * https://github.com/didi/DoraemonKit/blob/master/Android/doraemonkit/src/main/java/com/didichuxing/doraemonkit/kit/common/PerformanceDataManager.java + * + * @modified Noctis + */ + +public class PerformanceDataManager { + private final Logger mLogger = LoggerFactory.getLogger("Performance"); + + private static final float SECOND_IN_NANOS = 1000000000f; + private static final int NORMAL_FRAME_RATE = 1; + private String filePath; + private String memoryFileName = "memory.txt"; + private String cpuFileName = "cpu.txt"; + private String fpsFileName = "fps.txt"; + + private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA); + private long mLastFrameTimeNanos; + private int mLastFrameRate; + private int mLastSkippedFrames; + private float mLastCpuRate; + private float mLastMemoryInfo; + private String mPackageName; + private Handler mHandler; + private HandlerThread mHandlerThread; + private float mMaxMemory; + private ActivityManager mActivityManager; + private RandomAccessFile mProcStatFile; + private RandomAccessFile mAppStatFile; + private Long mLastCpuTime; + private Long mLastAppCpuTime; + private boolean mAboveAndroidO; // 是否是8.0及其以上 + private static final int MSG_CPU = 1; + private static final int MSG_MEMORY = 2; + + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + if (mLastFrameTimeNanos != 0L) { + long temp = frameTimeNanos - mLastFrameTimeNanos; + if (temp != 0) { + mLastFrameRate = Math.round(SECOND_IN_NANOS / (frameTimeNanos - mLastFrameTimeNanos)); + mLastSkippedFrames = 60 - mLastFrameRate; + } + } + mLastFrameTimeNanos = frameTimeNanos; + Choreographer.getInstance().postFrameCallback(this); + writeFpsDataIntoFile(); + } + }; + + private void executeCpuData() { +// mLogger.debug("current thread name is ==" + Thread.currentThread().getName()); + if (mAboveAndroidO) { + mLastCpuRate = getCpuDataForO(); +// mLogger.debug("cpu info is =" + mLastCpuRate); + writeCpuDataIntoFile(); + } else { + mLastCpuRate = getCPUData(); +// mLogger.debug("cpu info is =" + mLastCpuRate); + writeCpuDataIntoFile(); + } + } + + private float getCpuDataForO() { + Process process = null; + try { + process = Runtime.getRuntime().exec("top -n 1"); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + int cpuIndex = -1; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (TextUtils.isEmpty(line)) { + continue; + } + int tempIndex = getCPUIndex(line); + if (tempIndex != -1) { + cpuIndex = tempIndex; + continue; + } + if (line.startsWith(String.valueOf(android.os.Process.myPid()))) { + if (cpuIndex == -1) { + continue; + } + String[] param = line.split("\\s+"); + if (param.length <= cpuIndex) { + continue; + } + String cpu = param[cpuIndex]; + if (cpu.endsWith("%")) { + cpu = cpu.substring(0, cpu.lastIndexOf("%")); + } + float rate = Float.parseFloat(cpu) / Runtime.getRuntime().availableProcessors(); + return rate; + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (process != null) { + process.destroy(); + } + } + return 0; + } + + private int getCPUIndex(String line) { + if (line.contains("CPU")) { + String[] titles = line.split("\\s+"); + for (int i = 0; i < titles.length; i++) { + if (titles[i].contains("CPU")) { + return i; + } + } + } + return -1; + } + + private void executeMemoryData() { + mLastMemoryInfo = getMemoryData(); +// mLogger.debug("memory info is =" + mLastMemoryInfo); + writeMemoryDataIntoFile(); + } + + private static class Holder { + private static PerformanceDataManager INSTANCE = new PerformanceDataManager(); + } + + private PerformanceDataManager() { + } + + public static PerformanceDataManager getInstance() { + return Holder.INSTANCE; + } + + public void init(Context context) { + filePath = getFilePath(context); + mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mAboveAndroidO = true; + mPackageName = context.getPackageName(); + } + if (mHandlerThread == null) { + mHandlerThread = new HandlerThread("handler-thread"); + mHandlerThread.start(); + } + if (mHandler == null) { + mHandler = new Handler(mHandlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (msg.what == MSG_CPU) { + executeCpuData(); + if (mHandler != null) { + mHandler.sendEmptyMessageDelayed(MSG_CPU, NORMAL_FRAME_RATE * 1000); + } + } else if (msg.what == MSG_MEMORY) { + executeMemoryData(); + if (mHandler != null) { + mHandler.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_FRAME_RATE * 1000); + } + } + } + }; + } + } + + private String getFilePath(Context context) { + return context.getFilesDir().getAbsolutePath() + "/doraemon/"; + } + + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void startMonitorFrameInfo() { + Choreographer.getInstance().postFrameCallback(mFrameCallback); + } + + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public void stopMonitorFrameInfo() { + Choreographer.getInstance().removeFrameCallback(mFrameCallback); + } + + public void startMonitorCPUInfo() { + mHandler.sendEmptyMessageDelayed(MSG_CPU, NORMAL_FRAME_RATE * 1000); + } + + public void stopMonitorCPUInfo() { + mHandler.removeMessages(MSG_CPU); + } + + public void destroy() { + if (mHandlerThread != null) { + mHandlerThread.quit(); + } + + if (mHandler == null) { + return; + } + stopMonitorMemoryInfo(); + stopMonitorCPUInfo(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + stopMonitorFrameInfo(); + } + + mHandlerThread = null; + mHandler = null; + } + + public void startMonitorMemoryInfo() { + if (mMaxMemory == 0) { + mMaxMemory = mActivityManager.getMemoryClass(); + } + mHandler.sendEmptyMessageDelayed(MSG_MEMORY, NORMAL_FRAME_RATE * 1000); + } + + public void stopMonitorMemoryInfo() { + mHandler.removeMessages(MSG_MEMORY); + } + + private void writeCpuDataIntoFile() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(mLastCpuRate); + stringBuilder.append(" "); + stringBuilder.append(simpleDateFormat.format(new Date(System.currentTimeMillis()))); + FileUtils.writeTxtToFile(stringBuilder.toString(), filePath, cpuFileName); + } + + private void writeMemoryDataIntoFile() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(mLastMemoryInfo); + stringBuilder.append(" "); + stringBuilder.append(simpleDateFormat.format(new Date(System.currentTimeMillis()))); + FileUtils.writeTxtToFile(stringBuilder.toString(), filePath, memoryFileName); + } + + private void writeFpsDataIntoFile() { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(mLastFrameRate); + stringBuilder.append(" "); + stringBuilder.append(simpleDateFormat.format(new Date(System.currentTimeMillis()))); + FileUtils.writeTxtToFile(stringBuilder.toString(), filePath, fpsFileName); + } + + private float getCPUData() { + long cpuTime; + long appTime; + float value = 0.0f; + try { + if (mProcStatFile == null || mAppStatFile == null) { + mProcStatFile = new RandomAccessFile("/proc/stat", "r"); + mAppStatFile = new RandomAccessFile("/proc/" + android.os.Process.myPid() + "/stat", "r"); + } else { + mProcStatFile.seek(0L); + mAppStatFile.seek(0L); + } + String procStatString = mProcStatFile.readLine(); + String appStatString = mAppStatFile.readLine(); + String[] procStats = procStatString.split(" "); + String[] appStats = appStatString.split(" "); + cpuTime = Long.parseLong(procStats[2]) + Long.parseLong(procStats[3]) + + Long.parseLong(procStats[4]) + Long.parseLong(procStats[5]) + + Long.parseLong(procStats[6]) + Long.parseLong(procStats[7]) + + Long.parseLong(procStats[8]); + appTime = Long.parseLong(appStats[13]) + Long.parseLong(appStats[14]); + if (mLastCpuTime == null && mLastAppCpuTime == null) { + mLastCpuTime = cpuTime; + mLastAppCpuTime = appTime; + return value; + } + value = ((float) (appTime - mLastAppCpuTime) / (float) (cpuTime - mLastCpuTime)) * 100f; + mLastCpuTime = cpuTime; + mLastAppCpuTime = appTime; + } catch (Exception e) { + mLogger.warn("getCPUData fail: " + e.toString()); + } + return value; + } + + private float getMemoryData() { + float mem = 0.0F; + try { + // 统计进程的内存信息 totalPss + final Debug.MemoryInfo[] memInfo = mActivityManager.getProcessMemoryInfo(new int[]{android.os.Process.myPid()}); + if (memInfo.length > 0) { + // TotalPss = dalvikPss + nativePss + otherPss, in KB + final int totalPss = memInfo[0].getTotalPss(); + if (totalPss >= 0) { + // Mem in MB + mem = totalPss / 1024.0F; + } + } + } catch (Exception e) { + mLogger.warn("getMemoryData fail: " + e.toString()); + } + return mem; + } + + private float parseMemoryData(String data) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data.getBytes()))); + String line; + while ((line = bufferedReader.readLine()) != null) { + line = line.trim(); + if (line.contains("Permission Denial")) { + break; + } else { + String[] lineItems = line.split("\\s+"); + if (lineItems != null && lineItems.length > 1) { + String result = lineItems[0]; + mLogger.debug("result is ==" + result); + bufferedReader.close(); + if (!TextUtils.isEmpty(result) && result.contains("K:")) { + result = result.replace("K:", ""); + if (result.contains(",")) { + result = result.replace(",", "."); + } + } + // Mem in MB + return Float.parseFloat(result) / 1024; + } + } + } + return 0; + } + + private float parseCPUData(String data) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data.getBytes()))); + String line; + while ((line = bufferedReader.readLine()) != null) { + line = line.trim(); + if (line.contains("Permission Denial")) { + break; + } else { + String[] lineItems = line.split("\\s+"); + if (lineItems != null && lineItems.length > 1) { + mLogger.debug("result is ==" + lineItems[0]); + bufferedReader.close(); + return Float.parseFloat(lineItems[0].replace("%", "")); + } + } + } + return 0; + } + + public String getCpuFilePath() { + return filePath + cpuFileName; + } + + public String getMemoryFilePath() { + return filePath + memoryFileName; + } + + public String getFpsFilePath() { + return filePath + fpsFileName; + } + + public long getLastFrameRate() { + return mLastFrameRate; + } + + public float getLastCpuRate() { + return mLastCpuRate; + } + + public float getLastMemoryInfo() { + return mLastMemoryInfo; + } + + public int getLastSkippedFrames() { + return mLastSkippedFrames; + } + + public float getMaxMemory() { + return mMaxMemory; + } +} diff --git a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceMetrics.kt b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceMetrics.kt new file mode 100644 index 0000000..0eb5c90 --- /dev/null +++ b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/PerformanceMetrics.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The AoE Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.didi.aoe.library.common.stat + +/** + * + * + * @author noctis + * @since 1.1.0 + */ +enum class PerformanceMetrics { + CPU, + MEMERY +} \ No newline at end of file diff --git a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/StatInfo.kt b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/StatInfo.kt index 816073a..deec907 100644 --- a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/StatInfo.kt +++ b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/StatInfo.kt @@ -23,5 +23,12 @@ package com.didi.aoe.library.common.stat * @since 1.1.0 */ data class StatInfo @JvmOverloads constructor( - var timeCostInMills: Long = 0 -) \ No newline at end of file + var timeCostInMills: Long = 0, + var cpuRate: Float = 0f, + var memoryInfo: Float = 0f, + var maxMemory: Float = 0f +) { + override fun toString(): String { + return "StatInfo(timeCostInMills=$timeCostInMills, cpuRate=$cpuRate, memoryInfo=$memoryInfo, maxMemory=$maxMemory)" + } +} \ No newline at end of file diff --git a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/TimeStat.kt b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/TimeStat.kt index a4956c6..2ece8b5 100644 --- a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/TimeStat.kt +++ b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/stat/TimeStat.kt @@ -23,7 +23,7 @@ package com.didi.aoe.library.common.stat * @since 1.1.0 */ class TimeStat { - + companion object { private val startTimeCache = mutableMapOf() private val endTimeCache = mutableMapOf() @@ -40,7 +40,7 @@ class TimeStat { @JvmStatic fun getCostTime(key: String): Long { - return endTimeCache[key] ?: 0 - (startTimeCache[key] ?: 0) + return (endTimeCache[key] ?: 0) - (startTimeCache[key] ?: 0) } } } \ No newline at end of file diff --git a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/util/FileUtils.kt b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/util/FileUtils.kt index e85fc93..7bd2e11 100644 --- a/Android/aoe/common/src/main/java/com/didi/aoe/library/common/util/FileUtils.kt +++ b/Android/aoe/common/src/main/java/com/didi/aoe/library/common/util/FileUtils.kt @@ -119,4 +119,55 @@ object FileUtils { } return null } + + // 将字符串写入到文本文件中 porting form https://github.com/didi/DoraemonKit/blob/master/Android/doraemonkit/src/main/java/com/didichuxing/doraemonkit/util/FileUtil.java + @JvmStatic + fun writeTxtToFile(strcontent: String, filePath: String, fileName: String) { + //生成文件夹之后,再生成文件,不然会出错 + makeFilePath(filePath, fileName) + val strFilePath = filePath + fileName + // 每次写入时,都换行写 + val strContent = "$strcontent\r\n" + try { + val file = File(strFilePath) + if (!file.exists()) { + mLogger.debug("Create the file:$strFilePath") + file.parentFile.mkdirs() + file.createNewFile() + } + val raf = RandomAccessFile(file, "rwd") + raf.seek(file.length()) + raf.write(strContent.toByteArray()) + raf.close() + } catch (e: java.lang.Exception) { + mLogger.debug("Error on write File:$e") + } + } + + // 生成文件 + private fun makeFilePath(filePath: String, fileName: String): File? { + var file: File? = null + makeRootDirectory(filePath) + try { + file = File(filePath + fileName) + if (!file.exists()) { + file.createNewFile() + } + } catch (e: java.lang.Exception) { + e.printStackTrace() + } + return file + } + + // 生成文件夹 + private fun makeRootDirectory(filePath: String) { + try { + val file = File(filePath) + if (!file.exists()) { + file.mkdir() + } + } catch (e: java.lang.Exception) { + mLogger.info("error:", e.toString() + "") + } + } } \ No newline at end of file diff --git a/Android/aoe/core/src/main/java/com/didi/aoe/library/core/AoeClient.kt b/Android/aoe/core/src/main/java/com/didi/aoe/library/core/AoeClient.kt index 4d0fcec..9cf4253 100644 --- a/Android/aoe/core/src/main/java/com/didi/aoe/library/core/AoeClient.kt +++ b/Android/aoe/core/src/main/java/com/didi/aoe/library/core/AoeClient.kt @@ -26,6 +26,8 @@ import com.didi.aoe.library.api.StatusCode import com.didi.aoe.library.api.interpreter.InterpreterInitResult import com.didi.aoe.library.api.interpreter.InterpreterInitResult.Companion.create import com.didi.aoe.library.api.interpreter.OnInterpreterInitListener +import com.didi.aoe.library.common.stat.PerformanceDataManager +import com.didi.aoe.library.common.stat.PerformanceMetrics import com.didi.aoe.library.common.stat.StatInfo import com.didi.aoe.library.common.stat.TimeStat.Companion.getCostTime import com.didi.aoe.library.common.stat.TimeStat.Companion.setEnd @@ -45,8 +47,10 @@ class AoeClient { private val mContext: Context private var mClientId: String + // AoE 执行器 private val mProcessor: AoeProcessor + // Client 配置信息 private val mOptions: Options @@ -55,6 +59,7 @@ class AoeClient { // 模型assets目录地址 private val mModelDirs: Array + // 模型配置信息 private val mModelOptions: MutableList = ArrayList() @@ -139,6 +144,16 @@ class AoeClient { fun init(listener: OnInitListener?) { mLogger.debug("[init]") initInternal(listener) + + if (!mOptions.performanceMetrics.isNullOrEmpty()) { + PerformanceDataManager.getInstance().init(mContext) + if (mOptions.performanceMetrics!!.contains(PerformanceMetrics.CPU)) { + PerformanceDataManager.getInstance().startMonitorCPUInfo() + } + if (mOptions.performanceMetrics!!.contains(PerformanceMetrics.MEMERY)) { + PerformanceDataManager.getInstance().startMonitorMemoryInfo() + } + } } @Throws(AoeIOException::class) @@ -246,6 +261,7 @@ class AoeClient { fun release() { mLogger.debug("[release]") mProcessor.interpreterComponent.release() + PerformanceDataManager.getInstance().destroy() } /** @@ -255,7 +271,10 @@ class AoeClient { */ fun acquireLatestStatInfo(): StatInfo { return StatInfo( - getCostTime(generalClientKey("process")) + getCostTime(generalClientKey("process")), + cpuRate = PerformanceDataManager.getInstance().lastCpuRate, + memoryInfo = PerformanceDataManager.getInstance().lastMemoryInfo, + maxMemory = PerformanceDataManager.getInstance().maxMemory ) } @@ -273,20 +292,29 @@ class AoeClient { private set var parcelerClassName: String? = null private set + /** * 使用独立进程进行模型加载和推理, 默认false */ var isUseRemoteService = false private set + /** * 当[.useRemoteService] = false 时,优先应用 interpreter 实例,当未指定时,使用interpreterClassName构造 */ var interpreter: InterpreterComponent<*, *>? = null private set + @IntRange(from = 1, to = MAX_THREAD_NUM) var threadNum = 1 private set + /** + * 使用性能分析模式,统计CPU&内存 + */ + var performanceMetrics: Array? = null + private set + /** * 设置模型配置加载器,适用与自定义模型配置策略 * @@ -332,6 +360,12 @@ class AoeClient { return this } + + fun setPerformanceMetrics(vararg metrics: PerformanceMetrics): Options { + performanceMetrics = metrics + return this + } + /** * 设置推理引擎执行线程数,如推理引擎不支持,则设置失效,默认为引擎推荐设置 * diff --git a/Android/examples/demo/src/main/java/com/didi/aoe/examples/demo/features/mnist/MnistFeatureFragment.java b/Android/examples/demo/src/main/java/com/didi/aoe/examples/demo/features/mnist/MnistFeatureFragment.java index 5b87bef..9ef19b0 100644 --- a/Android/examples/demo/src/main/java/com/didi/aoe/examples/demo/features/mnist/MnistFeatureFragment.java +++ b/Android/examples/demo/src/main/java/com/didi/aoe/examples/demo/features/mnist/MnistFeatureFragment.java @@ -14,6 +14,7 @@ import com.didi.aoe.features.mnist.model.SketchModel; import com.didi.aoe.features.mnist.render.SketchRenderer; import com.didi.aoe.features.mnist.widget.SketchView; +import com.didi.aoe.library.common.stat.PerformanceMetrics; import com.didi.aoe.library.core.AoeClient; import com.didi.aoe.library.service.AoeModelOptionLoader; @@ -38,6 +39,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { new AoeClient.Options() .setModelOptionLoader(AoeModelOptionLoader.class) .setInterpreter(MnistInterpreter.class) + .setPerformanceMetrics(PerformanceMetrics.CPU, PerformanceMetrics.MEMERY) .useRemoteService(false), "mnist"); mClient.init(new AoeClient.OnInitListener() { @@ -77,7 +79,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat Object result = mClient.process(mSketchModel.getPixelData()); if (result instanceof Integer) { int num = (int) result; - Log.d(TAG, "num: " + num); + Log.d(TAG, "num: " + num + ", " + mClient.acquireLatestStatInfo()); mResultTextView.post( () -> mResultTextView.setText((num == -1) ? "Not recognized." : String.valueOf(num))); }