Home | History | Annotate | Download | only in performance
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 #define LOG_TAG "HwbinderThroughputTest"
     17 
     18 #include <unistd.h>
     19 #include <sys/wait.h>
     20 
     21 #include <cstring>
     22 #include <iostream>
     23 #include <string>
     24 #include <tuple>
     25 #include <vector>
     26 
     27 #include <log/log.h>
     28 
     29 #include <android/hardware/tests/libhwbinder/1.0/IBenchmark.h>
     30 #include <hidl/HidlSupport.h>
     31 
     32 using namespace std;
     33 using namespace android;
     34 using namespace android::hardware;
     35 
     36 // Generated HIDL files
     37 using android::hardware::tests::libhwbinder::V1_0::IBenchmark;
     38 
     39 #define ASSERT_TRUE(cond) \
     40 do { \
     41     if (!(cond)) {\
     42        cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \
     43        exit(EXIT_FAILURE); \
     44     } \
     45 } while (0)
     46 
     47 class Pipe {
     48     int m_readFd;
     49     int m_writeFd;
     50     Pipe(int readFd, int writeFd)
     51             : m_readFd{readFd}, m_writeFd{writeFd} {
     52     }
     53     Pipe(const Pipe &) = delete;
     54     Pipe& operator=(const Pipe &) = delete;
     55     Pipe& operator=(const Pipe &&) = delete;
     56  public:
     57     Pipe(Pipe&& rval) noexcept {
     58         m_readFd = rval.m_readFd;
     59         m_writeFd = rval.m_writeFd;
     60         rval.m_readFd = 0;
     61         rval.m_writeFd = 0;
     62     }
     63     ~Pipe() {
     64         if (m_readFd)
     65             close(m_readFd);
     66         if (m_writeFd)
     67             close(m_writeFd);
     68     }
     69     void signal() {
     70         bool val = true;
     71         int error = write(m_writeFd, &val, sizeof(val));
     72         ASSERT_TRUE(error >= 0);
     73     }
     74     void wait() {
     75         bool val = false;
     76         int error = read(m_readFd, &val, sizeof(val));
     77         ASSERT_TRUE(error >= 0);
     78     }
     79     template<typename T> void send(const T& v) {
     80         int error = write(m_writeFd, &v, sizeof(T));
     81         ASSERT_TRUE(error >= 0);
     82     }
     83     template<typename T> void recv(T& v) {
     84         int error = read(m_readFd, &v, sizeof(T));
     85         ASSERT_TRUE(error >= 0);
     86     }
     87     static tuple<Pipe, Pipe> createPipePair() {
     88         int a[2];
     89         int b[2];
     90 
     91         int error1 = pipe(a);
     92         int error2 = pipe(b);
     93         ASSERT_TRUE(error1 >= 0);
     94         ASSERT_TRUE(error2 >= 0);
     95 
     96         return make_tuple(Pipe(a[0], b[1]), Pipe(b[0], a[1]));
     97     }
     98 };
     99 
    100 static const uint32_t num_buckets = 128;
    101 static const uint64_t max_time_bucket = 50ull * 1000000;
    102 static const uint64_t time_per_bucket = max_time_bucket / num_buckets;
    103 static constexpr float time_per_bucket_ms = time_per_bucket / 1.0E6;
    104 
    105 struct ProcResults {
    106     uint64_t m_best = max_time_bucket;
    107     uint64_t m_worst = 0;
    108     uint32_t m_buckets[num_buckets] = {0};
    109     uint64_t m_transactions = 0;
    110     uint64_t m_total_time = 0;
    111 
    112     // Add a new latency data point and update the aggregation info
    113     // e.g. best/worst/total_time.
    114     void add_time(uint64_t time) {
    115         m_buckets[min(time, max_time_bucket - 1) / time_per_bucket] += 1;
    116         m_best = min(time, m_best);
    117         m_worst = max(time, m_worst);
    118         m_transactions += 1;
    119         m_total_time += time;
    120     }
    121     // Combine two sets of latency data points and update the aggregation info.
    122     static ProcResults combine(const ProcResults& a, const ProcResults& b) {
    123         ProcResults ret;
    124         for (uint32_t i = 0; i < num_buckets; i++) {
    125             ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i];
    126         }
    127         ret.m_worst = max(a.m_worst, b.m_worst);
    128         ret.m_best = min(a.m_best, b.m_best);
    129         ret.m_transactions = a.m_transactions + b.m_transactions;
    130         ret.m_total_time = a.m_total_time + b.m_total_time;
    131         return ret;
    132     }
    133     // Calculate and report the final aggregated results.
    134     void dump() {
    135         double best = (double) m_best / 1.0E6;
    136         double worst = (double) m_worst / 1.0E6;
    137         double average = (double) m_total_time / m_transactions / 1.0E6;
    138         cout << "average:"
    139              << average
    140              << "ms worst:"
    141              << worst
    142              << "ms best:"
    143              << best
    144              << "ms"
    145              << endl;
    146 
    147         uint64_t cur_total = 0;
    148         for (uint32_t i = 0; i < num_buckets; i++) {
    149             float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms;
    150             if ((cur_total < 0.5f * m_transactions)
    151                 && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) {
    152                 cout << "50%: " << cur_time << " ";
    153             }
    154             if ((cur_total < 0.9f * m_transactions)
    155                 && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) {
    156                 cout << "90%: " << cur_time << " ";
    157             }
    158             if ((cur_total < 0.95f * m_transactions)
    159                 && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) {
    160                 cout << "95%: " << cur_time << " ";
    161             }
    162             if ((cur_total < 0.99f * m_transactions)
    163                 && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) {
    164                 cout << "99%: " << cur_time << " ";
    165             }
    166             cur_total += m_buckets[i];
    167         }
    168         cout << endl;
    169 
    170     }
    171 };
    172 
    173 string generateServiceName(int num) {
    174     string serviceName = "hwbinderService" + to_string(num);
    175     return serviceName;
    176 }
    177 
    178 void service_fx(const string &serviceName, Pipe p) {
    179     // Start service.
    180     sp<IBenchmark> server = IBenchmark::getService(serviceName, true);
    181     ALOGD("Registering %s", serviceName.c_str());
    182     status_t status = server->registerAsService(serviceName);
    183     if (status != ::android::OK) {
    184         ALOGE("Failed to register service %s", serviceName.c_str());
    185         exit(EXIT_FAILURE);
    186     }
    187 
    188     ALOGD("Starting %s", serviceName.c_str());
    189 
    190     // Signal service started to master and wait to exit.
    191     p.signal();
    192     p.wait();
    193     exit(EXIT_SUCCESS);
    194 }
    195 
    196 void worker_fx(
    197         int num,
    198         int iterations,
    199         int service_count,
    200         bool get_stub,
    201         Pipe p) {
    202     srand(num);
    203     p.signal();
    204     p.wait();
    205 
    206     // Get references to test services.
    207     vector<sp<IBenchmark>> workers;
    208 
    209     for (int i = 0; i < service_count; i++) {
    210         sp<IBenchmark> service = IBenchmark::getService(
    211                 generateServiceName(i), get_stub);
    212         ASSERT_TRUE(service != NULL);
    213         if (get_stub) {
    214             ASSERT_TRUE(!service->isRemote());
    215         } else {
    216             ASSERT_TRUE(service->isRemote());
    217         }
    218         workers.push_back(service);
    219     }
    220 
    221     ProcResults results;
    222     chrono::time_point<chrono::high_resolution_clock> start, end;
    223     // Prepare data to IPC
    224     hidl_vec<uint8_t> data_vec;
    225     data_vec.resize(16);
    226     for (size_t i = 0; i < data_vec.size(); i++) {
    227         data_vec[i] = i;
    228     }
    229     // Run the benchmark.
    230     for (int i = 0; i < iterations; i++) {
    231         // Randomly pick a service.
    232         int target = rand() % service_count;
    233 
    234         start = chrono::high_resolution_clock::now();
    235         Return<void> ret = workers[target]->sendVec(data_vec, [&](const auto &) {});
    236         if (!ret.isOk()) {
    237             cout << "thread " << num << " failed status: "
    238                 << ret.description() << endl;
    239             exit(EXIT_FAILURE);
    240         }
    241         end = chrono::high_resolution_clock::now();
    242 
    243         uint64_t cur_time = uint64_t(
    244                chrono::duration_cast<chrono::nanoseconds>(end - start).count());
    245         results.add_time(cur_time);
    246     }
    247     // Signal completion to master and wait.
    248     p.signal();
    249     p.wait();
    250 
    251     // Send results to master and wait for go to exit.
    252     p.send(results);
    253     p.wait();
    254 
    255     exit (EXIT_SUCCESS);
    256 }
    257 
    258 Pipe make_service(string service_name) {
    259     auto pipe_pair = Pipe::createPipePair();
    260     pid_t pid = fork();
    261     if (pid) {
    262         /* parent */
    263         return move(get<0>(pipe_pair));
    264     } else {
    265         /* child */
    266         service_fx(service_name, move(get<1>(pipe_pair)));
    267         /* never get here */
    268         return move(get<0>(pipe_pair));
    269     }
    270 }
    271 
    272 Pipe make_worker(int num, int iterations, int service_count, bool get_stub) {
    273     auto pipe_pair = Pipe::createPipePair();
    274     pid_t pid = fork();
    275     if (pid) {
    276         /* parent */
    277         return move(get<0>(pipe_pair));
    278     } else {
    279         /* child */
    280         worker_fx(num, iterations, service_count, get_stub,
    281                   move(get<1>(pipe_pair)));
    282         /* never get here */
    283         return move(get<0>(pipe_pair));
    284     }
    285 }
    286 
    287 void wait_all(vector<Pipe>& v) {
    288     for (size_t i = 0; i < v.size(); i++) {
    289         v[i].wait();
    290     }
    291 }
    292 
    293 void signal_all(vector<Pipe>& v) {
    294     for (size_t i = 0; i < v.size(); i++) {
    295         v[i].signal();
    296     }
    297 }
    298 
    299 int main(int argc, char *argv[]) {
    300     setenv("TREBLE_TESTING_OVERRIDE", "true", true);
    301 
    302     enum HwBinderMode {
    303         kBinderize = 0,
    304         kPassthrough = 1,
    305     };
    306     HwBinderMode mode = HwBinderMode::kBinderize;
    307 
    308     // Num of workers.
    309     int workers = 2;
    310     // Num of services.
    311     int services = -1;
    312     int iterations = 10000;
    313 
    314     vector<Pipe> worker_pipes;
    315     vector<Pipe> service_pipes;
    316 
    317     // Parse arguments.
    318     for (int i = 1; i < argc; i++) {
    319         if (string(argv[i]) == "-m") {
    320             if (!strcmp(argv[i + 1], "PASSTHROUGH")) {
    321                 mode = HwBinderMode::kPassthrough;
    322             }
    323             i++;
    324             continue;
    325         }
    326         if (string(argv[i]) == "-w") {
    327             workers = atoi(argv[i + 1]);
    328             i++;
    329             continue;
    330         }
    331         if (string(argv[i]) == "-i") {
    332             iterations = atoi(argv[i + 1]);
    333             i++;
    334             continue;
    335         }
    336         if (string(argv[i]) == "-s") {
    337             services = atoi(argv[i + 1]);
    338             i++;
    339             continue;
    340         }
    341     }
    342     // If service number is not provided, set it the same as the worker number.
    343     if (services == -1) {
    344         services = workers;
    345     }
    346     if (mode == HwBinderMode::kBinderize) {
    347         // Create services.
    348         vector<pid_t> pIds;
    349         for (int i = 0; i < services; i++) {
    350             string serviceName = generateServiceName(i);
    351             cout << "creating service: " << serviceName << endl;
    352             service_pipes.push_back(make_service(serviceName));
    353         }
    354         // Wait until all services are up.
    355         wait_all(service_pipes);
    356     }
    357 
    358     // Create workers (test clients).
    359     bool get_stub = mode == HwBinderMode::kBinderize ? false : true;
    360     for (int i = 0; i < workers; i++) {
    361         worker_pipes.push_back(make_worker(i, iterations, services, get_stub));
    362     }
    363     // Wait untill all workers are ready.
    364     wait_all(worker_pipes);
    365 
    366     // Run the workers and wait for completion.
    367     chrono::time_point<chrono::high_resolution_clock> start, end;
    368     cout << "waiting for workers to complete" << endl;
    369     start = chrono::high_resolution_clock::now();
    370     signal_all(worker_pipes);
    371     wait_all(worker_pipes);
    372     end = chrono::high_resolution_clock::now();
    373 
    374     // Calculate overall throughput.
    375     double iterations_per_sec = double(iterations * workers)
    376         / (chrono::duration_cast < chrono::nanoseconds
    377             > (end - start).count() / 1.0E9);
    378     cout << "iterations per sec: " << iterations_per_sec << endl;
    379 
    380     // Collect all results from the workers.
    381     cout << "collecting results" << endl;
    382     signal_all(worker_pipes);
    383     ProcResults tot_results;
    384     for (int i = 0; i < workers; i++) {
    385         ProcResults tmp_results;
    386         worker_pipes[i].recv(tmp_results);
    387         tot_results = ProcResults::combine(tot_results, tmp_results);
    388     }
    389     tot_results.dump();
    390 
    391     if (mode == HwBinderMode::kBinderize) {
    392         // Kill all the services.
    393         cout << "killing services" << endl;
    394         signal_all(service_pipes);
    395         for (int i = 0; i < services; i++) {
    396             int status;
    397             wait(&status);
    398             if (status != 0) {
    399                 cout << "nonzero child status" << status << endl;
    400             }
    401         }
    402     }
    403     // Kill all the workers.
    404     cout << "killing workers" << endl;
    405     signal_all(worker_pipes);
    406     for (int i = 0; i < workers; i++) {
    407         int status;
    408         wait(&status);
    409         if (status != 0) {
    410             cout << "nonzero child status" << status << endl;
    411         }
    412     }
    413     return 0;
    414 }
    415