IOEventLoop* loop;
event* e;
std::function<bool()> callback;
+ bool enabled;
IOEvent(IOEventLoop* loop, const std::function<bool()>& callback)
- : loop(loop), e(nullptr), callback(callback) {}
+ : loop(loop), e(nullptr), callback(callback), enabled(false) {}
~IOEvent() {
if (e != nullptr) {
return AddEvent(fd, EV_READ | EV_PERSIST, nullptr, callback);
}
+IOEventRef IOEventLoop::AddWriteEvent(int fd,
+ const std::function<bool()>& callback) {
+ if (!MakeFdNonBlocking(fd)) {
+ return nullptr;
+ }
+ return AddEvent(fd, EV_WRITE | EV_PERSIST, nullptr, callback);
+}
+
bool IOEventLoop::AddSignalEvent(int sig,
const std::function<bool()>& callback) {
return AddEvent(sig, EV_SIGNAL | EV_PERSIST, nullptr, callback) != nullptr;
LOG(ERROR) << "event_add() failed";
return nullptr;
}
+ e->enabled = true;
events_.push_back(std::move(e));
return events_.back().get();
}
return true;
}
+bool IOEventLoop::DisableEvent(IOEventRef ref) {
+ if (ref->enabled) {
+ if (event_del(ref->e) != 0) {
+ LOG(ERROR) << "event_del() failed";
+ return false;
+ }
+ ref->enabled = false;
+ }
+ return true;
+}
+
+bool IOEventLoop::EnableEvent(IOEventRef ref) {
+ if (!ref->enabled) {
+ if (event_add(ref->e, nullptr) != 0) {
+ LOG(ERROR) << "event_add() failed";
+ return false;
+ }
+ ref->enabled = true;
+ }
+ return true;
+}
+
bool IOEventLoop::DelEvent(IOEventRef ref) {
+ DisableEvent(ref);
IOEventLoop* loop = ref->loop;
for (auto it = loop->events_.begin(); it != loop->events_.end(); ++it) {
if (it->get() == ref) {
- if (event_del((*it)->e) != 0) {
- LOG(ERROR) << "event_del() failed";
- return false;
- }
loop->events_.erase(it);
break;
}
// to control the Event, otherwise return nullptr.
IOEventRef AddReadEvent(int fd, const std::function<bool()>& callback);
+ // Register a write Event, so [callback] is called when [fd] can be written
+ // without blocking.
+ IOEventRef AddWriteEvent(int fd, const std::function<bool()>& callback);
+
// Register a signal Event, so [callback] is called each time signal [sig]
// happens.
bool AddSignalEvent(int sig, const std::function<bool()>& callback);
// Exit the loop started by RunLoop().
bool ExitLoop();
+ // Disable an Event, which can be enabled later.
+ static bool DisableEvent(IOEventRef ref);
+ // Enable a disabled Event.
+ static bool EnableEvent(IOEventRef ref);
+
// Unregister an Event.
static bool DelEvent(IOEventRef ref);
int fd[2];
ASSERT_EQ(0, pipe(fd));
IOEventLoop loop;
- static int count;
- static int retry_count;
- count = 0;
- retry_count = 0;
+ int count = 0;
+ int retry_count = 0;
ASSERT_NE(nullptr, loop.AddReadEvent(fd[0], [&]() {
while (true) {
char c;
close(fd[1]);
}
+TEST(IOEventLoop, write) {
+ int fd[2];
+ ASSERT_EQ(0, pipe(fd));
+ IOEventLoop loop;
+ int count = 0;
+ ASSERT_NE(nullptr, loop.AddWriteEvent(fd[1], [&]() {
+ int ret = 0;
+ char buf[4096];
+ while ((ret = write(fd[1], buf, sizeof(buf))) > 0) {
+ }
+ if (ret == -1 && errno == EAGAIN) {
+ if (++count == 100) {
+ loop.ExitLoop();
+ }
+ return true;
+ }
+ return false;
+ }));
+ std::thread thread([&]() {
+ usleep(500000);
+ while (true) {
+ usleep(1000);
+ char buf[4096];
+ if (read(fd[0], buf, sizeof(buf)) <= 0) {
+ break;
+ }
+ }
+ });
+ ASSERT_TRUE(loop.RunLoop());
+ // close fd[1] to make read thread stop.
+ close(fd[1]);
+ thread.join();
+ close(fd[0]);
+ ASSERT_EQ(100, count);
+}
+
TEST(IOEventLoop, signal) {
IOEventLoop loop;
- static int count;
- count = 0;
+ int count = 0;
ASSERT_TRUE(loop.AddSignalEvent(SIGINT, [&]() {
if (++count == 100) {
loop.ExitLoop();
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 1000;
- static int count;
- count = 0;
+ int count = 0;
IOEventLoop loop;
ASSERT_TRUE(loop.AddPeriodicEvent(tv, [&]() {
if (++count == 100) {
int fd[2];
ASSERT_EQ(0, pipe(fd));
IOEventLoop loop;
- static int count;
- count = 0;
+ int count = 0;
IOEventRef ref = loop.AddReadEvent(fd[0], [&]() {
count++;
return IOEventLoop::DelEvent(ref);
close(fd[0]);
close(fd[1]);
}
+
+TEST(IOEventLoop, disable_enable_event) {
+ int fd[2];
+ ASSERT_EQ(0, pipe(fd));
+ IOEventLoop loop;
+ int count = 0;
+ IOEventRef ref = loop.AddWriteEvent(fd[1], [&]() {
+ count++;
+ return IOEventLoop::DisableEvent(ref);
+ });
+ ASSERT_NE(nullptr, ref);
+
+ timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 500000;
+ int periodic_count = 0;
+ ASSERT_TRUE(loop.AddPeriodicEvent(tv, [&]() {
+ periodic_count++;
+ if (periodic_count == 1) {
+ if (count != 1) {
+ return false;
+ }
+ return IOEventLoop::EnableEvent(ref);
+ } else {
+ if (count != 2) {
+ return false;
+ }
+ return loop.ExitLoop();
+ }
+ }));
+
+ ASSERT_TRUE(loop.RunLoop());
+ ASSERT_EQ(2, count);
+ ASSERT_EQ(2, periodic_count);
+ close(fd[0]);
+ close(fd[1]);
+}
template <typename SampleT, typename CallChainNodeT>
class CallgraphDisplayer {
public:
+ CallgraphDisplayer(uint32_t max_stack = UINT32_MAX,
+ double percent_limit = 0.0)
+ : max_stack_(max_stack), percent_limit_(percent_limit) {}
+
virtual ~CallgraphDisplayer() {}
void operator()(FILE* fp, const SampleT* sample) {
void DisplayCallGraphEntry(FILE* fp, size_t depth, std::string prefix,
const std::unique_ptr<CallChainNodeT>& node,
uint64_t parent_period, bool last) {
- if (depth > 20) {
- LOG(WARNING) << "truncated callgraph at depth " << depth;
+ if (depth > max_stack_) {
return;
}
- prefix += "|";
- fprintf(fp, "%s\n", prefix.c_str());
- if (last) {
- prefix.back() = ' ';
- }
std::string percentage_s = "-- ";
if (node->period + node->children_period != parent_period) {
double percentage =
100.0 * (node->period + node->children_period) / parent_period;
+ if (percentage < percent_limit_) {
+ return;
+ }
percentage_s = android::base::StringPrintf("--%.2f%%-- ", percentage);
}
+ prefix += "|";
+ fprintf(fp, "%s\n", prefix.c_str());
+ if (last) {
+ prefix.back() = ' ';
+ }
fprintf(fp, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(),
PrintSampleName(node->chain[0]).c_str());
prefix.append(percentage_s.size(), ' ');
virtual std::string PrintSampleName(const SampleT* sample) {
return sample->symbol->DemangledName();
}
+
+ private:
+ uint32_t max_stack_;
+ double percent_limit_;
};
// SampleDisplayer is a class using a collections of display functions to show a
#include <android-base/logging.h>
#include <android-base/file.h>
+#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
if (!NextArgumentOrError(args, &i)) {
return false;
}
- errno = 0;
- char* endptr;
- duration_in_sec_ = strtod(args[i].c_str(), &endptr);
- if (duration_in_sec_ <= 0 || *endptr != '\0' || errno == ERANGE) {
+ if (!android::base::ParseDouble(args[i].c_str(), &duration_in_sec_,
+ 1e-9)) {
LOG(ERROR) << "Invalid duration: " << args[i].c_str();
return false;
}
#include <vector>
#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
" the graph shows how functions call others.\n"
" Default is caller mode.\n"
"-i <file> Specify path of record file, default is perf.data.\n"
+"--max-stack <frames> Set max stack frames shown when printing call graph.\n"
"-n Print the sample count for each item.\n"
"--no-demangle Don't demangle symbol names.\n"
"--no-show-ip Don't show vaddr in file for unknown symbols.\n"
"-o report_file_name Set report file name, default is stdout.\n"
+"--percent-limit <percent> Set min percentage shown when printing call graph.\n"
"--pids pid1,pid2,... Report only for selected pids.\n"
"--sort key1,key2,... Select keys used to sort and print the report. The\n"
" appearance order of keys decides the order of keys used\n"
system_wide_collection_(false),
accumulate_callchain_(false),
print_callgraph_(false),
- callgraph_show_callee_(false) {}
+ callgraph_show_callee_(false),
+ callgraph_max_stack_(UINT32_MAX),
+ callgraph_percent_limit_(0) {}
bool Run(const std::vector<std::string>& args);
bool accumulate_callchain_;
bool print_callgraph_;
bool callgraph_show_callee_;
+ uint32_t callgraph_max_stack_;
+ double callgraph_percent_limit_;
std::string report_filename_;
};
}
record_filename_ = args[i];
+ } else if (args[i] == "--max-stack") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ if (!android::base::ParseUint(args[i].c_str(), &callgraph_max_stack_)) {
+ LOG(ERROR) << "invalid arg for --max-stack: " << args[i];
+ return false;
+ }
} else if (args[i] == "-n") {
print_sample_count = true;
return false;
}
report_filename_ = args[i];
-
+ } else if (args[i] == "--percent-limit") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ if (!android::base::ParseDouble(args[i].c_str(),
+ &callgraph_percent_limit_, 0.0)) {
+ LOG(ERROR) << "invalid arg for --percent-limit: " << args[i];
+ }
} else if (args[i] == "--pids" || args[i] == "--tids") {
const std::string& option = args[i];
std::unordered_set<int>& filter =
displayer.AddExclusiveDisplayFunction(
ReportCmdCallgraphDisplayerWithVaddrInFile());
} else {
- displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer());
+ displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer(
+ callgraph_max_stack_, callgraph_percent_limit_));
}
}
}
ASSERT_TRUE(success);
}
+TEST_F(ReportCommandTest, max_stack_and_percent_limit_option) {
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("89.03"), std::string::npos);
+
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--max-stack", "0"});
+ ASSERT_TRUE(success);
+ ASSERT_EQ(content.find("89.03"), std::string::npos);
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT, {"-g", "--max-stack", "1"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("89.03"), std::string::npos);
+
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
+ {"-g", "--percent-limit", "90"});
+ ASSERT_TRUE(success);
+ ASSERT_EQ(content.find("89.03"), std::string::npos);
+ Report(PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT,
+ {"-g", "--percent-limit", "70"});
+ ASSERT_TRUE(success);
+ ASSERT_NE(content.find("89.03"), std::string::npos);
+}
+
#if defined(__linux__)
#include "event_selection_set.h"
#include <vector>
#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
#include <android-base/strings.h>
#include "command.h"
if (!NextArgumentOrError(args, &i)) {
return false;
}
- errno = 0;
- char* endptr;
- duration_in_sec_ = strtod(args[i].c_str(), &endptr);
- if (duration_in_sec_ <= 0 || *endptr != '\0' || errno == ERANGE) {
+ if (!android::base::ParseDouble(args[i].c_str(), &duration_in_sec_,
+ 1e-9)) {
LOG(ERROR) << "Invalid duration: " << args[i].c_str();
return false;
}
if (!NextArgumentOrError(args, &i)) {
return false;
}
- errno = 0;
- char* endptr;
- interval_in_ms_ = strtod(args[i].c_str(), &endptr);
- if (interval_in_ms_ <= 0 || *endptr != '\0' || errno == ERANGE) {
+ if (!android::base::ParseDouble(args[i].c_str(), &interval_in_ms_,
+ 1e-9)) {
LOG(ERROR) << "Invalid interval: " << args[i].c_str();
return false;
}
// generated_by_linux_perf.data is generated by `perf record -F 1 -a -g -- sleep 0.1`.
static const std::string PERF_DATA_GENERATED_BY_LINUX_PERF = "generated_by_linux_perf.data";
+// generated by `simpleperf record -g ls`.
+static const std::string PERF_DATA_MAX_STACK_AND_PERCENT_LIMIT = "perf_test_max_stack_and_percent_limit.data";
+
#endif // SIMPLE_PERF_GET_TEST_DATA_H_