blob: b466a4f06e7f4bddd7e530a94c7b8a09251b1dd9 [file] [log] [blame]
Ahmad Sharifae1714d2013-01-17 11:29:37 -08001// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Alex Vakulenko262be3f2014-07-30 15:25:50 -07005#include "debugd/src/perf_tool.h"
Ahmad Sharifae1714d2013-01-17 11:29:37 -08006
Chinglin Yu0d4368d2018-10-12 13:54:41 +08007#include <signal.h>
8#include <sys/types.h>
David Sharp16d35652016-04-05 17:20:08 -07009#include <unistd.h>
Simon Que21bb7902014-07-28 16:17:20 -070010
Ben Chan51b0c142017-01-06 18:10:27 -080011#include <base/bind.h>
12#include <base/strings/stringprintf.h>
Chinglin Yu0d4368d2018-10-12 13:54:41 +080013#include <base/time/time.h>
David Sharp16d35652016-04-05 17:20:08 -070014
Eric Carusocc7106c2017-04-27 14:22:42 -070015#include "debugd/src/error_utils.h"
Alex Vakulenko262be3f2014-07-30 15:25:50 -070016#include "debugd/src/process_with_output.h"
Ahmad Sharifae1714d2013-01-17 11:29:37 -080017
David Sharpe01f7252015-06-30 16:24:05 -070018namespace debugd {
19
Ahmad Shariff5597f62013-04-25 12:25:41 -070020namespace {
21
David Sharp61901312015-08-05 13:32:07 -070022const char kUnsupportedPerfToolErrorName[] =
23 "org.chromium.debugd.error.UnsupportedPerfTool";
David Sharp16d35652016-04-05 17:20:08 -070024const char kProcessErrorName[] = "org.chromium.debugd.error.RunProcess";
Chinglin Yu0d4368d2018-10-12 13:54:41 +080025const char kStopProcessErrorName[] = "org.chromium.debugd.error.StopProcess";
David Sharp61901312015-08-05 13:32:07 -070026
Eric Carusocc7106c2017-04-27 14:22:42 -070027const char kArgsError[] = "perf_args must begin with {\"perf\", \"record\"}, "
28 " {\"perf\", \"stat\"}, or {\"perf\", \"mem\"}";
29
Ahmad Shariff5597f62013-04-25 12:25:41 -070030// Location of quipper on ChromeOS.
31const char kQuipperLocation[] = "/usr/bin/quipper";
32
Simon Queecb08352015-10-02 17:38:12 -070033enum PerfSubcommand {
34 PERF_COMMAND_RECORD,
35 PERF_COMMAND_STAT,
36 PERF_COMMAND_MEM,
37 PERF_COMMAND_UNSUPPORTED,
38};
39
40// Returns one of the above enums given an vector of perf arguments, starting
41// with "perf" itself in |args[0]|.
42PerfSubcommand GetPerfSubcommandType(const std::vector<std::string>& args) {
43 if (args[0] == "perf" && args.size() > 1) {
44 if (args[1] == "record")
45 return PERF_COMMAND_RECORD;
46 if (args[1] == "stat")
47 return PERF_COMMAND_STAT;
48 if (args[1] == "mem")
49 return PERF_COMMAND_MEM;
50 }
51
52 return PERF_COMMAND_UNSUPPORTED;
53}
54
David Sharp16d35652016-04-05 17:20:08 -070055void AddQuipperArguments(brillo::Process* process,
56 const uint32_t duration_secs,
57 const std::vector<std::string>& perf_args) {
58 process->AddArg(kQuipperLocation);
Eric Caruso96d03d32017-04-25 18:01:17 -070059 process->AddArg(base::StringPrintf("%u", duration_secs));
David Sharp16d35652016-04-05 17:20:08 -070060 for (const auto& arg : perf_args) {
61 process->AddArg(arg);
62 }
63}
64
Ahmad Shariff5597f62013-04-25 12:25:41 -070065} // namespace
66
Chinglin Yu0d4368d2018-10-12 13:54:41 +080067PerfTool::PerfTool() {
68 signal_handler_.Init();
69 process_reaper_.Register(&signal_handler_);
70}
Ahmad Sharifae1714d2013-01-17 11:29:37 -080071
Eric Caruso56eacb22017-08-04 11:00:37 -070072bool PerfTool::GetPerfOutput(uint32_t duration_secs,
73 const std::vector<std::string>& perf_args,
74 std::vector<uint8_t>* perf_data,
75 std::vector<uint8_t>* perf_stat,
76 int32_t* status,
77 brillo::ErrorPtr* error) {
Simon Queecb08352015-10-02 17:38:12 -070078 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
79 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -070080 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso56eacb22017-08-04 11:00:37 -070081 return false;
David Sharp61901312015-08-05 13:32:07 -070082 }
83
Eric Caruso56eacb22017-08-04 11:00:37 -070084 // This whole method is synchronous, so we create a subprocess, let it run to
85 // completion, then gather up its output to return it.
86 ProcessWithOutput process;
87 process.SandboxAs("root", "root");
88 if (!process.Init()) {
89 DEBUGD_ADD_ERROR(
90 error, kProcessErrorName, "Process initialization failure.");
91 return false;
92 }
93
94 AddQuipperArguments(&process, duration_secs, perf_args);
95
Simon Queac5f9cf2015-06-20 13:50:22 -070096 std::string output_string;
Eric Caruso56eacb22017-08-04 11:00:37 -070097 *status = process.Run();
98 if (*status != 0) {
99 output_string =
100 base::StringPrintf("<process exited with status: %d>", *status);
101 } else {
102 process.GetOutput(&output_string);
103 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700104
Simon Queecb08352015-10-02 17:38:12 -0700105 switch (subcommand) {
106 case PERF_COMMAND_RECORD:
107 case PERF_COMMAND_MEM:
Simon Queac5f9cf2015-06-20 13:50:22 -0700108 perf_data->assign(output_string.begin(), output_string.end());
Simon Queecb08352015-10-02 17:38:12 -0700109 break;
110 case PERF_COMMAND_STAT:
Simon Queac5f9cf2015-06-20 13:50:22 -0700111 perf_stat->assign(output_string.begin(), output_string.end());
Simon Queecb08352015-10-02 17:38:12 -0700112 break;
113 default:
114 // Discard the output.
115 break;
116 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700117
Eric Caruso56eacb22017-08-04 11:00:37 -0700118 return true;
Simon Queac5f9cf2015-06-20 13:50:22 -0700119}
120
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800121void PerfTool::OnQuipperProcessExited(const siginfo_t& siginfo) {
122 // Called after SIGCHLD has been received from the signalfd file descriptor.
123 // Wait() for the child process wont' block. It'll just reap the zombie child
124 // process.
125 quipper_process_->Wait();
126 quipper_process_ = nullptr;
127
128 profiler_session_id_.reset();
129}
130
Eric Caruso56eacb22017-08-04 11:00:37 -0700131bool PerfTool::GetPerfOutputFd(uint32_t duration_secs,
David Sharp16d35652016-04-05 17:20:08 -0700132 const std::vector<std::string>& perf_args,
Eric Caruso0b241882018-04-04 13:43:46 -0700133 const base::ScopedFD& stdout_fd,
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800134 uint64_t* session_id,
Eric Carusocc7106c2017-04-27 14:22:42 -0700135 brillo::ErrorPtr* error) {
David Sharp16d35652016-04-05 17:20:08 -0700136 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
137 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700138 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso8fe49c72017-04-25 10:43:59 -0700139 return false;
David Sharp16d35652016-04-05 17:20:08 -0700140 }
141
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800142 if (quipper_process_) {
143 // Do not run multiple sessions at the same time. Attempt to start another
144 // profiler session using this method yields a DBus error. Note that
145 // starting another session using GetPerfOutput() will still succeed.
146 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Existing perf tool running.");
147 return false;
148 }
149
150 DCHECK(!profiler_session_id_);
151
152 quipper_process_ = std::make_unique<SandboxedProcess>();
153 quipper_process_->SandboxAs("root", "root");
154 if (!quipper_process_->Init()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700155 DEBUGD_ADD_ERROR(
156 error, kProcessErrorName, "Process initialization failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700157 return false;
David Sharp16d35652016-04-05 17:20:08 -0700158 }
159
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800160 AddQuipperArguments(quipper_process_.get(), duration_secs, perf_args);
161 quipper_process_->BindFd(stdout_fd.get(), 1);
David Sharp16d35652016-04-05 17:20:08 -0700162
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800163 if (!quipper_process_->Start()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700164 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Process start failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700165 return false;
David Sharp16d35652016-04-05 17:20:08 -0700166 }
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800167 DCHECK_GT(quipper_process_->pid(), 0);
168
169 process_reaper_.WatchForChild(
170 FROM_HERE, quipper_process_->pid(),
171 base::Bind(&PerfTool::OnQuipperProcessExited, base::Unretained(this)));
172
173 // Generate an opaque, pseudo-unique, session ID using time and process ID.
174 profiler_session_id_ = *session_id =
175 static_cast<uint64_t>(base::Time::Now().ToTimeT()) << 32 |
176 (quipper_process_->pid() & 0xffffffff);
177
178 return true;
179}
180
181bool PerfTool::StopPerf(uint64_t session_id, brillo::ErrorPtr* error) {
182 if (!profiler_session_id_) {
183 DEBUGD_ADD_ERROR(error, kStopProcessErrorName, "Perf tool not started");
184 return false;
185 }
186
187 if (profiler_session_id_ != session_id) {
188 // Session ID mismatch: return a failure without affecting the existing
189 // profiler session.
190 DEBUGD_ADD_ERROR(error, kStopProcessErrorName,
191 "Invalid profile session id.");
192 return false;
193 }
194
195 // Stop by sending SIGINT to the profiler session. The sandboxed quipper
196 // process will be reaped in OnQuipperProcessExited().
197 if (quipper_process_) {
198 DCHECK_GT(quipper_process_->pid(), 0);
199 if (kill(quipper_process_->pid(), SIGINT) != 0) {
200 PLOG(WARNING) << "Failed to stop the profiler session.";
201 }
202 }
203
Eric Caruso8fe49c72017-04-25 10:43:59 -0700204 return true;
David Sharp16d35652016-04-05 17:20:08 -0700205}
206
Ben Chana0011d82014-05-13 00:19:29 -0700207} // namespace debugd