/* * Copyright (C) 2016 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 <android/hardware/tests/libhwbinder/1.0/IScheduleTest.h> #include <hidl/LegacySupport.h> #include <pthread.h> #include <sys/wait.h> #include <fstream> #include <iomanip> #include <iostream> #include <string> #include "PerfTest.h" #ifdef ASSERT #undef ASSERT #endif #define ASSERT(cond) \ do { \ if (!(cond)) { \ cerr << __func__ << ":" << __LINE__ << " condition:" << #cond << " failed\n" << endl; \ exit(EXIT_FAILURE); \ } \ } while (0) #define REQUIRE(stat) \ do { \ int cond = (stat); \ ASSERT(cond); \ } while (0) using android::hardware::registerPassthroughServiceImplementation; using android::hardware::tests::libhwbinder::V1_0::IScheduleTest; using android::sp; using std::cerr; using std::cout; using std::endl; using std::fstream; using std::left; using std::ios; using std::get; using std::move; using std::to_string; using std::setprecision; using std::setw; using std::string; using std::vector; static vector<sp<IScheduleTest> > services; // default arguments static bool dump_raw_data = false; static int no_pair = 1; static int iterations = 100; static int verbose = 0; static int is_tracing; static bool pass_through = false; // the deadline latency that we are interested in static uint64_t deadline_us = 2500; static bool traceIsOn() { fstream file; file.open(TRACE_PATH "/tracing_on", ios::in); char on; file >> on; file.close(); return on == '1'; } static int threadGetPri() { sched_param param; int policy; REQUIRE(!pthread_getschedparam(pthread_self(), &policy, ¶m)); return param.sched_priority; } static void threadDumpPri(const char* prefix) { sched_param param; int policy; if (!verbose) { return; } cout << "--------------------------------------------------" << endl; cout << setw(12) << left << prefix << " pid: " << getpid() << " tid: " << gettid() << " cpu: " << sched_getcpu() << endl; REQUIRE(!pthread_getschedparam(pthread_self(), &policy, ¶m)); string s = (policy == SCHED_OTHER) ? "SCHED_OTHER" : (policy == SCHED_FIFO) ? "SCHED_FIFO" : (policy == SCHED_RR) ? "SCHED_RR" : "???"; cout << setw(12) << left << s << param.sched_priority << endl; return; } struct ThreadArg { void* result; ///< pointer to PResults int target; ///< the terget service number }; static void* threadStart(void* p) { ThreadArg* priv = (ThreadArg*)p; int target = priv->target; PResults* presults = (PResults*)priv->result; Tick sta, end; threadDumpPri("fifo-caller"); uint32_t call_sta = (threadGetPri() << 16) | sched_getcpu(); sp<IScheduleTest> service = services[target]; TICK_NOW(sta); uint32_t ret = service->send(verbose, call_sta); TICK_NOW(end); presults->fifo.addTime(tickDiffNS(sta, end)); presults->nNotInherent += (ret >> 16) & 0xffff; presults->nNotSync += ret & 0xffff; return 0; } // create a fifo thread to transact and wait it to finished static void threadTransaction(int target, PResults* presults) { ThreadArg thread_arg; void* dummy; pthread_t thread; pthread_attr_t attr; sched_param param; thread_arg.target = target; thread_arg.result = presults; REQUIRE(!pthread_attr_init(&attr)); REQUIRE(!pthread_attr_setschedpolicy(&attr, SCHED_FIFO)); param.sched_priority = sched_get_priority_max(SCHED_FIFO); REQUIRE(!pthread_attr_setschedparam(&attr, ¶m)); REQUIRE(!pthread_create(&thread, &attr, threadStart, &thread_arg)); REQUIRE(!pthread_join(thread, &dummy)); } static void serviceFx(const string& serviceName, Pipe p) { // Start service. if (registerPassthroughServiceImplementation<IScheduleTest>(serviceName) != ::android::OK) { cerr << "Failed to register service " << serviceName.c_str() << endl; exit(EXIT_FAILURE); } // tell main I'm init-ed p.signal(); // wait for kill p.wait(); exit(0); } static Pipe makeServiceProces(string service_name) { auto pipe_pair = Pipe::createPipePair(); pid_t pid = fork(); if (pid) { // parent return move(get<0>(pipe_pair)); } else { threadDumpPri("service"); // child serviceFx(service_name, move(get<1>(pipe_pair))); // never get here ASSERT(0); return move(get<0>(pipe_pair)); } } static void clientFx(int num, int server_count, int iterations, Pipe p) { PResults presults; presults.fifo.setTracingMode(is_tracing, deadline_us); if (dump_raw_data) { presults.fifo.setupRawData(); } for (int i = 0; i < server_count; i++) { sp<IScheduleTest> service = IScheduleTest::getService("hwbinderService" + to_string(i), pass_through); ASSERT(service != nullptr); if (pass_through) { ASSERT(!service->isRemote()); } else { ASSERT(service->isRemote()); } services.push_back(service); } // tell main I'm init-ed p.signal(); // wait for kick-off p.wait(); // Client for each pair iterates here // each iterations contains exactly 2 transactions for (int i = 0; i < iterations; i++) { Tick sta, end; // the target is paired to make it easier to diagnose int target = num; // 1. transaction by fifo thread threadTransaction(target, &presults); threadDumpPri("other-caller"); uint32_t call_sta = (threadGetPri() << 16) | sched_getcpu(); sp<IScheduleTest> service = services[target]; // 2. transaction by other thread TICK_NOW(sta); uint32_t ret = service->send(verbose, call_sta); TICK_NOW(end); presults.other.addTime(tickDiffNS(sta, end)); presults.nNotInherent += (ret >> 16) & 0xffff; presults.nNotSync += ret & 0xffff; } // tell main i'm done p.signal(); // wait to send result p.wait(); if (dump_raw_data) { cout << "\"fifo_" + to_string(num) + "_data\": "; presults.flushRawData(); } cout.flush(); int sent = p.send(presults); ASSERT(sent >= 0); // wait for kill p.wait(); exit(0); } static Pipe makeClientProcess(int num, int iterations, int no_pair) { auto pipe_pair = Pipe::createPipePair(); pid_t pid = fork(); if (pid) { // parent return move(get<0>(pipe_pair)); } else { // child threadDumpPri("client"); clientFx(num, no_pair, iterations, move(get<1>(pipe_pair))); // never get here ASSERT(0); return move(get<0>(pipe_pair)); } } static void waitAll(vector<Pipe>& v) { for (size_t i = 0; i < v.size(); i++) { v[i].wait(); } } static void signalAll(vector<Pipe>& v) { for (size_t i = 0; i < v.size(); i++) { v[i].signal(); } } static void help() { cout << "usage:" << endl; cout << "-i 1 # number of iterations" << endl; cout << "-pair 4 # number of process pairs" << endl; cout << "-deadline_us 2500 # deadline in us" << endl; cout << "-v # debug" << endl; cout << "-raw_data # dump raw data" << endl; cout << "-trace # halt the trace on a dealine hit" << endl; exit(0); } // Test: // // libhwbinder_latency -i 1 -v // libhwbinder_latency -i 10000 -pair 4 // atrace --async_start -c sched idle workq binder_driver freq && \ // libhwbinder_latency -i 10000 -pair 4 -trace int main(int argc, char** argv) { setenv("TREBLE_TESTING_OVERRIDE", "true", true); vector<Pipe> client_pipes; vector<Pipe> service_pipes; for (int i = 1; i < argc; i++) { if (string(argv[i]) == "-h") { help(); } if (string(argv[i]) == "-m") { if (!strcmp(argv[i + 1], "PASSTHROUGH")) { pass_through = true; } i++; continue; } if (string(argv[i]) == "-i") { iterations = atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-pair" || string(argv[i]) == "-w") { no_pair = atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-deadline_us") { deadline_us = atoi(argv[i + 1]); i++; continue; } if (string(argv[i]) == "-v") { verbose = 1; } if (string(argv[i]) == "-raw_data") { dump_raw_data = true; } // The -trace argument is used like that: // // First start trace with atrace command as usual // >atrace --async_start sched freq // // then use the -trace arguments like // -trace -deadline_us 2500 // // This makes the program to stop trace once it detects a transaction // duration over the deadline. By writing '0' to // /sys/kernel/debug/tracing and halt the process. The tracelog is // then available on /sys/kernel/debug/trace if (string(argv[i]) == "-trace") { is_tracing = 1; } } if (!pass_through) { // Create services. for (int i = 0; i < no_pair; i++) { service_pipes.push_back(makeServiceProces("hwbinderService" + to_string(i))); } // Wait until all services are up. waitAll(service_pipes); } if (is_tracing && !traceIsOn()) { cerr << "trace is not running" << endl; cerr << "check " << TRACE_PATH "/tracing_on" << endl; cerr << "use atrace --async_start first" << endl; exit(EXIT_FAILURE); } threadDumpPri("main"); cout << "{" << endl; cout << "\"cfg\":{\"pair\":" << (no_pair) << ",\"iterations\":" << iterations << ",\"deadline_us\":" << deadline_us << ",\"passthrough\":" << pass_through << "}," << endl; // the main process fork 2 processes for each pairs // 1 server + 1 client // each has a pipe to communicate with for (int i = 0; i < no_pair; i++) { client_pipes.push_back(makeClientProcess(i, iterations, no_pair)); } // wait client to init waitAll(client_pipes); // kick off clients signalAll(client_pipes); // wait client to finished waitAll(client_pipes); // collect all results PResults total, presults[no_pair]; for (int i = 0; i < no_pair; i++) { client_pipes[i].signal(); int recvd = client_pipes[i].recv(presults[i]); ASSERT(recvd >= 0); total = PResults::combine(total, presults[i]); } cout << "\"ALL\":"; total.dump(); for (int i = 0; i < no_pair; i++) { cout << "\"P" << i << "\":"; presults[i].dump(); } if (!pass_through) { signalAll(service_pipes); } int nNotInherent = 0; for (int i = 0; i < no_pair; i++) { nNotInherent += presults[i].nNotInherent; } cout << "\"inheritance\": " << (nNotInherent == 0 ? "\"PASS\"" : "\"FAIL\"") << endl; cout << "}" << endl; // kill all signalAll(client_pipes); return -nNotInherent; }