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.
401 lines
12 KiB
401 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) {
|
|
android::hardware::details::setTrebleTestingOverride(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;
|
|
}
|