Java tutorial
/* * Copyright (C) 2017 The Regents of the University of Michigan * * 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 edu.umich.oasis.testapp; import android.app.Activity; import android.content.Context; import android.content.ServiceConnection; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; import android.os.Debug; import android.os.Message; import android.os.OperationCanceledException; import android.os.PowerManager; import android.os.RemoteException; import android.support.v7.app.AlertDialog; import android.support.v7.widget.GridLayout; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.Switch; import android.widget.TextView; import org.apache.commons.lang3.time.StopWatch; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import edu.umich.oasis.client.OASISConnection; import edu.umich.oasis.client.Soda; import edu.umich.oasis.common.IOASISService; import edu.umich.oasis.common.OASISConstants; public class PerfActivity extends Activity implements CompoundButton.OnCheckedChangeListener, View.OnClickListener, OASISConnection.DisconnectCallback { private static final String TAG = "OASIS.PerfTest"; private static final boolean localLOGV = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean localLOGD = Log.isLoggable(TAG, Log.DEBUG); private ScrollView paramsView; private ProgressBar progressBar; private TextView statusText; private Button executeButton; private Switch latencySwitch; private GridLayout latencyGrid; private EditText latencyLoops; private EditText latencyTrials; private CheckBox latencyTainted; private CheckBox latencyUntainted; private EditText latencySparesLow; private EditText latencySparesHigh; private Switch marshalSwitch; private GridLayout marshalGrid; private EditText marshalLoops; private EditText marshalTrials; private EditText marshalSizes; private Switch memorySwitch; private GridLayout memoryGrid; private EditText memorySandboxesMin; private EditText memorySandboxesMax; private OASISConnection conn; private ServiceConnection sc; private PerfTask task; private Resources res; private Soda.S2<Boolean, byte[], Void> execSoda; public static int getClampedSandboxCount(EditText text) { int count = Integer.parseInt(text.getText().toString()); return Math.max(0, Math.min(count, OASISConstants.NUM_SANDBOXES)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_perf); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); paramsView = (ScrollView) findViewById(R.id.perf_params); progressBar = (ProgressBar) findViewById(R.id.perf_progress); statusText = (TextView) findViewById(R.id.perf_status); executeButton = (Button) findViewById(R.id.perf_execute); latencySwitch = (Switch) findViewById(R.id.perf_do_latency); latencyGrid = (GridLayout) findViewById(R.id.perf_params_latency); latencyLoops = (EditText) findViewById(R.id.perf_latency_loops_per_trial); latencyTrials = (EditText) findViewById(R.id.perf_latency_trials_per_run); latencyTainted = (CheckBox) findViewById(R.id.perf_latency_tainted); latencyUntainted = (CheckBox) findViewById(R.id.perf_latency_not_tainted); latencySparesLow = (EditText) findViewById(R.id.perf_latency_spares_low); latencySparesHigh = (EditText) findViewById(R.id.perf_latency_spares_high); marshalSwitch = (Switch) findViewById(R.id.perf_do_marshal); marshalGrid = (GridLayout) findViewById(R.id.perf_params_marshal); marshalLoops = (EditText) findViewById(R.id.perf_marshal_loops_per_trial); marshalTrials = (EditText) findViewById(R.id.perf_marshal_trials_per_run); marshalSizes = (EditText) findViewById(R.id.perf_marshal_sizes); memorySwitch = (Switch) findViewById(R.id.perf_do_memory); memoryGrid = (GridLayout) findViewById(R.id.perf_params_memory); memorySandboxesMin = (EditText) findViewById(R.id.perf_memory_sandboxes_low); memorySandboxesMax = (EditText) findViewById(R.id.perf_memory_sandboxes_high); latencySwitch.setTag(R.id.perf_tests, latencyGrid); latencySwitch.setOnCheckedChangeListener(this); marshalSwitch.setTag(R.id.perf_tests, marshalGrid); marshalSwitch.setOnCheckedChangeListener(this); memorySwitch.setTag(R.id.perf_tests, memoryGrid); memorySwitch.setOnCheckedChangeListener(this); executeButton.setOnClickListener(this); res = getResources(); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { GridLayout layout = (GridLayout) buttonView.getTag(R.id.perf_tests); layout.setVisibility(isChecked ? View.VISIBLE : View.GONE); } @Override public void onClick(View v) { if (task == null) { task = new PerfTask(); OASISConnection.bind(this, this); paramsView.setEnabled(false); executeButton.setText(R.string.perf_cancel); } else { task.cancel(false); paramsView.setEnabled(true); task = null; executeButton.setText(R.string.perf_start); } } @Override public void onDestroy() { super.onDestroy(); } private void onEndTask(int text) { paramsView.setEnabled(true); statusText.setText(text); progressBar.setIndeterminate(false); progressBar.setProgress(1); progressBar.setMax(1); if (this.conn != null) { this.conn.close(); } executeButton.setText(R.string.perf_start); this.task = null; } private void showAlert(Throwable t) { if (t != null) { new AlertDialog.Builder(this).setTitle(R.string.perf_failed_title) .setMessage(Log.getStackTraceString(t)).show(); } } @Override public void onConnect(OASISConnection conn) throws Exception { this.conn = conn; if (this.task != null) { try { execSoda = conn.resolveStatic(void.class, PerfSoda.class, "execSoda", boolean.class, byte[].class); task.execute(memorySwitch.isChecked() ? new MemoryTest() : null, latencySwitch.isChecked() ? new LatencyTest() : null, marshalSwitch.isChecked() ? new MarshalTest() : null); } catch (Exception e) { showAlert(e); onEndTask(R.string.task_failed); } } } @Override public void onDisconnect(OASISConnection conn) throws Exception { this.conn = null; } private final class LatencyTest extends PerfSubtest { private final int loops, trials, sandboxesLow, sandboxesHigh; private final boolean tainted, untainted; private IOASISService svc; public LatencyTest() { super("LatencyTest"); tainted = latencyTainted.isChecked(); untainted = latencyUntainted.isChecked(); if (!tainted && !untainted) { throw new IllegalArgumentException("Must specify one type of test to run"); } loops = Integer.parseInt(latencyLoops.getText().toString()); trials = Integer.parseInt(latencyTrials.getText().toString()); sandboxesLow = getClampedSandboxCount(latencySparesLow); sandboxesHigh = getClampedSandboxCount(latencySparesHigh); } private long executeTrial(PerfTask task, boolean shouldTaint, int numSpares) throws Exception { StopWatch stopWatch = new StopWatch(); // Reset to a known state. svc.setMinHotSpare(numSpares); svc.setSandboxCount(0); svc.setSandboxCount(OASISConstants.NUM_SANDBOXES); // Get all of the sandboxes into steady state. for (int i = 0; i < OASISConstants.NUM_SANDBOXES; i++) { execSoda.arg(shouldTaint).argNull().call(); } svc.forceGarbageCollection(); stopWatch.start(); for (int i = 0; i < loops; i++) { execSoda.arg(shouldTaint).argNull().call(); task.throwIfCancelled(); } stopWatch.stop(); return stopWatch.getNanoTime(); } private void doTrials(PerfTask task, PrintWriter writer, boolean shouldTaint) throws Exception { int progress = 0; final int totalProgress = (sandboxesHigh - sandboxesLow + 1) * trials; for (int numSpares = sandboxesLow; numSpares <= sandboxesHigh; numSpares++) { for (int trial = 1; trial <= trials; trial++) { task.throwIfCancelled(); String status = String.format("%s: Testing %s, %d/%d sandboxes, trial %d/%d", describe(), shouldTaint ? "tainted" : "untainted", numSpares, sandboxesHigh, trial, trials); task.publishProgress(progress++, totalProgress, status); long totalTimeNanos = executeTrial(task, shouldTaint, numSpares); long averageTimeNanos = totalTimeNanos / loops; writer.format("%b,%d,%d,%d,%d,%d", shouldTaint, numSpares, trial, loops, totalTimeNanos, averageTimeNanos); writer.println(); } } } @Override public void execute(PerfTask task) throws Exception { task.publishProgress(-1, -1, describe() + ": Initializing..."); svc = conn.getRawInterface(); final int oldSandboxCount = svc.setSandboxCount(/*0*/OASISConstants.NUM_SANDBOXES); final int oldMinSpare = svc.setMinHotSpare(0); final int oldMaxSpare = svc.setMaxHotSpare(OASISConstants.NUM_SANDBOXES); final int oldMaxIdle = svc.setMaxIdleCount(OASISConstants.NUM_SANDBOXES); try (PrintWriter out = new PrintWriter(openRunOutput("csv"), true)) { out.println("Tainted,Number of Spares,Trial,Loops,Total Latency (ns),Average Latency (ns)"); if (untainted) { doTrials(task, out, false); } if (tainted) { doTrials(task, out, true); } } finally { svc.setSandboxCount(oldSandboxCount); svc.setMaxHotSpare(oldMaxSpare); svc.setMinHotSpare(oldMinSpare); svc.setMaxIdleCount(oldMaxIdle); } } } private final class MarshalTest extends PerfSubtest { private final SparseArray<String> sizes; private final int loops, trials; private int parseSize(String size) { char suffix = Character.toUpperCase(size.charAt(size.length() - 1)); int multiplier; switch (suffix) { case 'B': multiplier = 1; break; case 'K': multiplier = 1024; break; case 'M': multiplier = 1024 * 1024; break; case 'G': multiplier = 1024 * 1024 * 1024; break; default: return Integer.parseInt(size); } final int coefficient = Integer.parseInt(size.substring(0, size.length() - 1).trim()); return coefficient * multiplier; } public MarshalTest() { super("MarshalTest"); loops = Integer.parseInt(marshalLoops.getText().toString()); trials = Integer.parseInt(marshalTrials.getText().toString()); String marshalSizeString = marshalSizes.getText().toString(); String[] humanSizes = marshalSizeString.split(",\\s*"); sizes = new SparseArray<>(humanSizes.length); for (String humanSize : humanSizes) { int size = parseSize(humanSize); sizes.append(size, humanSize); } } private final long NANOS_PER_SEC = (1000L * 1000L * 1000L); public void execute(PerfTask task) throws Exception { final StopWatch stopWatch = new StopWatch(); final IOASISService svc = conn.getRawInterface(); final byte[] emptyByteArray = new byte[0]; try (FileInputStream urandom = new FileInputStream("/dev/urandom"); PrintWriter out = new PrintWriter(openRunOutput("csv"), true)) { out.println( "Data Size,Trial,Loops,Total Latency (ns),Average Latency (ns),Average Bandwidth (bytes/s)"); final int numSizes = sizes.size(); final int totalProgress = numSizes * trials; int progress = 0; for (int offset = 0; offset < numSizes; offset++) { final int size = sizes.keyAt(offset); final String humanSize = sizes.valueAt(offset); task.throwIfCancelled(); task.publishProgress(-1, -1, describe() + ": Generating " + humanSize + " bytes"); final byte[] buf = new byte[size]; urandom.read(buf); for (int trial = 1; trial <= trials; trial++) { task.publishProgress(progress++, totalProgress, String.format("%s: Running %s trial %d/%d", describe(), humanSize, trial, trials)); int oldSandboxes = svc.setSandboxCount(0); svc.setSandboxCount(oldSandboxes); execSoda.arg(false).arg(emptyByteArray).call(); svc.forceGarbageCollection(); stopWatch.start(); for (int loop = 0; loop < loops; loop++) { execSoda.arg(false).arg(buf).call(); } stopWatch.stop(); long totalTimeNanos = stopWatch.getNanoTime(); long averageTimeNanos = totalTimeNanos / loops; long bandwidth = (size * loops * NANOS_PER_SEC) / totalTimeNanos; out.format("%s,%d,%d,%d,%d,%d", humanSize, trial, loops, totalTimeNanos, averageTimeNanos, bandwidth).println(); stopWatch.reset(); } } } } } private final class MemoryTest extends PerfSubtest { final int minSandboxes, maxSandboxes; public MemoryTest() { super("MemoryTest"); minSandboxes = getClampedSandboxCount(memorySandboxesMin); maxSandboxes = getClampedSandboxCount(memorySandboxesMax); } public void execute(PerfTask task) throws Exception { task.publishProgress(-1, -1, describe() + ": Initializing..."); IOASISService svc = conn.getRawInterface(); List<Debug.MemoryInfo> sandboxMemInfo = new ArrayList<>(OASISConstants.NUM_SANDBOXES); Debug.MemoryInfo serviceMemInfo; final int oldSandboxCount = svc.setSandboxCount(0); final int oldMinSpare = svc.setMinHotSpare(0); final int oldMaxSpare = svc.setMaxHotSpare(0); final int oldMaxIdle = svc.setMaxIdleCount(OASISConstants.NUM_SANDBOXES); try (PrintWriter out = new PrintWriter(openRunOutput("csv"), true)) { out.println("Number of Sandboxes,Trusted Service PSS,Sandboxes PSS,Total PSS"); for (int sbCount = minSandboxes; sbCount <= maxSandboxes; sbCount++) { task.throwIfCancelled(); task.publishProgress(sbCount - minSandboxes, maxSandboxes - minSandboxes + 1, describe() + ": Getting memory info for " + sbCount + " sandboxes"); svc.setSandboxCount(0); svc.setSandboxCount(sbCount); for (int i = 0; i < sbCount; i++) { execSoda.arg(false).argNull().forceSandbox(i).call(); } svc.forceGarbageCollection(); sandboxMemInfo.clear(); serviceMemInfo = svc.dumpMemoryInfo(sandboxMemInfo); int servicePss = serviceMemInfo.getTotalPss(); int sandboxPss = 0; for (Debug.MemoryInfo sbInfo : sandboxMemInfo) { if (sbInfo != null) { sandboxPss += sbInfo.getTotalPss(); } } Log.d(TAG, String.format("%d sandboxes, service %d, sandboxes %d", sbCount, servicePss, sandboxPss)); out.format("%d,%d,%d,%d", sbCount, servicePss, sandboxPss, servicePss + sandboxPss).println(); } } finally { svc.setSandboxCount(oldSandboxCount); svc.setMaxHotSpare(oldMaxSpare); svc.setMinHotSpare(oldMinSpare); svc.setMaxIdleCount(oldMaxIdle); } } } private abstract class PerfSubtest { private final String type; protected PerfSubtest(String type) { this.type = Objects.requireNonNull(type); } public String describe() { return type; } private File getOutputFile(String extension) { String fileName = res.getString(R.string.perf_result_filename, type, System.currentTimeMillis(), extension); File file = new File(getExternalFilesDir(null), fileName); file.getParentFile().mkdirs(); return file; } protected OutputStream openRunOutput(String extension) throws IOException { return new BufferedOutputStream(new FileOutputStream(getOutputFile(extension), false)); } public abstract void execute(PerfTask task) throws Exception; } private final class PerfTask extends AsyncTask<PerfSubtest, Message, Throwable> { public final void publishProgress(int amountDone, int totalAmount, CharSequence status) { if (status == null) { status = ""; } publishProgress(Message.obtain(null, amountDone, totalAmount, -1, status)); } public final void publishProgress(int amountDone, int totalAmount, int statusTextId) { publishProgress(Message.obtain(null, amountDone, totalAmount, statusTextId, null)); } @Override protected final void onPreExecute() { res = getResources(); paramsView.setEnabled(false); statusText.setText(R.string.task_starting); progressBar.setIndeterminate(true); } @Override protected final void onPostExecute(Throwable result) { if (result == null) { onEndTask(R.string.task_complete); } else { onEndTask(R.string.task_failed); new AlertDialog.Builder(PerfActivity.this).setTitle(R.string.perf_failed_title) .setMessage(Log.getStackTraceString(result)).show(); } } @Override protected final void onProgressUpdate(Message... values) { Message msg = values[0]; int progressAmount = msg.what; int totalAmount = msg.arg1; int resId = msg.arg2; CharSequence status = (CharSequence) msg.obj; if (progressAmount == -1) { progressBar.setIndeterminate(true); } else { progressBar.setIndeterminate(false); progressBar.setMax(totalAmount); progressBar.setProgress(progressAmount); } if (resId != -1) { statusText.setText(resId); } else { statusText.setText(status); } msg.recycle(); } @Override protected final void onCancelled(Throwable unused) { onEndTask(R.string.task_cancelled); } @Override protected final Throwable doInBackground(PerfSubtest... params) { final PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); final PowerManager.WakeLock lock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); try { lock.acquire(); for (PerfSubtest test : params) { if (test == null) { continue; } String description = test.describe(); String status = res.getString(R.string.task_starting_task, description); publishProgress(-1, -1, status); try { test.execute(this); } catch (OperationCanceledException oce) { Log.i(TAG, "Cancelled", oce); return oce; } catch (Throwable th) { Log.e(TAG, "Error when executing task " + description, th); return th; } } return null; } finally { if (lock.isHeld()) { lock.release(); } } } public final boolean isTaskCancelled() { return super.isCancelled(); } public final void throwIfCancelled() throws OperationCanceledException { if (super.isCancelled()) { throw new OperationCanceledException(); } } } }