blob: bdc72be1de5e07c8354dd8c94d925399525fd4ab [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;
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000127 quipper_process_output_fd_.reset();
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800128
129 profiler_session_id_.reset();
130}
131
Eric Caruso56eacb22017-08-04 11:00:37 -0700132bool PerfTool::GetPerfOutputFd(uint32_t duration_secs,
David Sharp16d35652016-04-05 17:20:08 -0700133 const std::vector<std::string>& perf_args,
Eric Caruso0b241882018-04-04 13:43:46 -0700134 const base::ScopedFD& stdout_fd,
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800135 uint64_t* session_id,
Eric Carusocc7106c2017-04-27 14:22:42 -0700136 brillo::ErrorPtr* error) {
David Sharp16d35652016-04-05 17:20:08 -0700137 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
138 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700139 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso8fe49c72017-04-25 10:43:59 -0700140 return false;
David Sharp16d35652016-04-05 17:20:08 -0700141 }
142
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800143 if (quipper_process_) {
144 // Do not run multiple sessions at the same time. Attempt to start another
145 // profiler session using this method yields a DBus error. Note that
146 // starting another session using GetPerfOutput() will still succeed.
147 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Existing perf tool running.");
148 return false;
149 }
150
151 DCHECK(!profiler_session_id_);
152
153 quipper_process_ = std::make_unique<SandboxedProcess>();
154 quipper_process_->SandboxAs("root", "root");
155 if (!quipper_process_->Init()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700156 DEBUGD_ADD_ERROR(
157 error, kProcessErrorName, "Process initialization failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700158 return false;
David Sharp16d35652016-04-05 17:20:08 -0700159 }
160
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800161 AddQuipperArguments(quipper_process_.get(), duration_secs, perf_args);
162 quipper_process_->BindFd(stdout_fd.get(), 1);
David Sharp16d35652016-04-05 17:20:08 -0700163
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800164 if (!quipper_process_->Start()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700165 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Process start failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700166 return false;
David Sharp16d35652016-04-05 17:20:08 -0700167 }
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800168 DCHECK_GT(quipper_process_->pid(), 0);
169
170 process_reaper_.WatchForChild(
171 FROM_HERE, quipper_process_->pid(),
172 base::Bind(&PerfTool::OnQuipperProcessExited, base::Unretained(this)));
173
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000174 // When GetPerfOutputFd() is used to run the perf tool, the user will read
175 // from the read end of |stdout_fd| until the write end is closed. At that
176 // point, it may make another call to GetPerfOutputFd() and expect that will
177 // start another perf run. |stdout_fd| will be closed when the last process
178 // holding it exits, which is minijail0 in this case. However, the kernel
179 // closes fds before signaling process exit. Therefore, it's possible for
180 // |stdout_fd| to be closed and the user tries to run another
181 // GetPerfOutputFd() before we're signaled of the process exit. To mitigate
182 // this, hold on to a dup() of |stdout_fd| until we're signaled that the
183 // process has exited. This guarantees that the caller can make a new
184 // GetPerfOutputFd() call when it finishes reading the output.
185 quipper_process_output_fd_.reset(dup(stdout_fd.get()));
186 DCHECK(quipper_process_output_fd_.is_valid());
187
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800188 // Generate an opaque, pseudo-unique, session ID using time and process ID.
189 profiler_session_id_ = *session_id =
190 static_cast<uint64_t>(base::Time::Now().ToTimeT()) << 32 |
191 (quipper_process_->pid() & 0xffffffff);
192
193 return true;
194}
195
196bool PerfTool::StopPerf(uint64_t session_id, brillo::ErrorPtr* error) {
197 if (!profiler_session_id_) {
198 DEBUGD_ADD_ERROR(error, kStopProcessErrorName, "Perf tool not started");
199 return false;
200 }
201
202 if (profiler_session_id_ != session_id) {
203 // Session ID mismatch: return a failure without affecting the existing
204 // profiler session.
205 DEBUGD_ADD_ERROR(error, kStopProcessErrorName,
206 "Invalid profile session id.");
207 return false;
208 }
209
210 // Stop by sending SIGINT to the profiler session. The sandboxed quipper
211 // process will be reaped in OnQuipperProcessExited().
212 if (quipper_process_) {
213 DCHECK_GT(quipper_process_->pid(), 0);
214 if (kill(quipper_process_->pid(), SIGINT) != 0) {
215 PLOG(WARNING) << "Failed to stop the profiler session.";
216 }
217 }
218
Eric Caruso8fe49c72017-04-25 10:43:59 -0700219 return true;
David Sharp16d35652016-04-05 17:20:08 -0700220}
221
Ben Chana0011d82014-05-13 00:19:29 -0700222} // namespace debugd