Merge "Moving BTtraffic from experiment location to here" into cw-d-mr1-dev

This commit is contained in:
Yuchao Zhou
2015-09-16 22:42:34 +00:00
committed by Android (Google) Code Review
10 changed files with 690 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := bttraffic
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.experimental.bttraffic" >
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18"
/>
<application
android:allowBackup="false"
android:label="@string/app_name" >
<service
android:name=".BTtraffic"
android:enabled="true"
android:exported="true" >
</service>
</application>
</manifest>

View File

@@ -0,0 +1,45 @@
This is a tool to generate classic Bluetooth traffic with specified period and package size.
Together with the SvcMonitor, which will be called automatically in this android service, can be
used to measure the CPU usage from the Java layer Bluetooth code and the underlying system service
com.android.bluetooth.
1. Server (Listener) - Client (Sender) model. Both run as an Android service.
2. No pairing needed. Communicate via unsecured RFcomm. Client establishes the connection by
providing the MAC addr of the server.
3. Bluetooth has to be turned on on both side.
4. Client can configure the traffic by specifying the transfer period and package size.
5. A separate monitor process will be automatically forked and will be reading from /proc file
system to calculate the cpu usage. The measurement is updated once per second.
6. The monitor process (com.google.android.experimental.svcmonitor/.ScvMonitor) can be run as an
independent service to measure cpu usage on any similarly configured tests (e.g. wifi, BLE). Refer
to SvcMonitor's README for usage and details.
Usage:
To instal the test:
On both the server and client device, install the 2 apk:
$ adb install $OUT/system/app/bttraffic/bttraffic.apk
$ adb install $OUT/system/app/svcmonitor/svcmonitor.apk
To start the service on the SERVER side:
$ adb shell am startservice -a start --ez ack true \
com.google.android.experimental.bttraffic/.BTtraffic
To start the service on the CLIENT side:
$ adb shell am startservice -a start \
-e addr "F8:A9:D0:A8:74:8E" --ei size 1000 --ei period 15 \
com.google.android.experimental.bttraffic/.BTtraffic
To stop the test:
On either the server or client:
$ adb shell am startservice -a stop \
com.google.android.experimental.bttraffic/.BTtraffic
To look at the data:
$ adb logcat | grep bttraffic
Options:
-e addr: MAC addr of the server, in uppercase letter.
--ei size: package size, unit: byte; default: 1024, MAX: 20MB
--ei period: system sleep time between sending each package, unit: ms, default: 5000
** if -1 is provided, client will only send the package once.
--ez ack: whether acknowledge is required (true/false)

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Bluetooth Test</string>
</resources>

View File

@@ -0,0 +1,328 @@
package com.google.android.experimental.bttraffic;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Exception;
import java.lang.Runtime;
import java.lang.RuntimeException;
import java.lang.Process;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
public class BTtraffic extends Service {
public static final String TAG = "bttraffic";
static final String SERVICE_NAME = "bttraffic";
static final String SYS_SERVICE_NAME = "com.android.bluetooth";
static final UUID SERVICE_UUID = UUID.fromString("5e8945b0-1234-5432-a5e2-0800200c9a67");
volatile Thread mWorkerThread;
volatile boolean isShuttingDown = false;
volatile boolean isServer = false;
public BTtraffic() {}
static void safeClose(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
Log.d(TAG, "Unable to close resource.\n");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
stopSelf();
return 0;
}
if ("stop".equals(intent.getAction())) {
stopService();
} else if ("start".equals(intent.getAction())) {
startWorker(intent);
} else {
Log.d(TAG, "unknown action: + " + intent.getAction());
}
return 0;
}
private void startWorker(Intent intent) {
if (mWorkerThread != null) {
Log.d(TAG, "worker thread already active");
return;
}
isShuttingDown = false;
String remoteAddr = intent.getStringExtra("addr");
Log.d(TAG, "startWorker: addr=" + remoteAddr);
Runnable worker =
remoteAddr == null
? new ListenerRunnable(this, intent)
: new SenderRunnable(this, remoteAddr, intent);
isServer = remoteAddr == null ? true: false;
mWorkerThread = new Thread(worker, "BTtrafficWorker");
try {
startMonitor();
Log.d(TAG, "Monitor service started");
mWorkerThread.start();
Log.d(TAG, "Worker thread started");
} catch (Exception e) {
Log.d(TAG, "Failed to start service", e);
}
}
private void startMonitor()
throws Exception {
if (isServer) {
Log.d(TAG, "Start monitor on server");
String[] startmonitorCmd = {
"/system/bin/am",
"startservice",
"-a", "start",
"-e", "java", SERVICE_NAME,
"-e", "hal", SYS_SERVICE_NAME,
"com.google.android.experimental.svcmonitor/.SvcMonitor"
};
Process ps = new ProcessBuilder()
.command(startmonitorCmd)
.redirectErrorStream(true)
.start();
} else {
Log.d(TAG, "No need to start SvcMonitor on client");
}
}
private void stopMonitor()
throws Exception {
if (isServer) {
Log.d(TAG, "StopMonitor on server");
String[] stopmonitorCmd = {
"/system/bin/am",
"startservice",
"-a", "stop",
"com.google.android.experimental.svcmonitor/.SvcMonitor"
};
Process ps = new ProcessBuilder()
.command(stopmonitorCmd)
.redirectErrorStream(true)
.start();
} else {
Log.d(TAG, "No need to stop Svcmonitor on client");
}
}
public void stopService() {
if (mWorkerThread == null) {
Log.d(TAG, "no active thread");
return;
}
isShuttingDown = true;
try {
stopMonitor();
} catch (Exception e) {
Log.d(TAG, "Unable to stop SvcMonitor!", e);
}
if (Thread.currentThread() != mWorkerThread) {
mWorkerThread.interrupt();
Log.d(TAG, "Interrupting thread");
try {
mWorkerThread.join();
} catch (InterruptedException e) {
Log.d(TAG, "Unable to join thread!");
}
}
mWorkerThread = null;
stopSelf();
Log.d(TAG, "Service stopped");
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
public static class ListenerRunnable implements Runnable {
private final BTtraffic bttraffic;
private final boolean sendAck;
private Intent intent;
private final int maxbuffersize = 20 * 1024 * 1024;
public ListenerRunnable(BTtraffic bttraffic, Intent intent) {
this.bttraffic = bttraffic;
this.sendAck = intent.getBooleanExtra("ack", true);
this.intent = intent;
}
@Override
public void run() {
BluetoothServerSocket serverSocket;
try {
Log.d(TAG, "getting server socket");
serverSocket = BluetoothAdapter.getDefaultAdapter()
.listenUsingInsecureRfcommWithServiceRecord(
SERVICE_NAME, SERVICE_UUID);
} catch (IOException e) {
Log.d(TAG, "error creating server socket, stopping thread");
bttraffic.stopService();
return;
}
Log.d(TAG, "got server socket, starting accept loop");
BluetoothSocket socket = null;
try {
Log.d(TAG, "accepting");
socket = serverSocket.accept();
if (!Thread.interrupted()) {
Log.d(TAG, "accepted, listening");
doListening(socket.getInputStream(), socket.getOutputStream());
Log.d(TAG, "listen finished");
}
} catch (IOException e) {
Log.d(TAG, "error while accepting or listening", e);
} finally {
Log.d(TAG, "Linster interruped");
Log.d(TAG, "closing socket and stopping service");
safeClose(serverSocket);
safeClose(socket);
if (!bttraffic.isShuttingDown)
bttraffic.stopService();
}
}
private void doListening(InputStream inputStream, OutputStream outputStream)
throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(maxbuffersize);
while (!Thread.interrupted()) {
readBytesIntoBuffer(inputStream, byteBuffer, 4);
byteBuffer.flip();
int length = byteBuffer.getInt();
if (Thread.interrupted())
break;
readBytesIntoBuffer(inputStream, byteBuffer, length);
if (sendAck)
outputStream.write(0x55);
}
}
void readBytesIntoBuffer(InputStream inputStream, ByteBuffer byteBuffer, int numToRead)
throws IOException {
byteBuffer.clear();
while (true) {
int position = byteBuffer.position();
int remaining = numToRead - position;
if (remaining == 0) {
break;
}
int count = inputStream.read(byteBuffer.array(), position, remaining);
if (count < 0) {
throw new IOException("read the EOF");
}
byteBuffer.position(position + count);
}
}
}
public static class SenderRunnable implements Runnable {
private final BTtraffic bttraffic;
private final String remoteAddr;
private final int pkgsize, period;
private final int defaultpkgsize = 1024;
private final int defaultperiod = 5000;
private static ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
public SenderRunnable(BTtraffic bttraffic, String remoteAddr, Intent intent) {
this.bttraffic = bttraffic;
this.remoteAddr = remoteAddr;
this.pkgsize = intent.getIntExtra("size", defaultpkgsize);
this.period = intent.getIntExtra("period", defaultperiod);
}
@Override
public void run() {
BluetoothDevice device = null;
try {
device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr);
} catch (IllegalArgumentException e) {
Log.d(TAG, "Invalid BT MAC address!\n");
}
if (device == null) {
Log.d(TAG, "can't find matching device, stopping thread and service");
bttraffic.stopService();
return;
}
BluetoothSocket socket = null;
try {
Log.d(TAG, "connecting to device with MAC addr: " + remoteAddr);
socket = device.createInsecureRfcommSocketToServiceRecord(SERVICE_UUID);
socket.connect();
Log.d(TAG, "connected, starting to send");
doSending(socket.getOutputStream());
Log.d(TAG, "send stopped, stopping service");
} catch (Exception e) {
Log.d(TAG, "error while sending", e);
} finally {
Log.d(TAG, "finishing, closing thread and service");
safeClose(socket);
if (!bttraffic.isShuttingDown)
bttraffic.stopService();
}
}
private void doSending(OutputStream outputStream) throws IOException {
Log.w(TAG, "doSending");
try {
Random random = new Random(System.currentTimeMillis());
byte[] bytes = new byte[pkgsize];
random.nextBytes(bytes);
while (!Thread.interrupted()) {
writeBytes(outputStream, bytes.length);
outputStream.write(bytes, 0, bytes.length);
if (period < 0)
break;
if (period == 0)
continue;
SystemClock.sleep(period);
}
Log.d(TAG, "Sender interrupted");
} catch (IOException e) {
Log.d(TAG, "doSending got error", e);
}
}
private static void writeBytes(OutputStream outputStream, int value) throws IOException {
lengthBuffer.putInt(value);
lengthBuffer.flip();
outputStream.write(lengthBuffer.array(), lengthBuffer.position(), lengthBuffer.limit());
}
}
}

View File

@@ -0,0 +1,16 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := svcmonitor
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.experimental.svcmonitor" >
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18"
/>
<application
android:allowBackup="false"
android:label="@string/app_name" >
<service
android:name=".SvcMonitor"
android:enabled="true"
android:exported="true" >
</service>
</application>
</manifest>

View File

@@ -0,0 +1,27 @@
This Android service measures CPU usage of a program and an underlying system service it relies on.
An example of this would be an android app XYZ communicates to some other device via Bluetooth. The
SvcMonitor service can monitor the CPU usage of XYZ and com.android.bluetooth.
Usage:
To start the service:
$ adb shell am startservice -a start \
-e java XYZ -e hal com.android.bluetooth \
com.google.android.experimental.svcmonitor/.SvcMonitor
To stop the service:
$ adb shell am startservice -a stop \
com.google.android.experimental.svcmonitor/.SvcMonitor
To stop the service config:
$ adb shell am startservice -a change \
-e java NewName -e hal NewService \
com.google.android.experimental.svcmonitor/.SvcMonitor
To monitor the data:
$ adb logcat | grep XYZ
Options:
-e java NameOfProgram: any running processs name.
-e hal NameOfSysService: name of the system service the previous process relies on.
--ei period: period between each measurement (frequency). Unit: ms, Default:1000, Min: 100

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Bluetooth Test</string>
</resources>

View File

@@ -0,0 +1,209 @@
package com.google.android.experimental.svcmonitor;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.lang.Runnable;
import java.lang.Thread;
import java.util.Set;
public class SvcMonitor extends Service {
public static final String TAG = "svcmonitor";
String javaProc, halProc;
volatile Thread tMonitor;
int period;
public SvcMonitor() {};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
stopSelf();
return 0;
}
Log.d(TAG, "Starting SvcMonitor");
if ("stop".equals(intent.getAction())) {
stopService();
} else if ("start".equals(intent.getAction())) {
startMonitor(intent);
} else if ("change".equals(intent.getAction())) {
changeConfig(intent);
} else {
Log.d(TAG, "unknown action: + " + intent.getAction());
}
return 0;
}
private void changeConfig(Intent intent) {
if (tMonitor == null) {
Log.d(TAG, "Service not active. Start service first");
return;
}
stopThread();
startMonitor(intent);
}
private void startMonitor(Intent intent) {
if (tMonitor != null) {
Log.d(TAG, "thread already active");
return;
}
javaProc = intent.getStringExtra("java");
halProc = intent.getStringExtra("hal");
period = intent.getIntExtra("period", 1000);
if (javaProc == null || halProc == null || period < 100) {
Log.d(TAG, "Failed starting monitor, invalid arguments.");
stopSelf();
return;
}
Runnable monitor = new MonitorRunnable(this);
tMonitor = new Thread(monitor);
tMonitor.start();
}
private void stopService() {
stopThread();
stopSelf();
Log.d(TAG, "SvcMonitor stopped");
}
private void stopThread() {
if (tMonitor == null) {
Log.d(TAG, "no active thread");
return;
}
Log.d(TAG, "interrupting monitor thread");
tMonitor.interrupt();
try {
tMonitor.join();
} catch (InterruptedException e) {
Log.d(TAG, "Unable to finish monitor thread");
}
tMonitor = null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
public static class MonitorRunnable implements Runnable {
long java_time_old, hal_time_old, cpu_time_old = -1;
String javaPID, halPID;
SvcMonitor svcmonitor;
static String javaProcTAG;
int period;
public MonitorRunnable(SvcMonitor svcmonitor) {
this.svcmonitor = svcmonitor;
this.period = svcmonitor.period;
javaPID = getPIDof(svcmonitor.javaProc);
halPID = getPIDof(svcmonitor.halProc);
java_time_old = getPsTime(javaPID);
hal_time_old = getPsTime(halPID);
cpu_time_old = getPsTime("");
javaProcTAG = String.valueOf(svcmonitor.javaProc.toCharArray());
}
@Override
public void run() {
if (halPID.isEmpty() || javaPID.isEmpty()) {
Log.d(javaProcTAG, "No such process: " +
(halPID.isEmpty() ? svcmonitor.halProc : svcmonitor.javaProc));
return;
}
while (!Thread.interrupted()) {
calculateUsage();
SystemClock.sleep(period);
}
Log.d(TAG, "Stopping monitor thread");
}
private void calculateUsage() {
long java_time = getPsTime(javaPID);
long hal_time = getPsTime(halPID);
long cpu_time = getPsTime("");
if (cpu_time_old >= 0) {
float java_diff = (float) (java_time - java_time_old);
float hal_diff = (float) (hal_time - hal_time_old);
float cpu_diff = (float) (cpu_time - cpu_time_old);
Log.w(javaProcTAG, "\n----------------\n");
Log.w(javaProcTAG, "JAVA level CPU: "
+ (java_diff * 100.0 / cpu_diff) + "%\n");
Log.w(javaProcTAG, " HAL level CPU: "
+ (hal_diff * 100.0 / cpu_diff) + "%\n");
Log.w(javaProcTAG, " SYS level CPU: "
+ ((java_diff + hal_diff) * 100.0 / cpu_diff) + "%\n");
} else {
Log.w(TAG, "Waiting for status\n");
}
java_time_old = java_time;
hal_time_old = hal_time;
cpu_time_old = cpu_time;
}
private String getPIDof(String psName) {
String pid = "";
try {
String[] cmd = {"/system/bin/sh", "-c", "ps | grep " + psName};
Process ps = Runtime.getRuntime().exec(cmd);
BufferedReader in = new BufferedReader(
new InputStreamReader(ps.getInputStream()));
String temp = in.readLine();
if (temp == null || temp.isEmpty())
throw new IOException("No such process: " + psName);
pid = temp.split(" +")[1];
in.close();
} catch (IOException e) {
Log.d(javaProcTAG, "Error finding PID of process: " + psName + "\n", e);
}
return pid;
}
private long getPsTime(String pid) {
String psStat = getPsStat("/" + pid);
String[] statBreakDown = psStat.split(" +");
long psTime;
if (pid.isEmpty()) {
psTime = Long.parseLong(statBreakDown[1])
+ Long.parseLong(statBreakDown[2])
+ Long.parseLong(statBreakDown[3])
+ Long.parseLong(statBreakDown[4]);
} else {
psTime = Long.parseLong(statBreakDown[13])
+ Long.parseLong(statBreakDown[14]);
}
return psTime;
}
private String getPsStat(String psname) {
String stat = "";
try {
FileInputStream fs = new FileInputStream("/proc" + psname + "/stat");
BufferedReader br = new BufferedReader(new InputStreamReader(fs));
stat = br.readLine();
fs.close();
} catch (IOException e) {
Log.d(TAG, "Error retreiving stat. \n");
}
return stat;
}
}
}