summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorLouis Huemiller <lhuemill@google.com>2010-10-22 10:35:43 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2010-10-22 10:35:43 -0700
commit0bdc4387cad6355da33178c597f7aa4d931c09e1 (patch)
tree969717782768c557eefc6d6446520d66f931565e /tests
parent133a37ede3e955093c6004d94496e2cdc2cc1264 (diff)
parentd2447fd2505466a8c30cdca247325f79ba95be34 (diff)
downloadextras-0bdc4387cad6355da33178c597f7aa4d931c09e1.tar.gz
Merge "Binder add ints benchmark"
Diffstat (limited to 'tests')
-rw-r--r--tests/binder/Android.mk17
-rw-r--r--tests/binder/benchmarks/Android.mk48
-rw-r--r--tests/binder/benchmarks/binderAddInts.cpp458
-rw-r--r--tests/include/testUtil.h15
-rw-r--r--tests/lib/testUtil/testUtil.c94
5 files changed, 599 insertions, 33 deletions
diff --git a/tests/binder/Android.mk b/tests/binder/Android.mk
new file mode 100644
index 00000000..4343259b
--- /dev/null
+++ b/tests/binder/Android.mk
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+#
+
+include $(call all-subdir-makefiles)
diff --git a/tests/binder/benchmarks/Android.mk b/tests/binder/benchmarks/Android.mk
new file mode 100644
index 00000000..53d35a07
--- /dev/null
+++ b/tests/binder/benchmarks/Android.mk
@@ -0,0 +1,48 @@
+#
+# Copyright (C) 2010 The Android Open Source Project
+#
+# 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.
+#
+
+ifneq ($(TARGET_SIMULATOR),true) # GTest needs STLport, which the simulator
+ # doesn't support
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := eng tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativebenchmark
+
+LOCAL_STATIC_LIBRARIES += \
+ libgtest \
+ libgtest_main \
+ libtestUtil
+
+LOCAL_SHARED_LIBRARIES += \
+ libutils \
+ libstlport \
+ libbinder
+
+LOCAL_C_INCLUDES += \
+ bionic \
+ bionic/libstdc++/include \
+ external/stlport/stlport \
+ external/gtest/include \
+ system/extras/tests/include \
+ frameworks/base/include
+
+LOCAL_MODULE := binderAddInts
+LOCAL_SRC_FILES := binderAddInts.cpp
+include $(BUILD_EXECUTABLE)
+
+endif
diff --git a/tests/binder/benchmarks/binderAddInts.cpp b/tests/binder/benchmarks/binderAddInts.cpp
new file mode 100644
index 00000000..88937dc5
--- /dev/null
+++ b/tests/binder/benchmarks/binderAddInts.cpp
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Binder add integers benchmark
+ *
+ * Measures the rate at which a short binder IPC operation can be
+ * performed. The operation consists of the client sending a parcel
+ * that contains two integers. For each parcel that the server
+ * receives, it adds the two integers and sends the sum back to
+ * the client.
+ *
+ * This benchmark supports the following command-line options:
+ *
+ * -c cpu - bind client to specified cpu (default: unbound)
+ * -s cpu - bind server to specified cpu (default: unbound)
+ * -n num - perform IPC operation num times (default: 1000)
+ * -d time - delay specified amount of seconds after each
+ * IPC operation. (default 1e-3)
+ */
+
+#include <cerrno>
+#include <grp.h>
+#include <iostream>
+#include <libgen.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+#include <testUtil.h>
+
+using namespace android;
+using namespace std;
+
+const int unbound = -1; // Indicator for a thread not bound to a specific CPU
+
+String16 serviceName("test.binderAddInts");
+
+struct options {
+ int serverCPU;
+ int clientCPU;
+ unsigned int iterations;
+ float iterDelay; // End of iteration delay in seconds
+} options = { // Set defaults
+ unbound, // Server CPU
+ unbound, // Client CPU
+ 1000, // Iterations
+ 1e-3, // End of iteration delay
+};
+
+class AddIntsService : public BBinder
+{
+ public:
+ AddIntsService(int cpu = unbound);
+ virtual ~AddIntsService() {};
+
+ enum command {
+ ADD_INTS = 0x120,
+ };
+
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data, Parcel* reply,
+ uint32_t flags = 0);
+
+ private:
+ int cpu_;
+};
+
+// Workaround for missing sched_setaffinity(2) and getcpu(2)
+#define CPU_SETSIZE 1024
+struct getcpu_cache;
+typedef struct { uint64_t bits[CPU_SETSIZE / 64]; } cpu_set_t;
+static int sched_getaffinity(pid_t pid, unsigned int cpusetsize,
+ cpu_set_t *set);
+static int sched_setaffinity(pid_t pid, unsigned int cpusetsize,
+ cpu_set_t *mask);
+static int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache);
+static int CPU_ISSET(int cpu, const cpu_set_t *set);
+static void CPU_SET(int cpu, cpu_set_t *set);
+static void CPU_ZERO(cpu_set_t *set);
+
+// File scope function prototypes
+static void server(void);
+static void client(void);
+static void bindCPU(unsigned int cpu);
+static ostream &operator<<(ostream &stream, const String16& str);
+static ostream &operator<<(ostream &stream, const cpu_set_t& set);
+
+int main(int argc, char *argv[])
+{
+ int rv;
+
+ // Determine CPUs available for use.
+ // This testcase limits its self to using CPUs that were
+ // available at the start of the benchmark.
+ cpu_set_t availCPUs;
+ if ((rv = sched_getaffinity(0, sizeof(availCPUs), &availCPUs)) != 0) {
+ cerr << "sched_getaffinity failure, rv: " << rv
+ << " errno: " << errno << endl;
+ exit(1);
+ }
+
+ // Parse command line arguments
+ int opt;
+ while ((opt = getopt(argc, argv, "s:c:n:d:?")) != -1) {
+ char *chptr; // character pointer for command-line parsing
+
+ switch (opt) {
+ case 'c': // client CPU
+ case 's': { // server CPU
+ // Parse the CPU number
+ int cpu = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ cerr << "Invalid cpu specified for -" << (char) opt
+ << " option of: " << optarg << endl;
+ exit(2);
+ }
+
+ // Is the CPU available?
+ if (!CPU_ISSET(cpu, &availCPUs)) {
+ cerr << "CPU " << optarg << " not currently available" << endl;
+ cerr << " Available CPUs: " << availCPUs << endl;
+ exit(3);
+ }
+
+ // Record the choice
+ *((opt == 'c') ? &options.clientCPU : &options.serverCPU) = cpu;
+ break;
+ }
+
+ case 'n': // iterations
+ options.iterations = strtoul(optarg, &chptr, 10);
+ if (*chptr != '\0') {
+ cerr << "Invalid iterations specified of: " << optarg << endl;
+ exit(4);
+ }
+ if (options.iterations < 1) {
+ cerr << "Less than 1 iteration specified by: "
+ << optarg << endl;
+ exit(5);
+ }
+ break;
+
+ case 'd': // Delay between each iteration
+ options.iterDelay = strtod(optarg, &chptr);
+ if ((*chptr != '\0') || (options.iterDelay < 0.0)) {
+ cerr << "Invalid delay specified of: " << optarg << endl;
+ exit(6);
+ }
+ break;
+
+ case '?':
+ default:
+ cerr << basename(argv[0]) << " [options]" << endl;
+ cerr << " options:" << endl;
+ cerr << " -s cpu - server CPU number" << endl;
+ cerr << " -c cpu - client CPU number" << endl;
+ cerr << " -n num - iterations" << endl;
+ cerr << " -d time - delay after operation in seconds" << endl;
+ exit(((optopt == 0) || (optopt == '?')) ? 0 : 7);
+ }
+ }
+
+ // Display selected options
+ cout << "serverCPU: ";
+ if (options.serverCPU == unbound) {
+ cout << " unbound";
+ } else {
+ cout << options.serverCPU;
+ }
+ cout << endl;
+ cout << "clientCPU: ";
+ if (options.clientCPU == unbound) {
+ cout << " unbound";
+ } else {
+ cout << options.clientCPU;
+ }
+ cout << endl;
+ cout << "iterations: " << options.iterations << endl;
+ cout << "iterDelay: " << options.iterDelay << endl;
+
+ // Fork client, use this process as server
+ fflush(stdout);
+ switch (pid_t pid = fork()) {
+ case 0: // Child
+ client();
+ return 0;
+
+ default: // Parent
+ server();
+
+ // Wait for all children to end
+ do {
+ int stat;
+ rv = wait(&stat);
+ if ((rv == -1) && (errno == ECHILD)) { break; }
+ if (rv == -1) {
+ cerr << "wait failed, rv: " << rv << " errno: "
+ << errno << endl;
+ perror(NULL);
+ exit(8);
+ }
+ } while (1);
+ return 0;
+
+ case -1: // Error
+ exit(9);
+ }
+
+ return 0;
+}
+
+static void server(void)
+{
+ int rv;
+
+ // Add the service
+ sp<ProcessState> proc(ProcessState::self());
+ sp<IServiceManager> sm = defaultServiceManager();
+ if ((rv = sm->addService(serviceName,
+ new AddIntsService(options.serverCPU))) != 0) {
+ cerr << "addService " << serviceName << " failed, rv: " << rv
+ << " errno: " << errno << endl;
+ }
+
+ // Start threads to handle server work
+ proc->startThreadPool();
+}
+
+static void client(void)
+{
+ int rv;
+ sp<IServiceManager> sm = defaultServiceManager();
+ double min = FLT_MAX, max = 0.0, total = 0.0; // Time in seconds for all
+ // the IPC calls.
+
+ // If needed bind to client CPU
+ if (options.clientCPU != unbound) { bindCPU(options.clientCPU); }
+
+ // Attach to service
+ sp<IBinder> binder;
+ do {
+ binder = sm->getService(serviceName);
+ if (binder != 0) break;
+ cout << serviceName << " not published, waiting..." << endl;
+ usleep(500000); // 0.5 s
+ } while(true);
+
+ // Perform the IPC operations
+ for (unsigned int iter = 0; iter < options.iterations; iter++) {
+ Parcel send, reply;
+
+ // Create parcel to be sent. Will use the iteration cound
+ // and the iteration count + 3 as the two integer values
+ // to be sent.
+ int val1 = iter;
+ int val2 = iter + 3;
+ int expected = val1 + val2; // Expect to get the sum back
+ send.writeInt32(val1);
+ send.writeInt32(val2);
+
+ // Send the parcel, while timing how long it takes for
+ // the answer to return.
+ struct timespec start;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+ if ((rv = binder->transact(AddIntsService::ADD_INTS,
+ send, &reply)) != 0) {
+ cerr << "binder->transact failed, rv: " << rv
+ << " errno: " << errno << endl;
+ exit(10);
+ }
+ struct timespec current;
+ clock_gettime(CLOCK_MONOTONIC, &current);
+
+ // Calculate how long this operation took and update the stats
+ struct timespec deltaTimespec = tsDelta(&start, &current);
+ double delta = ts2double(&deltaTimespec);
+ min = (delta < min) ? delta : min;
+ max = (delta > max) ? delta : max;
+ total += delta;
+ int result = reply.readInt32();
+ if (result != (int) (iter + iter + 3)) {
+ cerr << "Unexpected result for iteration " << iter << endl;
+ cerr << " result: " << result << endl;
+ cerr << "expected: " << expected << endl;
+ }
+
+ if (options.iterDelay > 0.0) { delay(options.iterDelay); }
+ }
+
+ // Display the results
+ cout << "Time per iteration min: " << min
+ << " avg: " << (total / options.iterations)
+ << " max: " << max
+ << endl;
+}
+
+AddIntsService::AddIntsService(int cpu): cpu_(cpu) {
+ if (cpu != unbound) { bindCPU(cpu); }
+};
+
+// Server function that handles parcels received from the client
+status_t AddIntsService::onTransact(uint32_t code, const Parcel &data,
+ Parcel* reply, uint32_t flags) {
+ int val1, val2;
+ status_t rv(0);
+ int cpu;
+
+ // If server bound to a particular CPU, check that
+ // were executing on that CPU.
+ if (cpu_ != unbound) {
+ getcpu((unsigned int *) &cpu, NULL, NULL);
+ if (cpu != cpu_) {
+ cerr << "server onTransact on CPU " << cpu << " expected CPU "
+ << cpu_ << endl;
+ exit(20);
+ }
+ }
+
+ // Perform the requested operation
+ switch (code) {
+ case ADD_INTS:
+ val1 = data.readInt32();
+ val2 = data.readInt32();
+ reply->writeInt32(val1 + val2);
+ break;
+
+ default:
+ cerr << "server onTransact unknown code, code: " << code << endl;
+ exit(21);
+ }
+
+ return rv;
+}
+
+static void bindCPU(unsigned int cpu)
+{
+ int rv;
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu, &cpuset);
+ rv = sched_setaffinity(0, sizeof(cpuset), &cpuset);
+
+ if (rv != 0) {
+ cerr << "bindCPU failed, rv: " << rv << " errno: " << errno << endl;
+ perror(NULL);
+ exit(30);
+ }
+}
+
+static ostream &operator<<(ostream &stream, const String16& str)
+{
+ for (unsigned int n1 = 0; n1 < str.size(); n1++) {
+ if ((str[n1] > 0x20) && (str[n1] < 0x80)) {
+ stream << (char) str[n1];
+ } else {
+ stream << '~';
+ }
+ }
+
+ return stream;
+}
+
+static ostream &operator<<(ostream &stream, const cpu_set_t& set)
+{
+ for (unsigned int n1 = 0; n1 < CPU_SETSIZE; n1++) {
+ if (CPU_ISSET(n1, &set)) {
+ if (n1 != 0) { stream << ' '; }
+ stream << n1;
+ }
+ }
+
+ return stream;
+}
+
+// ======== Local implementation of system calls with missing bionic call stubs
+static int sched_getaffinity(pid_t pid, unsigned int cpusetsize,
+ cpu_set_t *set)
+{
+ int rv;
+
+ rv = syscall(__NR_sched_getaffinity, pid, cpusetsize, set);
+ if (rv < 0) { return rv; }
+
+ // Kernel implementation of sched_getaffinity() returns the number
+ // of bytes in the set that it set. Set the rest of our set bits
+ // to 0.
+ memset(((char *) set) + rv, 0x00, sizeof(cpu_set_t) - rv);
+
+ return 0;
+}
+
+static int sched_setaffinity(pid_t pid, unsigned int cpusetsize,
+ cpu_set_t *mask)
+{
+ int rv;
+
+ rv = syscall(__NR_sched_setaffinity, pid, cpusetsize, mask);
+
+ return rv;
+}
+
+static int getcpu(unsigned *cpu, unsigned *node, struct getcpu_cache *tcache)
+{
+ int rv;
+
+ rv = syscall(345, cpu, node, tcache);
+
+ return rv;
+}
+
+static int CPU_ISSET(int cpu, const cpu_set_t *set)
+{
+ if (cpu < 0) { return 0; }
+ if ((unsigned) cpu >= (sizeof(cpu_set_t) * CHAR_BIT)) { return 0; }
+
+ if ((*((uint64_t *)set + (cpu / 64))) & (1ULL << (cpu % 64))) {
+ return true;
+ }
+
+ return false;
+}
+
+static void CPU_SET(int cpu, cpu_set_t *set)
+{
+ if (cpu < 0) { return; }
+ if ((unsigned) cpu > (sizeof(cpu_set_t) * CHAR_BIT)) { return; }
+
+ *((uint64_t *)set + (cpu / 64)) |= 1ULL << (cpu % 64);
+}
+
+static void CPU_ZERO(cpu_set_t *set)
+{
+ memset(set, 0x00, sizeof(cpu_set_t));
+}
diff --git a/tests/include/testUtil.h b/tests/include/testUtil.h
index d1d09549..8199c036 100644
--- a/tests/include/testUtil.h
+++ b/tests/include/testUtil.h
@@ -22,15 +22,18 @@
#include <stdio.h>
#include <sys/time.h>
-#ifdef __cplusplus
-extern "C" {
-#endif
+__BEGIN_DECLS
// Time Utilities
-double tv2double(const struct timeval *val);
struct timespec double2ts(double amt);
+struct timeval double2tv(double amt);
+double ts2double(const struct timespec *val);
+double tv2double(const struct timeval *val);
+struct timespec tsDelta(const struct timespec *first,
+ const struct timespec *second);
struct timeval tvDelta(const struct timeval *first,
const struct timeval *second);
+
void delay(float amt);
// Pseudo Random Utilities
@@ -47,8 +50,6 @@ void testPrint(FILE *stream, const char *fmt, ...);
testPrint(stderr, __VA_ARGS__); \
} while (0)
-#ifdef __cplusplus
-}
-#endif
+__END_DECLS
#endif
diff --git a/tests/lib/testUtil/testUtil.c b/tests/lib/testUtil/testUtil.c
index 1c4572d5..b0170148 100644
--- a/tests/lib/testUtil/testUtil.c
+++ b/tests/lib/testUtil/testUtil.c
@@ -33,13 +33,25 @@ typedef unsigned int bool_t;
#define false (!true)
#define MAXSTR 200
+
static const char *logCatTag;
+static const unsigned int uSecsPerSec = 1000000;
+static const unsigned int nSecsPerSec = 1000000000;
+
+// struct timespec to double
+double ts2double(const struct timespec *val)
+{
+ double rv;
+
+ rv = val->tv_sec;
+ rv += (double) val->tv_nsec / nSecsPerSec;
+
+ return rv;
+}
// struct timeval to double
-double
-tv2double(const struct timeval *val)
+double tv2double(const struct timeval *val)
{
- const unsigned int uSecsPerSec = 1000000;
double rv;
rv = val->tv_sec;
@@ -49,10 +61,8 @@ tv2double(const struct timeval *val)
}
// double to struct timespec
-struct timespec
-double2ts(double amt)
+struct timespec double2ts(double amt)
{
- const unsigned int nSecsPerSec = 1000000000;
struct timespec rv;
rv.tv_sec = floor(amt);
@@ -66,14 +76,51 @@ double2ts(double amt)
return rv;
}
+// double to struct timeval
+struct timeval double2tv(double amt)
+{
+ struct timeval rv;
+
+ rv.tv_sec = floor(amt);
+ rv.tv_usec = (amt - rv.tv_sec) * uSecsPerSec;
+ // TODO: Handle cases where amt is negative
+ while ((unsigned) rv.tv_usec >= uSecsPerSec) {
+ rv.tv_usec -= uSecsPerSec;
+ rv.tv_sec++;
+ }
+
+ return rv;
+}
+
+// Delta (difference) between two struct timespec.
+// It is expected that the time given by the structure pointed to by
+// second, is later than the time pointed to by first.
+struct timespec tsDelta(const struct timespec *first,
+ const struct timespec *second)
+{
+ struct timespec rv;
+
+ assert(first != NULL);
+ assert(second != NULL);
+ assert(first->tv_nsec >= 0 && first->tv_nsec < nSecsPerSec);
+ assert(second->tv_nsec >= 0 && second->tv_nsec < nSecsPerSec);
+ rv.tv_sec = second->tv_sec - first->tv_sec;
+ if (second->tv_nsec >= first->tv_nsec) {
+ rv.tv_nsec = second->tv_nsec - first->tv_nsec;
+ } else {
+ rv.tv_nsec = (second->tv_nsec + nSecsPerSec) - first->tv_nsec;
+ rv.tv_sec--;
+ }
+
+ return rv;
+}
+
// Delta (difference) between two struct timeval.
// It is expected that the time given by the structure pointed to by
// second, is later than the time pointed to by first.
-struct timeval
-tvDelta(const struct timeval *first,
- const struct timeval *second)
+struct timeval tvDelta(const struct timeval *first,
+ const struct timeval *second)
{
- const unsigned int uSecsPerSec = 1000000;
struct timeval rv;
assert(first != NULL);
@@ -91,8 +138,7 @@ tvDelta(const struct timeval *first,
return rv;
}
-void
-testPrint(FILE *stream, const char *fmt, ...)
+void testPrint(FILE *stream, const char *fmt, ...)
{
char line[MAXSTR];
va_list args;
@@ -109,15 +155,13 @@ testPrint(FILE *stream, const char *fmt, ...)
}
// Set tag used while logging to the logcat error interface
-void
-testSetLogCatTag(const char *tag)
+void testSetLogCatTag(const char *tag)
{
logCatTag = tag;
}
// Obtain pointer to current log to logcat error interface tag
-const char *
-testGetLogCatTag(void)
+const char * testGetLogCatTag(void)
{
return logCatTag;
}
@@ -130,8 +174,7 @@ testGetLogCatTag(void)
* Precondition: srand48() called to set the seed of
* the pseudo random number generator.
*/
-int
-testRandBool(void)
+int testRandBool(void)
{
/* Use the most significant bit from lrand48(), because the
* less significant bits are less random across different seeds
@@ -145,25 +188,24 @@ testRandBool(void)
// The amt variable is of type float and thus non-integer amounts
// of time can be specified. This function automatically handles cases
// where nanosleep(2) returns early due to reception of a signal.
-void
-delay(float amt)
+void delay(float amt)
{
- struct timeval start, current, delta;
+ struct timespec start, current, delta;
struct timespec remaining;
// Get the time at which we started
- gettimeofday(&start, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &start);
do {
// Get current time
- gettimeofday(&current, NULL);
+ clock_gettime(CLOCK_MONOTONIC, &current);
// How much time is left
- delta = tvDelta(&start, &current);
- if (tv2double(&delta) > amt) { break; }
+ delta = tsDelta(&start, &current);
+ if (ts2double(&delta) > amt) { break; }
// Request to sleep for the remaining time
- remaining = double2ts(amt - tv2double(&delta));
+ remaining = double2ts(amt - ts2double(&delta));
(void) nanosleep(&remaining, NULL);
} while (true);
}