blob: 86540d155fd774473d21b6bae53d946272456d14 [file] [log] [blame]
// 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_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();
}
// Returns true if |input| ends with "perf".
bool IsPerfCommand(const string& input) {
// Name of the perf executable, without any path info.
const char kPerfBasename[] = "perf";
return input.size() >= strlen(kPerfBasename) &&
input.substr(input.size() - strlen(kPerfBasename)) == kPerfBasename;
}
// Given a perf command line, returns the subcommand, i.e. the argument that
// follows "perf".
// e.g. "sudo /usr/bin/perf record" -> "record".
bool GetPerfSubcommand(const std::vector<string>& command_line_args,
string* subcommand) {
for (size_t i = 0; i < command_line_args.size(); ++i) {
if (!IsPerfCommand(command_line_args[i]))
continue;
if (++i < command_line_args.size()) {
*subcommand = command_line_args[i];
return true;
}
}
return false;
}
// Checks for suspicious or malformed args that may contain an unsafe command.
// TODO(cwp-team): Should catch other things like non-perf commands.
bool PerfArgsContainExtraCommand(const std::vector<string>& args) {
// Perf uses '--' to mark the end of arguments and the beginning of the
// command to run and profile. We need to run sleep, so disallow this.
return std::find(args.begin(), args.end(), "--") != args.end();
}
// 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
bool PerfRecorder::RunCommandAndGetSerializedOutput(
const std::vector<string>& perf_args,
const int time,
string* output_string) {
if (PerfArgsContainExtraCommand(perf_args)) {
LOG(ERROR) << "Perf arguments may contain a command. Refusing to run it!";
return false;
}
string perf_type;
if (!GetPerfSubcommand(perf_args, &perf_type)) {
LOG(ERROR) << "Could not determine perf command type from args.";
return false;
}
if (perf_type != kPerfRecordCommand && perf_type != kPerfStatCommand) {
LOG(ERROR) << "Unsupported perf subcommand: " << perf_type;
return false;
}
std::vector<string> full_perf_args(perf_args.begin(), perf_args.end());
ScopedTempFile output_file;
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.
if (!RunCommand(full_perf_args, nullptr))
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