blob: b81f3b1ad577bdff6fe7633e2efdeab9d6853757 [file] [log] [blame]
// Copyright (c) 2013 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 "debugd/src/perf_tool.h"
#include <unistd.h>
#include <base/bind.h>
#include <base/strings/stringprintf.h>
#include "debugd/src/error_utils.h"
#include "debugd/src/process_with_output.h"
namespace debugd {
namespace {
const char kUnsupportedPerfToolErrorName[] =
"org.chromium.debugd.error.UnsupportedPerfTool";
const char kProcessErrorName[] = "org.chromium.debugd.error.RunProcess";
const char kArgsError[] = "perf_args must begin with {\"perf\", \"record\"}, "
" {\"perf\", \"stat\"}, or {\"perf\", \"mem\"}";
// Location of quipper on ChromeOS.
const char kQuipperLocation[] = "/usr/bin/quipper";
enum PerfSubcommand {
PERF_COMMAND_RECORD,
PERF_COMMAND_STAT,
PERF_COMMAND_MEM,
PERF_COMMAND_UNSUPPORTED,
};
// Returns one of the above enums given an vector of perf arguments, starting
// with "perf" itself in |args[0]|.
PerfSubcommand GetPerfSubcommandType(const std::vector<std::string>& args) {
if (args[0] == "perf" && args.size() > 1) {
if (args[1] == "record")
return PERF_COMMAND_RECORD;
if (args[1] == "stat")
return PERF_COMMAND_STAT;
if (args[1] == "mem")
return PERF_COMMAND_MEM;
}
return PERF_COMMAND_UNSUPPORTED;
}
void AddQuipperArguments(brillo::Process* process,
const uint32_t duration_secs,
const std::vector<std::string>& perf_args) {
process->AddArg(kQuipperLocation);
process->AddArg(base::StringPrintf("%u", duration_secs));
for (const auto& arg : perf_args) {
process->AddArg(arg);
}
}
// For use with brillo::Process::SetPreExecCallback(), this runs after
// the fork() in the child process, but before exec(). Call fork() again
// and exit the parent (child of main process). The grandchild will get
// orphaned, meaning init will wait() for it up so we don't have to.
bool Orphan() {
if (fork() == 0) { // (grand-)child
return true;
}
// parent
::_Exit(EXIT_SUCCESS);
}
} // namespace
PerfTool::PerfTool() {}
bool PerfTool::GetPerfOutput(uint32_t duration_secs,
const std::vector<std::string>& perf_args,
std::vector<uint8_t>* perf_data,
std::vector<uint8_t>* perf_stat,
int32_t* status,
brillo::ErrorPtr* error) {
PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
if (subcommand == PERF_COMMAND_UNSUPPORTED) {
DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
return false;
}
// This whole method is synchronous, so we create a subprocess, let it run to
// completion, then gather up its output to return it.
ProcessWithOutput process;
process.SandboxAs("root", "root");
if (!process.Init()) {
DEBUGD_ADD_ERROR(
error, kProcessErrorName, "Process initialization failure.");
return false;
}
AddQuipperArguments(&process, duration_secs, perf_args);
std::string output_string;
*status = process.Run();
if (*status != 0) {
output_string =
base::StringPrintf("<process exited with status: %d>", *status);
} else {
process.GetOutput(&output_string);
}
switch (subcommand) {
case PERF_COMMAND_RECORD:
case PERF_COMMAND_MEM:
perf_data->assign(output_string.begin(), output_string.end());
break;
case PERF_COMMAND_STAT:
perf_stat->assign(output_string.begin(), output_string.end());
break;
default:
// Discard the output.
break;
}
return true;
}
bool PerfTool::GetPerfOutputFd(uint32_t duration_secs,
const std::vector<std::string>& perf_args,
const dbus::FileDescriptor& stdout_fd,
brillo::ErrorPtr* error) {
PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
if (subcommand == PERF_COMMAND_UNSUPPORTED) {
DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
return false;
}
SandboxedProcess process;
process.SandboxAs("root", "root");
if (!process.Init()) {
DEBUGD_ADD_ERROR(
error, kProcessErrorName, "Process initialization failure.");
return false;
}
AddQuipperArguments(&process, duration_secs, perf_args);
process.SetPreExecCallback(base::Bind(Orphan));
process.BindFd(stdout_fd.value(), 1);
if (process.Run() != 0) {
DEBUGD_ADD_ERROR(error, kProcessErrorName, "Process start failure.");
return false;
}
return true;
}
} // namespace debugd