| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromiumos-wide-profiling/perf_recorder.h" |
| |
| #include <algorithm> |
| #include <cstdio> |
| #include <cstring> |
| #include <sstream> |
| #include <vector> |
| |
| #include "chromiumos-wide-profiling/compat/proto.h" |
| #include "chromiumos-wide-profiling/compat/string.h" |
| #include "chromiumos-wide-profiling/perf_option_parser.h" |
| #include "chromiumos-wide-profiling/perf_serializer.h" |
| #include "chromiumos-wide-profiling/perf_stat_parser.h" |
| #include "chromiumos-wide-profiling/run_command.h" |
| #include "chromiumos-wide-profiling/scoped_temp_path.h" |
| #include "chromiumos-wide-profiling/utils.h" |
| |
| namespace quipper { |
| |
| namespace { |
| |
| // Supported perf subcommands. |
| const char kPerfRecordCommand[] = "record"; |
| const char kPerfStatCommand[] = "stat"; |
| |
| string IntToString(const int i) { |
| stringstream ss; |
| ss << i; |
| return ss.str(); |
| } |
| |
| // Reads a perf data file and converts it to a PerfDataProto, which is stored as |
| // a serialized string in |output_string|. Returns true on success. |
| bool ParsePerfDataFileToString(const string& filename, string* output_string) { |
| // Now convert it into a protobuf. |
| PerfSerializer perf_serializer; |
| PerfParser::Options options; |
| // Make sure to remap address for security reasons. |
| options.do_remap = true; |
| // Discard unused perf events to reduce the protobuf size. |
| options.discard_unused_events = true; |
| |
| perf_serializer.set_options(options); |
| |
| PerfDataProto perf_data; |
| return perf_serializer.SerializeFromFile(filename, &perf_data) && |
| perf_data.SerializeToString(output_string); |
| } |
| |
| // Reads a perf data file and converts it to a PerfStatProto, which is stored as |
| // a serialized string in |output_string|. Returns true on success. |
| bool ParsePerfStatFileToString(const string& filename, |
| const std::vector<string>& command_line_args, |
| string* output_string) { |
| PerfStatProto perf_stat; |
| if (!ParsePerfStatFileToProto(filename, &perf_stat)) { |
| LOG(ERROR) << "Failed to parse PerfStatProto from " << filename; |
| return false; |
| } |
| |
| // Fill in the command line field of the protobuf. |
| string command_line; |
| for (size_t i = 0; i < command_line_args.size(); ++i) { |
| const string& arg = command_line_args[i]; |
| // Strip the output file argument from the command line. |
| if (arg == "-o") { |
| ++i; |
| continue; |
| } |
| command_line.append(arg + " "); |
| } |
| command_line.resize(command_line.size() - 1); |
| perf_stat.mutable_command_line()->assign(command_line); |
| |
| return perf_stat.SerializeToString(output_string); |
| } |
| |
| } // namespace |
| |
| PerfRecorder::PerfRecorder() : PerfRecorder({"/usr/bin/perf"}) {} |
| |
| PerfRecorder::PerfRecorder(const std::vector<string>& perf_binary_command) |
| : perf_binary_command_(perf_binary_command) { |
| } |
| |
| bool PerfRecorder::RunCommandAndGetSerializedOutput( |
| const std::vector<string>& perf_args, |
| const int time, |
| string* output_string) { |
| if (!ValidatePerfCommandLine(perf_args)) { |
| LOG(ERROR) << "Perf arguments are not safe to run!"; |
| return false; |
| } |
| |
| // ValidatePerfCommandLine should have checked perf_args[0] == "perf", and |
| // that perf_args[1] is a supported sub-command (e.g. "record" or "stat"). |
| |
| const string& perf_type = perf_args[1]; |
| |
| if (perf_type != kPerfRecordCommand && perf_type != kPerfStatCommand) { |
| LOG(ERROR) << "Unsupported perf subcommand: " << perf_type; |
| return false; |
| } |
| |
| ScopedTempFile output_file; |
| |
| // Assemble the full command line: |
| // - Replace "perf" in |perf_args[0]| with |perf_binary_command_| to |
| // guarantee we're running a binary we believe we can trust. |
| // - Add our own paramters. |
| |
| std::vector<string> full_perf_args(perf_binary_command_); |
| full_perf_args.insert(full_perf_args.end(), |
| perf_args.begin() + 1, // skip "perf" |
| perf_args.end()); |
| full_perf_args.insert(full_perf_args.end(), {"-o", output_file.path()}); |
| |
| // The perf stat output parser requires raw data from verbose output. |
| if (perf_type == kPerfStatCommand) |
| full_perf_args.emplace_back("-v"); |
| |
| // Append the sleep command to run perf for |time| seconds. |
| full_perf_args.insert(full_perf_args.end(), |
| {"--", "sleep", IntToString(time)}); |
| |
| // The perf command writes the output to a file, so ignore stdout. |
| int status = RunCommand(full_perf_args, nullptr); |
| if (status != 0) { |
| PLOG(ERROR) << "perf command failed with status: " << status << ", Error"; |
| return false; |
| } |
| |
| if (perf_type == kPerfRecordCommand) |
| return ParsePerfDataFileToString(output_file.path(), output_string); |
| |
| // Otherwise, parse as perf stat output. |
| return ParsePerfStatFileToString(output_file.path(), |
| full_perf_args, |
| output_string); |
| } |
| |
| } // namespace quipper |