blob: a9402b1f623eb9885b5a0d62d5ea0e8f8254d549 [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>
Qijiang Fan713061e2021-03-08 15:45:12 +090012#include <base/check.h>
13#include <base/check_op.h>
Ben Chan51b0c142017-01-06 18:10:27 -080014#include <base/strings/stringprintf.h>
Chinglin Yu0d4368d2018-10-12 13:54:41 +080015#include <base/time/time.h>
David Sharp16d35652016-04-05 17:20:08 -070016
Eric Carusocc7106c2017-04-27 14:22:42 -070017#include "debugd/src/error_utils.h"
Alex Vakulenko262be3f2014-07-30 15:25:50 -070018#include "debugd/src/process_with_output.h"
Ahmad Sharifae1714d2013-01-17 11:29:37 -080019
David Sharpe01f7252015-06-30 16:24:05 -070020namespace debugd {
21
Ahmad Shariff5597f62013-04-25 12:25:41 -070022namespace {
23
David Sharp61901312015-08-05 13:32:07 -070024const char kUnsupportedPerfToolErrorName[] =
25 "org.chromium.debugd.error.UnsupportedPerfTool";
David Sharp16d35652016-04-05 17:20:08 -070026const char kProcessErrorName[] = "org.chromium.debugd.error.RunProcess";
Chinglin Yu0d4368d2018-10-12 13:54:41 +080027const char kStopProcessErrorName[] = "org.chromium.debugd.error.StopProcess";
David Sharp61901312015-08-05 13:32:07 -070028
Tom Hughesd6c2d392020-08-24 18:12:11 -070029const char kArgsError[] =
30 "perf_args must begin with {\"perf\", \"record\"}, "
31 " {\"perf\", \"stat\"}, or {\"perf\", \"mem\"}";
Eric Carusocc7106c2017-04-27 14:22:42 -070032
Ahmad Shariff5597f62013-04-25 12:25:41 -070033// Location of quipper on ChromeOS.
34const char kQuipperLocation[] = "/usr/bin/quipper";
35
Simon Queecb08352015-10-02 17:38:12 -070036enum PerfSubcommand {
37 PERF_COMMAND_RECORD,
38 PERF_COMMAND_STAT,
39 PERF_COMMAND_MEM,
40 PERF_COMMAND_UNSUPPORTED,
41};
42
43// Returns one of the above enums given an vector of perf arguments, starting
44// with "perf" itself in |args[0]|.
45PerfSubcommand GetPerfSubcommandType(const std::vector<std::string>& args) {
46 if (args[0] == "perf" && args.size() > 1) {
47 if (args[1] == "record")
48 return PERF_COMMAND_RECORD;
49 if (args[1] == "stat")
50 return PERF_COMMAND_STAT;
51 if (args[1] == "mem")
52 return PERF_COMMAND_MEM;
53 }
54
55 return PERF_COMMAND_UNSUPPORTED;
56}
57
David Sharp16d35652016-04-05 17:20:08 -070058void AddQuipperArguments(brillo::Process* process,
59 const uint32_t duration_secs,
60 const std::vector<std::string>& perf_args) {
61 process->AddArg(kQuipperLocation);
Eric Caruso96d03d32017-04-25 18:01:17 -070062 process->AddArg(base::StringPrintf("%u", duration_secs));
David Sharp16d35652016-04-05 17:20:08 -070063 for (const auto& arg : perf_args) {
64 process->AddArg(arg);
65 }
66}
67
Ahmad Shariff5597f62013-04-25 12:25:41 -070068} // namespace
69
Chinglin Yu0d4368d2018-10-12 13:54:41 +080070PerfTool::PerfTool() {
71 signal_handler_.Init();
72 process_reaper_.Register(&signal_handler_);
73}
Ahmad Sharifae1714d2013-01-17 11:29:37 -080074
Eric Caruso56eacb22017-08-04 11:00:37 -070075bool PerfTool::GetPerfOutput(uint32_t duration_secs,
76 const std::vector<std::string>& perf_args,
77 std::vector<uint8_t>* perf_data,
78 std::vector<uint8_t>* perf_stat,
79 int32_t* status,
80 brillo::ErrorPtr* error) {
Simon Queecb08352015-10-02 17:38:12 -070081 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
82 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -070083 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso56eacb22017-08-04 11:00:37 -070084 return false;
David Sharp61901312015-08-05 13:32:07 -070085 }
86
Eric Caruso56eacb22017-08-04 11:00:37 -070087 // This whole method is synchronous, so we create a subprocess, let it run to
88 // completion, then gather up its output to return it.
89 ProcessWithOutput process;
90 process.SandboxAs("root", "root");
91 if (!process.Init()) {
Tom Hughesd6c2d392020-08-24 18:12:11 -070092 DEBUGD_ADD_ERROR(error, kProcessErrorName,
93 "Process initialization failure.");
Eric Caruso56eacb22017-08-04 11:00:37 -070094 return false;
95 }
96
97 AddQuipperArguments(&process, duration_secs, perf_args);
98
Simon Queac5f9cf2015-06-20 13:50:22 -070099 std::string output_string;
Eric Caruso56eacb22017-08-04 11:00:37 -0700100 *status = process.Run();
101 if (*status != 0) {
102 output_string =
103 base::StringPrintf("<process exited with status: %d>", *status);
104 } else {
105 process.GetOutput(&output_string);
106 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700107
Simon Queecb08352015-10-02 17:38:12 -0700108 switch (subcommand) {
Tom Hughesd6c2d392020-08-24 18:12:11 -0700109 case PERF_COMMAND_RECORD:
110 case PERF_COMMAND_MEM:
111 perf_data->assign(output_string.begin(), output_string.end());
112 break;
113 case PERF_COMMAND_STAT:
114 perf_stat->assign(output_string.begin(), output_string.end());
115 break;
116 default:
117 // Discard the output.
118 break;
Simon Queecb08352015-10-02 17:38:12 -0700119 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700120
Eric Caruso56eacb22017-08-04 11:00:37 -0700121 return true;
Simon Queac5f9cf2015-06-20 13:50:22 -0700122}
123
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800124void PerfTool::OnQuipperProcessExited(const siginfo_t& siginfo) {
125 // Called after SIGCHLD has been received from the signalfd file descriptor.
126 // Wait() for the child process wont' block. It'll just reap the zombie child
127 // process.
128 quipper_process_->Wait();
129 quipper_process_ = nullptr;
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000130 quipper_process_output_fd_.reset();
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800131
132 profiler_session_id_.reset();
133}
134
Eric Caruso56eacb22017-08-04 11:00:37 -0700135bool PerfTool::GetPerfOutputFd(uint32_t duration_secs,
David Sharp16d35652016-04-05 17:20:08 -0700136 const std::vector<std::string>& perf_args,
Eric Caruso0b241882018-04-04 13:43:46 -0700137 const base::ScopedFD& stdout_fd,
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800138 uint64_t* session_id,
Eric Carusocc7106c2017-04-27 14:22:42 -0700139 brillo::ErrorPtr* error) {
David Sharp16d35652016-04-05 17:20:08 -0700140 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
141 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700142 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso8fe49c72017-04-25 10:43:59 -0700143 return false;
David Sharp16d35652016-04-05 17:20:08 -0700144 }
145
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800146 if (quipper_process_) {
147 // Do not run multiple sessions at the same time. Attempt to start another
148 // profiler session using this method yields a DBus error. Note that
149 // starting another session using GetPerfOutput() will still succeed.
150 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Existing perf tool running.");
151 return false;
152 }
153
154 DCHECK(!profiler_session_id_);
155
156 quipper_process_ = std::make_unique<SandboxedProcess>();
157 quipper_process_->SandboxAs("root", "root");
158 if (!quipper_process_->Init()) {
Tom Hughesd6c2d392020-08-24 18:12:11 -0700159 DEBUGD_ADD_ERROR(error, kProcessErrorName,
160 "Process initialization failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700161 return false;
David Sharp16d35652016-04-05 17:20:08 -0700162 }
163
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800164 AddQuipperArguments(quipper_process_.get(), duration_secs, perf_args);
165 quipper_process_->BindFd(stdout_fd.get(), 1);
David Sharp16d35652016-04-05 17:20:08 -0700166
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800167 if (!quipper_process_->Start()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700168 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Process start failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700169 return false;
David Sharp16d35652016-04-05 17:20:08 -0700170 }
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800171 DCHECK_GT(quipper_process_->pid(), 0);
172
173 process_reaper_.WatchForChild(
174 FROM_HERE, quipper_process_->pid(),
175 base::Bind(&PerfTool::OnQuipperProcessExited, base::Unretained(this)));
176
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000177 // When GetPerfOutputFd() is used to run the perf tool, the user will read
178 // from the read end of |stdout_fd| until the write end is closed. At that
179 // point, it may make another call to GetPerfOutputFd() and expect that will
180 // start another perf run. |stdout_fd| will be closed when the last process
181 // holding it exits, which is minijail0 in this case. However, the kernel
182 // closes fds before signaling process exit. Therefore, it's possible for
183 // |stdout_fd| to be closed and the user tries to run another
184 // GetPerfOutputFd() before we're signaled of the process exit. To mitigate
185 // this, hold on to a dup() of |stdout_fd| until we're signaled that the
186 // process has exited. This guarantees that the caller can make a new
187 // GetPerfOutputFd() call when it finishes reading the output.
188 quipper_process_output_fd_.reset(dup(stdout_fd.get()));
189 DCHECK(quipper_process_output_fd_.is_valid());
190
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800191 // Generate an opaque, pseudo-unique, session ID using time and process ID.
192 profiler_session_id_ = *session_id =
193 static_cast<uint64_t>(base::Time::Now().ToTimeT()) << 32 |
194 (quipper_process_->pid() & 0xffffffff);
195
196 return true;
197}
198
199bool PerfTool::StopPerf(uint64_t session_id, brillo::ErrorPtr* error) {
200 if (!profiler_session_id_) {
201 DEBUGD_ADD_ERROR(error, kStopProcessErrorName, "Perf tool not started");
202 return false;
203 }
204
205 if (profiler_session_id_ != session_id) {
206 // Session ID mismatch: return a failure without affecting the existing
207 // profiler session.
208 DEBUGD_ADD_ERROR(error, kStopProcessErrorName,
209 "Invalid profile session id.");
210 return false;
211 }
212
213 // Stop by sending SIGINT to the profiler session. The sandboxed quipper
214 // process will be reaped in OnQuipperProcessExited().
215 if (quipper_process_) {
216 DCHECK_GT(quipper_process_->pid(), 0);
217 if (kill(quipper_process_->pid(), SIGINT) != 0) {
218 PLOG(WARNING) << "Failed to stop the profiler session.";
219 }
220 }
221
Eric Caruso8fe49c72017-04-25 10:43:59 -0700222 return true;
David Sharp16d35652016-04-05 17:20:08 -0700223}
224
Ben Chana0011d82014-05-13 00:19:29 -0700225} // namespace debugd