// Copyright (C) 2018 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. //#undef NDEBUG // get DCHECK etc. #include "common/debug.h" #include "common/expected.h" #include "perfetto/rx_producer.h" #include <android-base/unique_fd.h> #include <android-base/parseint.h> #include <android-base/file.h> #include "rxcpp/rx.hpp" #include <iostream> #include <optional> #include <sched.h> #include <sys/types.h> #include <sys/stat.h> #include <syscall.h> #include <fcntl.h> #include <unistd.h> using namespace iorap::perfetto; // NOLINT #if defined(IORAP_PERFETTO_MAIN) void Usage(char** argv) { std::cerr << "Usage: " << argv[0] << " [--config-proto=config.pb] [--duration-ms=5000] [--output-proto=output.pb]" << std::endl; std::cerr << "" << std::endl; std::cerr << " Request a perfetto trace, blocking until it's complete. The resulting trace proto" << std::endl; std::cerr << " is output to stdout as text, or to --output-proto as a binary." << std::endl; std::cerr << "" << std::endl; std::cerr << " Optional flags:" << std::endl; std::cerr << " --help,-h Print this Usage." << std::endl; std::cerr << " --output-proto $,-op $ Perfetto tracebuffer output file (default stdout)." << std::endl; std::cerr << " --config-proto $,-cp $ Path to binary protobuf config." << std::endl; std::cerr << " --duration-ms $,-dm $ How long to run trace for in milliseconds." << std::endl; std::cerr << " --simple Simplest possible perfetto state transitions (default off)." << std::endl; std::cerr << " --verbose,-v Set verbosity (default off)." << std::endl; std::cerr << " --wait,-w Wait for key stroke before continuing (default off)." << std::endl; exit(1); } PerfettoDependencies::Component CreateCommandLinePerfettoDependenciesComponent( uint32_t duration_ms) { // TODO: read from command line. static const uint32_t kBufferSize = 4096; // TODO: remove this hack. static const uint32_t kTraceDurationMs = duration_ms; // fruit: using 'bindInstance' causes a segfault every time. #if 0 // fruit: Can't use a stateful lambda, so use bindInstance instead of registerProvider. auto config = PerfettoDependencies::CreateConfig(duration_ms, /*deferred_start*/true, kBufferSize); .... bindInstance(config); #endif return fruit::createComponent() .bind<PerfettoConsumer, PerfettoConsumerImpl>() .registerProvider([]() /* -> TraceConfig */ { return PerfettoDependencies::CreateConfig(kTraceDurationMs, /*deferred_start*/true, kBufferSize); }); } static void CollectPerfettoTraceBufferViaAbstractions( RxProducerFactory& producer_factory, const std::string& arg_output_proto, const int arg_duration_ms) { LOG(VERBOSE) << "CollectPerfettoTraceBufferViaAbstractions"; // Don't create a subscriber to emit the PerfettoStreamCommand. // RxCpp is "greedy" and consumes every possible item emitted (it doesn't support 'pull'). We want // to operate on a (command,state) iteration every time, just like in a real scenario. // Adding the 'interval' turns into a non-greedy version (i.e. push). // Immediately emit 'kStartTracing', wait and emit kStopTracing, wait and emit kShutdown. // In reality, there would be a delay between all these events. auto /*observable<PerfettoStreamCommand>*/ commands = rxcpp::observable<>::just(PerfettoStreamCommand::kStartTracing) // wait 1x .concat( // Pick a value longer than the perfetto config delay_ms, so that we send // 'kShutdown' after tracing has already finished. rxcpp::observable<>::interval(std::chrono::milliseconds(arg_duration_ms * 2)) .take(2) // kStopTracing, kShutdown. .map([](int value) { // value is 1,2,3,... return static_cast<PerfettoStreamCommand>(value); // 1,2, ... }) ); auto /*observable<PerfettoTraceProto>*/ trace_proto_stream = producer_factory.CreateTraceStream(commands); trace_proto_stream .observe_on(ObserveOnNewIoThread()) // Write data on an idle-class-priority thread. .as_blocking() // Wait for observable to terminate with on_completed or on_error. .subscribe(/*on_next*/[arg_output_proto] (PerfettoTraceProto trace_proto) { if (!trace_proto.WriteFullyToFile(arg_output_proto)) { LOG(ERROR) << "Failed to save TraceBuffer to " << arg_output_proto; } else { LOG(INFO) << "TraceBuffer saved to file: " << arg_output_proto; LOG(INFO); LOG(INFO) << "To print this in a human readable form, execute these commands:"; LOG(INFO) << "$> adb pull '" << arg_output_proto << "'"; LOG(INFO) << "$> trace_to_text systrace <filename.pb>"; } }, /*on_error*/[](rxcpp::util::error_ptr err) { LOG(ERROR) << "Perfetto trace proto collection error: " << rxcpp::util::what(err); }); } namespace iorap::perfetto { // Reach inside rx_producer.cc // Not part of any headers because it's internal. void CollectPerfettoTraceBufferImmediately( RxProducerFactory& producer_factory, const std::string& arg_output_proto); } int main(int argc, char** argv) { android::base::InitLogging(argv); android::base::SetLogger(android::base::StderrLogger); bool wait_for_keystroke = false; bool enable_verbose = false; std::string arg_output_proto; std::string arg_config_proto; uint32_t arg_duration_ms = 1000; bool arg_simple = false; if (argc == 1) { Usage(argv); } for (int arg = 1; arg < argc; ++arg) { std::string argstr = argv[arg]; bool has_arg_next = (arg+1)<argc; std::string arg_next = has_arg_next ? argv[arg+1] : ""; if (argstr == "--help" || argstr == "-h") { Usage(argv); } else if (argstr == "--output-proto" || argstr == "-op") { if (!has_arg_next) { std::cerr << "Missing --output-proto <value>" << std::endl; return 1; } arg_output_proto = arg_next; ++arg; } else if (argstr == "--config-proto" || argstr == "-cp") { if (!has_arg_next) { std::cerr << "Missing --config-proto <value>" << std::endl; return 1; } arg_config_proto = arg_next; LOG(WARNING) << "TODO: parse configs from a file, not implemented yet."; ++arg; } else if (argstr == "--duration-ms" || argstr == "-dm") { if (!has_arg_next) { std::cerr << "Missing --duration-ms <value>" << std::endl; return 1; } if (!android::base::ParseUint(arg_next.c_str(), /*out*/&arg_duration_ms)) { std::cerr << "Invalid --duration-ms " << arg_next << ", reason: " << strerror(errno); return 1; } ++arg; } else if (argstr == "--simple") { arg_simple = true; } else if (argstr == "--verbose" || argstr == "-v") { enable_verbose = true; } else if (argstr == "--wait" || argstr == "-w") { wait_for_keystroke = true; } } if (enable_verbose) { android::base::SetMinimumLogSeverity(android::base::VERBOSE); LOG(VERBOSE) << "Verbose check"; LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild; } // Useful to attach a debugger... // 1) $> iorap-cmd-perfetto -w <args> // 2) $> gdbclient <pid> if (wait_for_keystroke) { LOG(INFO) << "Self pid: " << getpid(); LOG(INFO) << "Press any key to continue..."; std::cin >> wait_for_keystroke; } int return_code = 0; // TODO: convert #on-error into a non-0 return code. PerfettoDependencies::Injector injector{ CreateCommandLinePerfettoDependenciesComponent, arg_duration_ms }; RxProducerFactory rx_producer_factory{/*borrow*/injector}; if (arg_simple) { // To debug any kind of low-level perfetto issues. CollectPerfettoTraceBufferImmediately(/*inout*/rx_producer_factory, arg_output_proto); } else { // To debug our own iorap internal abstractions. CollectPerfettoTraceBufferViaAbstractions(/*inout*/rx_producer_factory, arg_output_proto, arg_duration_ms); } // Uncomment this if we want to leave the process around to inspect it from adb shell. // sleep(100000); // 0 -> successfully wrote the TraceProto out to file. // 1 -> failed along the way (#on_error and also see the error logs). return return_code; } #endif