OSDN Git Service

Merge "Binder add ints benchmark"
authorLouis Huemiller <lhuemill@google.com>
Fri, 22 Oct 2010 17:35:43 +0000 (10:35 -0700)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 22 Oct 2010 17:35:43 +0000 (10:35 -0700)
tests/binder/Android.mk [new file with mode: 0644]
tests/binder/benchmarks/Android.mk [new file with mode: 0644]
tests/binder/benchmarks/binderAddInts.cpp [new file with mode: 0644]
tests/include/testUtil.h
tests/lib/testUtil/testUtil.c

diff --git a/tests/binder/Android.mk b/tests/binder/Android.mk
new file mode 100644 (file)
index 0000000..4343259
--- /dev/null
@@ -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 (file)
index 0000000..53d35a0
--- /dev/null
@@ -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 (file)
index 0000000..88937dc
--- /dev/null
@@ -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));
+}
index d1d0954..8199c03 100644 (file)
 #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
index 1c4572d..b017014 100644 (file)
@@ -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);
 }