You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
12 KiB
400 lines
12 KiB
/* |
|
* 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; |
|
}
|
|
|