| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file provides a thin binary wrapper around the BattOr Agent |
| // library. This binary wrapper provides a means for non-C++ tracing |
| // controllers, such as Telemetry and Android Systrace, to issue high-level |
| // tracing commands to the BattOr through an interactive shell. |
| // |
| // Example usage of how an external trace controller might use this binary: |
| // |
| // 1) Telemetry's PowerTracingAgent is told to start recording power samples |
| // 2) PowerTracingAgent opens up a BattOr agent binary subprocess |
| // 3) PowerTracingAgent sends the subprocess the StartTracing message via |
| // STDIN |
| // 4) PowerTracingAgent waits for the subprocess to write a line to STDOUT |
| // ('Done.' if successful, some error message otherwise) |
| // 5) If the last command was successful, PowerTracingAgent waits for the |
| // duration of the trace |
| // 6) When the tracing should end, PowerTracingAgent records the clock sync |
| // start timestamp and sends the subprocess the |
| // 'RecordClockSyncMark <marker>' message via STDIN. |
| // 7) PowerTracingAgent waits for the subprocess to write a line to STDOUT |
| // ('Done.' if successful, some error message otherwise) |
| // 8) If the last command was successful, PowerTracingAgent records the clock |
| // sync end timestamp and sends the subprocess the StopTracing message via |
| // STDIN |
| // 9) PowerTracingAgent continues to read trace output lines from STDOUT until |
| // the binary exits with an exit code of 1 (indicating failure) or the |
| // 'Done.' line is printed to STDOUT, signaling the last line of the trace |
| // 10) PowerTracingAgent returns the battery trace to the Telemetry trace |
| // controller |
| |
| #include <stdint.h> |
| |
| #include <fstream> |
| #include <iomanip> |
| #include <iostream> |
| |
| #include "base/at_exit.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_tokenizer.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task_scheduler/task_scheduler.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "tools/battor_agent/battor_agent.h" |
| #include "tools/battor_agent/battor_error.h" |
| #include "tools/battor_agent/battor_finder.h" |
| |
| using std::endl; |
| |
| namespace battor { |
| |
| namespace { |
| |
| const char kIoThreadName[] = "BattOr IO Thread"; |
| |
| const char kUsage[] = |
| "Start the battor_agent shell with:\n" |
| "\n" |
| " battor_agent <switches>\n" |
| "\n" |
| "Switches: \n" |
| " --battor-path=<path> Uses the specified BattOr path.\n" |
| " --interactive Enables interactive power profiling." |
| "\n" |
| "Once in the shell, you can issue the following commands:\n" |
| "\n" |
| " StartTracing\n" |
| " StopTracing <optional file path>\n" |
| " SupportsExplicitClockSync\n" |
| " RecordClockSyncMarker <marker>\n" |
| " GetFirmwareGitHash\n" |
| " Exit\n" |
| " Help\n" |
| "\n"; |
| |
| // The command line switch used to enable interactive mode where starting and |
| // stopping is easily toggled. |
| const char kInteractiveSwitch[] = "interactive"; |
| |
| void PrintSupportsExplicitClockSync() { |
| std::cout << BattOrAgent::SupportsExplicitClockSync() << endl; |
| } |
| |
| // Logs the error and exits with an error code. |
| void HandleError(battor::BattOrError error) { |
| if (error != BATTOR_ERROR_NONE) |
| LOG(FATAL) << "Fatal error when communicating with the BattOr: " |
| << BattOrErrorToString(error); |
| } |
| |
| // Prints an error message and exits due to a required thread failing to start. |
| void ExitFromThreadStartFailure(const std::string& thread_name) { |
| LOG(FATAL) << "Failed to start " << thread_name; |
| } |
| |
| std::vector<std::string> TokenizeString(std::string cmd) { |
| base::StringTokenizer tokenizer(cmd, " "); |
| std::vector<std::string> tokens; |
| while (tokenizer.GetNext()) |
| tokens.push_back(tokenizer.token()); |
| return tokens; |
| } |
| |
| } // namespace |
| |
| // Wrapper class containing all state necessary for an independent binary to |
| // use a BattOrAgent to communicate with a BattOr. |
| class BattOrAgentBin : public BattOrAgent::Listener { |
| public: |
| BattOrAgentBin() : io_thread_(kIoThreadName) {} |
| |
| ~BattOrAgentBin() { DCHECK(!agent_); } |
| |
| // Starts the interactive BattOr agent shell and eventually returns an exit |
| // code. |
| int Run(int argc, char* argv[]) { |
| // If we don't have any BattOr to use, exit. |
| std::string path = BattOrFinder::FindBattOr(); |
| if (path.empty()) { |
| std::cout << "Unable to find a BattOr." << endl; |
| #if defined(OS_WIN) |
| std::cout << "Try \"--battor-path=<path>\" to specify the COM port where " |
| "the BattOr can be found, typically COM3." |
| << endl; |
| #endif |
| exit(1); |
| } |
| |
| SetUp(path); |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch(kInteractiveSwitch)) { |
| interactive_ = true; |
| std::cout << "Type <Enter> to toggle tracing, type Exit or Ctrl+C " |
| "to quit, or Help for help." |
| << endl; |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgentBin::RunNextCommand, base::Unretained(this))); |
| ui_thread_run_loop_.Run(); |
| |
| TearDown(); |
| return 0; |
| } |
| |
| // Performs any setup necessary for the BattOr binary to run. |
| void SetUp(const std::string& path) { |
| base::Thread::Options io_thread_options; |
| io_thread_options.message_loop_type = base::MessageLoopForIO::TYPE_IO; |
| if (!io_thread_.StartWithOptions(io_thread_options)) { |
| ExitFromThreadStartFailure(kIoThreadName); |
| } |
| |
| // Block until the creation of the BattOrAgent is complete. This doesn't |
| // seem necessary because we're posting the creation to the IO thread |
| // before posting any commands, so we're guaranteed that the creation |
| // will happen first. However, the crashes that happen without this sync |
| // mechanism in place say otherwise. |
| base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgentBin::CreateAgent, base::Unretained(this), path, |
| base::ThreadTaskRunnerHandle::Get(), &done)); |
| done.Wait(); |
| } |
| |
| // Performs any cleanup necessary after the BattOr binary is done running. |
| void TearDown() { |
| base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&BattOrAgentBin::DeleteAgent, |
| base::Unretained(this), &done)); |
| done.Wait(); |
| } |
| |
| void RunNextCommand() { |
| std::string cmd; |
| std::getline(std::cin, cmd); |
| |
| if (interactive_) { |
| if (cmd == "") { |
| cmd = is_tracing_ ? "StopTracing" : "StartTracing"; |
| std::cout << cmd << endl; |
| is_tracing_ = !is_tracing_; |
| } |
| } |
| |
| if (cmd == "StartTracing") { |
| StartTracing(); |
| } else if (cmd.find("StopTracing") != std::string::npos) { |
| std::vector<std::string> tokens = TokenizeString(cmd); |
| |
| if (tokens[0] != "StopTracing" || tokens.size() > 2) { |
| std::cout << "Invalid StopTracing command." << endl; |
| std::cout << kUsage << endl; |
| PostRunNextCommand(); |
| return; |
| } |
| |
| // tokens[1] contains the optional output file argument, which allows |
| // users to dump the trace to a file instead instead of to STDOUT. |
| std::string trace_output_file = |
| tokens.size() == 2 ? tokens[1] : std::string(); |
| |
| StopTracing(trace_output_file); |
| if (interactive_) { |
| PostRunNextCommand(); |
| } |
| } else if (cmd == "SupportsExplicitClockSync") { |
| PrintSupportsExplicitClockSync(); |
| PostRunNextCommand(); |
| } else if (cmd.find("RecordClockSyncMarker") != std::string::npos) { |
| std::vector<std::string> tokens = TokenizeString(cmd); |
| if (tokens.size() != 2 || tokens[0] != "RecordClockSyncMarker") { |
| std::cout << "Invalid RecordClockSyncMarker command." << endl; |
| std::cout << kUsage << endl; |
| PostRunNextCommand(); |
| return; |
| } |
| |
| RecordClockSyncMarker(tokens[1]); |
| } else if (cmd == "GetFirmwareGitHash") { |
| GetFirmwareGitHash(); |
| return; |
| } else if (cmd == "Exit" || std::cin.eof()) { |
| ui_thread_message_loop_.task_runner()->PostTask( |
| FROM_HERE, ui_thread_run_loop_.QuitClosure()); |
| } else { |
| std::cout << kUsage << endl; |
| PostRunNextCommand(); |
| } |
| } |
| |
| void PostRunNextCommand() { |
| ui_thread_message_loop_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgentBin::RunNextCommand, base::Unretained(this))); |
| } |
| |
| void GetFirmwareGitHash() { |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgent::GetFirmwareGitHash, |
| base::Unretained(agent_.get()))); |
| } |
| |
| void OnGetFirmwareGitHashComplete(const std::string& firmware_git_hash, |
| BattOrError error) override { |
| if (error == BATTOR_ERROR_NONE) |
| std::cout << firmware_git_hash << endl; |
| else |
| HandleError(error); |
| |
| PostRunNextCommand(); |
| } |
| |
| void StartTracing() { |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgent::StartTracing, base::Unretained(agent_.get()))); |
| } |
| |
| void OnStartTracingComplete(BattOrError error) override { |
| if (error == BATTOR_ERROR_NONE) |
| std::cout << "Done." << endl; |
| else |
| HandleError(error); |
| |
| PostRunNextCommand(); |
| } |
| |
| void StopTracing(const std::string& trace_output_file) { |
| trace_output_file_ = trace_output_file; |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&BattOrAgent::StopTracing, base::Unretained(agent_.get()))); |
| } |
| |
| std::string BattOrResultsToSummary(const BattOrResults& results) { |
| const uint32_t samples_per_second = results.GetSampleRate(); |
| |
| // Print a summary of a BattOr trace. These summaries are intended for human |
| // consumption and are subject to change at any moment. The summary is |
| // printed when using interactive mode. |
| std::stringstream trace_summary; |
| // Display floating-point numbers without exponents, in a five-character |
| // field, with two digits of precision. ie; |
| // 12.39 |
| // 8.40 |
| trace_summary << std::fixed << std::setw(5) << std::setprecision(2); |
| |
| // Scan through the sample data to summarize it. Report on average power and |
| // second-by-second power including min-second, median-second, and |
| // max-second. |
| double total_power = 0.0; |
| int num_seconds = 0; |
| std::vector<double> power_by_seconds; |
| const std::vector<float>& samples = results.GetPowerSamples(); |
| for (size_t i = 0; i < samples.size(); i += samples_per_second) { |
| size_t loop_count = samples.size() - i; |
| if (loop_count > samples_per_second) |
| loop_count = samples_per_second; |
| |
| double second_power = 0.0; |
| for (size_t j = i; j < i + loop_count; ++j) { |
| total_power += samples[i]; |
| second_power += samples[i]; |
| } |
| |
| // Print/store results for full seconds. |
| if (loop_count == samples_per_second) { |
| // Calculate power for one second in watts. |
| second_power /= samples_per_second; |
| trace_summary << "Second " << std::setw(2) << num_seconds |
| << " average power: " << std::setw(5) << second_power |
| << " W" << std::endl; |
| ++num_seconds; |
| power_by_seconds.push_back(second_power); |
| } |
| } |
| // Calculate average power in watts. |
| const double average_power_W = total_power / samples.size(); |
| const double duration_sec = |
| static_cast<double>(samples.size()) / samples_per_second; |
| trace_summary << "Average power over " << duration_sec |
| << " s : " << average_power_W << " W" << std::endl; |
| std::sort(power_by_seconds.begin(), power_by_seconds.end()); |
| if (power_by_seconds.size() >= 3) { |
| trace_summary << "Summary of power-by-seconds:" << std::endl |
| << "Minimum: " << power_by_seconds[0] << std::endl |
| << "Median: " |
| << power_by_seconds[power_by_seconds.size() / 2] |
| << std::endl |
| << "Maximum: " |
| << power_by_seconds[power_by_seconds.size() - 1] |
| << std::endl; |
| } else { |
| trace_summary << "Too short a trace to generate per-second summary."; |
| } |
| |
| return trace_summary.str(); |
| } |
| |
| void OnStopTracingComplete(const BattOrResults& results, |
| BattOrError error) override { |
| if (error == BATTOR_ERROR_NONE) { |
| std::string output_file = trace_output_file_; |
| if (trace_output_file_.empty()) { |
| // Save the detailed results in case they are needed. |
| base::FilePath default_path; |
| base::PathService::Get(base::DIR_USER_DESKTOP, &default_path); |
| default_path = default_path.Append(FILE_PATH_LITERAL("trace_data.txt")); |
| output_file = default_path.AsUTF8Unsafe().c_str(); |
| std::cout << "Saving detailed results to " << output_file << std::endl; |
| } |
| |
| if (interactive_) { |
| // Print a summary of the trace. |
| std::cout << BattOrResultsToSummary(results) << endl; |
| } |
| |
| std::ofstream trace_stream(output_file); |
| if (!trace_stream.is_open()) { |
| std::cout << "Tracing output file \"" << output_file |
| << "\" could not be opened." << endl; |
| exit(1); |
| } |
| trace_stream << results.ToString(); |
| trace_stream.close(); |
| std::cout << "Done." << endl; |
| } else { |
| HandleError(error); |
| } |
| |
| if (!interactive_) { |
| ui_thread_message_loop_.task_runner()->PostTask( |
| FROM_HERE, ui_thread_run_loop_.QuitClosure()); |
| } |
| } |
| |
| void RecordClockSyncMarker(const std::string& marker) { |
| io_thread_.task_runner()->PostTask( |
| FROM_HERE, base::Bind(&BattOrAgent::RecordClockSyncMarker, |
| base::Unretained(agent_.get()), marker)); |
| } |
| |
| void OnRecordClockSyncMarkerComplete(BattOrError error) override { |
| if (error == BATTOR_ERROR_NONE) |
| std::cout << "Done." << endl; |
| else |
| HandleError(error); |
| |
| PostRunNextCommand(); |
| } |
| |
| // Postable task for creating the BattOrAgent. Because the BattOrAgent has |
| // uber thread safe dependencies, all interactions with it, including creating |
| // and deleting it, MUST happen on the IO thread. |
| void CreateAgent( |
| const std::string& path, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner, |
| base::WaitableEvent* done) { |
| agent_.reset(new BattOrAgent(path, this, ui_thread_task_runner)); |
| done->Signal(); |
| } |
| |
| // Postable task for deleting the BattOrAgent. See the comment for |
| // CreateAgent() above regarding why this is necessary. |
| void DeleteAgent(base::WaitableEvent* done) { |
| agent_.reset(); |
| done->Signal(); |
| } |
| |
| private: |
| // NOTE: ui_thread_message_loop_ must appear before ui_thread_run_loop_ here |
| // because ui_thread_run_loop_ checks for the current MessageLoop during |
| // initialization. |
| base::MessageLoopForUI ui_thread_message_loop_; |
| base::RunLoop ui_thread_run_loop_; |
| |
| // Threads needed for serial communication. |
| base::Thread io_thread_; |
| |
| // The agent capable of asynchronously communicating with the BattOr. |
| std::unique_ptr<BattOrAgent> agent_; |
| |
| std::string trace_output_file_; |
| |
| // When true user can Start/Stop tracing by typing Enter. |
| bool interactive_ = false; |
| // Toggle to support alternating starting/stopping tracing. |
| bool is_tracing_ = false; |
| }; |
| |
| } // namespace battor |
| |
| int main(int argc, char* argv[]) { |
| base::AtExitManager exit_manager; |
| base::CommandLine::Init(argc, argv); |
| battor::BattOrAgentBin bin; |
| base::TaskScheduler::CreateAndStartWithDefaultParams("battor_agent"); |
| return bin.Run(argc, argv); |
| } |