blob: d7e5fd2d3ff5ee29168b52beca76ce37daa81703 [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
Tom Hughesd6c2d392020-08-24 18:12:11 -070027const char kArgsError[] =
28 "perf_args must begin with {\"perf\", \"record\"}, "
29 " {\"perf\", \"stat\"}, or {\"perf\", \"mem\"}";
Eric Carusocc7106c2017-04-27 14:22:42 -070030
Ahmad Shariff5597f62013-04-25 12:25:41 -070031// Location of quipper on ChromeOS.
32const char kQuipperLocation[] = "/usr/bin/quipper";
33
Simon Queecb08352015-10-02 17:38:12 -070034enum PerfSubcommand {
35 PERF_COMMAND_RECORD,
36 PERF_COMMAND_STAT,
37 PERF_COMMAND_MEM,
38 PERF_COMMAND_UNSUPPORTED,
39};
40
41// Returns one of the above enums given an vector of perf arguments, starting
42// with "perf" itself in |args[0]|.
43PerfSubcommand GetPerfSubcommandType(const std::vector<std::string>& args) {
44 if (args[0] == "perf" && args.size() > 1) {
45 if (args[1] == "record")
46 return PERF_COMMAND_RECORD;
47 if (args[1] == "stat")
48 return PERF_COMMAND_STAT;
49 if (args[1] == "mem")
50 return PERF_COMMAND_MEM;
51 }
52
53 return PERF_COMMAND_UNSUPPORTED;
54}
55
David Sharp16d35652016-04-05 17:20:08 -070056void AddQuipperArguments(brillo::Process* process,
57 const uint32_t duration_secs,
58 const std::vector<std::string>& perf_args) {
59 process->AddArg(kQuipperLocation);
Eric Caruso96d03d32017-04-25 18:01:17 -070060 process->AddArg(base::StringPrintf("%u", duration_secs));
David Sharp16d35652016-04-05 17:20:08 -070061 for (const auto& arg : perf_args) {
62 process->AddArg(arg);
63 }
64}
65
Ahmad Shariff5597f62013-04-25 12:25:41 -070066} // namespace
67
Chinglin Yu0d4368d2018-10-12 13:54:41 +080068PerfTool::PerfTool() {
69 signal_handler_.Init();
70 process_reaper_.Register(&signal_handler_);
71}
Ahmad Sharifae1714d2013-01-17 11:29:37 -080072
Eric Caruso56eacb22017-08-04 11:00:37 -070073bool PerfTool::GetPerfOutput(uint32_t duration_secs,
74 const std::vector<std::string>& perf_args,
75 std::vector<uint8_t>* perf_data,
76 std::vector<uint8_t>* perf_stat,
77 int32_t* status,
78 brillo::ErrorPtr* error) {
Simon Queecb08352015-10-02 17:38:12 -070079 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
80 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -070081 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso56eacb22017-08-04 11:00:37 -070082 return false;
David Sharp61901312015-08-05 13:32:07 -070083 }
84
Eric Caruso56eacb22017-08-04 11:00:37 -070085 // This whole method is synchronous, so we create a subprocess, let it run to
86 // completion, then gather up its output to return it.
87 ProcessWithOutput process;
88 process.SandboxAs("root", "root");
89 if (!process.Init()) {
Tom Hughesd6c2d392020-08-24 18:12:11 -070090 DEBUGD_ADD_ERROR(error, kProcessErrorName,
91 "Process initialization failure.");
Eric Caruso56eacb22017-08-04 11:00:37 -070092 return false;
93 }
94
95 AddQuipperArguments(&process, duration_secs, perf_args);
96
Simon Queac5f9cf2015-06-20 13:50:22 -070097 std::string output_string;
Eric Caruso56eacb22017-08-04 11:00:37 -070098 *status = process.Run();
99 if (*status != 0) {
100 output_string =
101 base::StringPrintf("<process exited with status: %d>", *status);
102 } else {
103 process.GetOutput(&output_string);
104 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700105
Simon Queecb08352015-10-02 17:38:12 -0700106 switch (subcommand) {
Tom Hughesd6c2d392020-08-24 18:12:11 -0700107 case PERF_COMMAND_RECORD:
108 case PERF_COMMAND_MEM:
109 perf_data->assign(output_string.begin(), output_string.end());
110 break;
111 case PERF_COMMAND_STAT:
112 perf_stat->assign(output_string.begin(), output_string.end());
113 break;
114 default:
115 // Discard the output.
116 break;
Simon Queecb08352015-10-02 17:38:12 -0700117 }
Simon Queac5f9cf2015-06-20 13:50:22 -0700118
Eric Caruso56eacb22017-08-04 11:00:37 -0700119 return true;
Simon Queac5f9cf2015-06-20 13:50:22 -0700120}
121
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800122void PerfTool::OnQuipperProcessExited(const siginfo_t& siginfo) {
123 // Called after SIGCHLD has been received from the signalfd file descriptor.
124 // Wait() for the child process wont' block. It'll just reap the zombie child
125 // process.
126 quipper_process_->Wait();
127 quipper_process_ = nullptr;
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000128 quipper_process_output_fd_.reset();
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800129
130 profiler_session_id_.reset();
131}
132
Eric Caruso56eacb22017-08-04 11:00:37 -0700133bool PerfTool::GetPerfOutputFd(uint32_t duration_secs,
David Sharp16d35652016-04-05 17:20:08 -0700134 const std::vector<std::string>& perf_args,
Eric Caruso0b241882018-04-04 13:43:46 -0700135 const base::ScopedFD& stdout_fd,
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800136 uint64_t* session_id,
Eric Carusocc7106c2017-04-27 14:22:42 -0700137 brillo::ErrorPtr* error) {
David Sharp16d35652016-04-05 17:20:08 -0700138 PerfSubcommand subcommand = GetPerfSubcommandType(perf_args);
139 if (subcommand == PERF_COMMAND_UNSUPPORTED) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700140 DEBUGD_ADD_ERROR(error, kUnsupportedPerfToolErrorName, kArgsError);
Eric Caruso8fe49c72017-04-25 10:43:59 -0700141 return false;
David Sharp16d35652016-04-05 17:20:08 -0700142 }
143
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800144 if (quipper_process_) {
145 // Do not run multiple sessions at the same time. Attempt to start another
146 // profiler session using this method yields a DBus error. Note that
147 // starting another session using GetPerfOutput() will still succeed.
148 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Existing perf tool running.");
149 return false;
150 }
151
152 DCHECK(!profiler_session_id_);
153
154 quipper_process_ = std::make_unique<SandboxedProcess>();
155 quipper_process_->SandboxAs("root", "root");
156 if (!quipper_process_->Init()) {
Tom Hughesd6c2d392020-08-24 18:12:11 -0700157 DEBUGD_ADD_ERROR(error, kProcessErrorName,
158 "Process initialization failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700159 return false;
David Sharp16d35652016-04-05 17:20:08 -0700160 }
161
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800162 AddQuipperArguments(quipper_process_.get(), duration_secs, perf_args);
163 quipper_process_->BindFd(stdout_fd.get(), 1);
David Sharp16d35652016-04-05 17:20:08 -0700164
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800165 if (!quipper_process_->Start()) {
Eric Carusocc7106c2017-04-27 14:22:42 -0700166 DEBUGD_ADD_ERROR(error, kProcessErrorName, "Process start failure.");
Eric Caruso8fe49c72017-04-25 10:43:59 -0700167 return false;
David Sharp16d35652016-04-05 17:20:08 -0700168 }
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800169 DCHECK_GT(quipper_process_->pid(), 0);
170
171 process_reaper_.WatchForChild(
172 FROM_HERE, quipper_process_->pid(),
173 base::Bind(&PerfTool::OnQuipperProcessExited, base::Unretained(this)));
174
Anand K Mistrya8b97f92020-04-24 10:32:18 +1000175 // When GetPerfOutputFd() is used to run the perf tool, the user will read
176 // from the read end of |stdout_fd| until the write end is closed. At that
177 // point, it may make another call to GetPerfOutputFd() and expect that will
178 // start another perf run. |stdout_fd| will be closed when the last process
179 // holding it exits, which is minijail0 in this case. However, the kernel
180 // closes fds before signaling process exit. Therefore, it's possible for
181 // |stdout_fd| to be closed and the user tries to run another
182 // GetPerfOutputFd() before we're signaled of the process exit. To mitigate
183 // this, hold on to a dup() of |stdout_fd| until we're signaled that the
184 // process has exited. This guarantees that the caller can make a new
185 // GetPerfOutputFd() call when it finishes reading the output.
186 quipper_process_output_fd_.reset(dup(stdout_fd.get()));
187 DCHECK(quipper_process_output_fd_.is_valid());
188
Chinglin Yu0d4368d2018-10-12 13:54:41 +0800189 // Generate an opaque, pseudo-unique, session ID using time and process ID.
190 profiler_session_id_ = *session_id =
191 static_cast<uint64_t>(base::Time::Now().ToTimeT()) << 32 |
192 (quipper_process_->pid() & 0xffffffff);
193
194 return true;
195}
196
197bool PerfTool::StopPerf(uint64_t session_id, brillo::ErrorPtr* error) {
198 if (!profiler_session_id_) {
199 DEBUGD_ADD_ERROR(error, kStopProcessErrorName, "Perf tool not started");
200 return false;
201 }
202
203 if (profiler_session_id_ != session_id) {
204 // Session ID mismatch: return a failure without affecting the existing
205 // profiler session.
206 DEBUGD_ADD_ERROR(error, kStopProcessErrorName,
207 "Invalid profile session id.");
208 return false;
209 }
210
211 // Stop by sending SIGINT to the profiler session. The sandboxed quipper
212 // process will be reaped in OnQuipperProcessExited().
213 if (quipper_process_) {
214 DCHECK_GT(quipper_process_->pid(), 0);
215 if (kill(quipper_process_->pid(), SIGINT) != 0) {
216 PLOG(WARNING) << "Failed to stop the profiler session.";
217 }
218 }
219
Eric Caruso8fe49c72017-04-25 10:43:59 -0700220 return true;
David Sharp16d35652016-04-05 17:20:08 -0700221}
222
Ben Chana0011d82014-05-13 00:19:29 -0700223} // namespace debugd