Use base::Subprocess in tests and tools
This CL changes the behavior of the cmdline and trigger
e2e tests. Instead of running the "perfetto" and
"trigger_perfetto" commands in a forked environment
(without exec()), exec the real binary.
fork() turns out to be super-fragile because forked
processes inherit the main process pipes that are
used to synchronize the test sequencing. By keeping
one end open, they prevent the main test process to
see the pipe EOF.
Change-Id: Ibc7838ee44fa3a2fa7c4d9fb24066de543a77e19
diff --git a/src/base/test/utils.cc b/src/base/test/utils.cc
index 188038a..24e606a 100644
--- a/src/base/test/utils.cc
+++ b/src/base/test/utils.cc
@@ -29,29 +29,43 @@
#include <unistd.h>
#endif
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+#include <mach-o/dyld.h>
+#endif
+
namespace perfetto {
namespace base {
-std::string GetTestDataPath(const std::string& path) {
+std::string GetCurExecutableDir() {
+ std::string self_path;
+ char buf[PATH_MAX];
#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
- char buf[PATH_MAX];
- ssize_t bytes = readlink("/proc/self/exe", buf, sizeof(buf));
- PERFETTO_CHECK(bytes != -1);
+ ssize_t size = readlink("/proc/self/exe", buf, sizeof(buf));
+ PERFETTO_CHECK(size != -1);
// readlink does not null terminate.
- buf[bytes] = 0;
- std::string self_path = std::string(buf);
+ self_path = std::string(buf, static_cast<size_t>(size));
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+ uint32_t size = sizeof(buf);
+ PERFETTO_CHECK(_NSGetExecutablePath(buf, &size) == 0);
+ self_path = std::string(buf, size);
+#else
+ PERFETTO_FATAL(
+ "GetCurExecutableDir() not implemented on the current platform");
+#endif
// Cut binary name.
- self_path = self_path.substr(0, self_path.find_last_of("/"));
+ return self_path.substr(0, self_path.find_last_of("/"));
+}
+
+std::string GetTestDataPath(const std::string& path) {
+ std::string self_path = GetCurExecutableDir();
std::string full_path = self_path + "/../../" + path;
if (access(full_path.c_str(), F_OK) == 0)
return full_path;
full_path = self_path + "/" + path;
if (access(full_path.c_str(), F_OK) == 0)
return full_path;
-#endif
- // TODO(hjd): Implement on MacOS/Windows
// Fall back to relative to root dir.
return path;
}
diff --git a/src/base/test/utils.h b/src/base/test/utils.h
index 9f61eac..365c93f 100644
--- a/src/base/test/utils.h
+++ b/src/base/test/utils.h
@@ -50,6 +50,7 @@
namespace perfetto {
namespace base {
+std::string GetCurExecutableDir();
std::string GetTestDataPath(const std::string& path);
} // namespace base
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index f7856f4..5a167c1 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -22,6 +22,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/tracing/ipc/default_socket.h"
#include "src/base/test/test_task_runner.h"
#include "src/profiling/memory/heapprofd_producer.h"
@@ -126,19 +127,11 @@
}
}
-pid_t ForkContinuousMalloc(size_t bytes) {
- // Make sure forked process does not get reparented to init.
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0:
- ContinuousMalloc(bytes);
- default:
- break;
- }
- return pid;
+base::Subprocess ForkContinuousMalloc(size_t bytes) {
+ base::Subprocess proc;
+ proc.args.entrypoint_for_testing = [bytes] { ContinuousMalloc(bytes); };
+ proc.Start();
+ return proc;
}
void __attribute__((constructor)) RunContinuousMalloc() {
@@ -309,22 +302,18 @@
}
};
-void KillAssertRunning(pid_t pid) {
- char buf[128];
- PERFETTO_CHECK(snprintf(buf, sizeof(buf), "/proc/%" PRIdMAX,
- static_cast<intmax_t>(pid)) > 0);
- // Assert /proc/<pid> exists.
- struct stat unused;
- PERFETTO_CHECK(stat(buf, &unused) == 0);
- int wstatus;
- PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
- PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, &wstatus, 0)) == pid);
+// This checks that the child is still running (to ensure it didn't crash
+// unxpectedly) and then kills it.
+void KillAssertRunning(base::Subprocess* child) {
+ ASSERT_EQ(child->Poll(), base::Subprocess::kRunning);
+ child->KillAndWaitForTermination();
}
TEST_P(HeapprofdEndToEnd, Smoke) {
constexpr size_t kAllocSize = 1024;
- pid_t pid = ForkContinuousMalloc(kAllocSize);
+ base::Subprocess child = ForkContinuousMalloc(kAllocSize);
+ const auto pid = child.pid();
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
@@ -350,15 +339,17 @@
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, TwoProcesses) {
constexpr size_t kAllocSize = 1024;
constexpr size_t kAllocSize2 = 7;
- pid_t pid = ForkContinuousMalloc(kAllocSize);
- pid_t pid2 = ForkContinuousMalloc(kAllocSize2);
+ base::Subprocess child = ForkContinuousMalloc(kAllocSize);
+ base::Subprocess child2 = ForkContinuousMalloc(kAllocSize2);
+ const auto pid = child.pid();
+ const auto pid2 = child2.pid();
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
@@ -383,14 +374,15 @@
ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid2));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid2), kAllocSize2);
- KillAssertRunning(pid);
- KillAssertRunning(pid2);
+ KillAssertRunning(&child);
+ KillAssertRunning(&child2);
}
TEST_P(HeapprofdEndToEnd, FinalFlush) {
constexpr size_t kAllocSize = 1024;
- pid_t pid = ForkContinuousMalloc(kAllocSize);
+ base::Subprocess child = ForkContinuousMalloc(kAllocSize);
+ const auto pid = child.pid();
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
@@ -413,7 +405,7 @@
ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, NativeStartup) {
@@ -442,32 +434,19 @@
// has received the trace config.
sleep(1);
- // Make sure the forked process does not get reparented to init.
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0: {
- const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
- int null = open("/dev/null", O_RDWR);
- dup2(null, STDIN_FILENO);
- dup2(null, STDOUT_FILENO);
- dup2(null, STDERR_FILENO);
- PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
- nullptr, envp) == 0);
- break;
- }
- default:
- break;
- }
+ base::Subprocess child({"/proc/self/exe"});
+ child.args.argv0_override = "heapprofd_continuous_malloc";
+ child.args.stdout_mode = base::Subprocess::kDevNull;
+ child.args.stderr_mode = base::Subprocess::kDevNull;
+ child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
+ child.Start();
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
@@ -481,7 +460,7 @@
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
- EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
+ EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.pid());
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
@@ -522,32 +501,19 @@
// has received the trace config.
sleep(1);
- // Make sure the forked process does not get reparented to init.
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0: {
- const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
- int null = open("/dev/null", O_RDWR);
- dup2(null, STDIN_FILENO);
- dup2(null, STDOUT_FILENO);
- dup2(null, STDERR_FILENO);
- PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
- nullptr, envp) == 0);
- break;
- }
- default:
- break;
- }
+ base::Subprocess child({"/proc/self/exe"});
+ child.args.argv0_override = "heapprofd_continuous_malloc";
+ child.args.stdout_mode = base::Subprocess::kDevNull;
+ child.args.stderr_mode = base::Subprocess::kDevNull;
+ child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
+ child.Start();
helper->WaitForTracingDisabled(kTracingDisabledTimeoutMs);
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
@@ -561,7 +527,7 @@
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
- EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
+ EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.pid());
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
@@ -593,25 +559,12 @@
heapprofd_config.set_all(false);
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
- // Make sure the forked process does not get reparented to init.
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0: {
- const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
- int null = open("/dev/null", O_RDWR);
- dup2(null, STDIN_FILENO);
- dup2(null, STDOUT_FILENO);
- dup2(null, STDERR_FILENO);
- PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
- nullptr, envp) == 0);
- break;
- }
- default:
- break;
- }
+ base::Subprocess child({"/proc/self/exe"});
+ child.args.argv0_override = "heapprofd_continuous_malloc";
+ child.args.stdout_mode = base::Subprocess::kDevNull;
+ child.args.stderr_mode = base::Subprocess::kDevNull;
+ child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
+ child.Start();
// Wait to make sure process is fully initialized, so we do not accidentally
// match it by the startup logic.
@@ -623,7 +576,7 @@
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
@@ -637,7 +590,7 @@
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
- EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
+ EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.pid());
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
@@ -670,24 +623,12 @@
ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
// Make sure the forked process does not get reparented to init.
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0: {
- const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
- int null = open("/dev/null", O_RDWR);
- dup2(null, STDIN_FILENO);
- dup2(null, STDOUT_FILENO);
- dup2(null, STDERR_FILENO);
- PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
- nullptr, envp) == 0);
- break;
- }
- default:
- break;
- }
+ base::Subprocess child({"/proc/self/exe"});
+ child.args.argv0_override = "heapprofd_continuous_malloc";
+ child.args.stdout_mode = base::Subprocess::kDevNull;
+ child.args.stderr_mode = base::Subprocess::kDevNull;
+ child.args.env.push_back("HEAPPROFD_TESTING_RUN_MALLOC=1");
+ child.Start();
// Wait to make sure process is fully initialized, so we do not accidentally
// match it by the startup logic.
@@ -699,7 +640,7 @@
helper->ReadData();
helper->WaitForReadData(0, kWaitForReadDataTimeoutMs);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
const auto& packets = helper->trace();
ASSERT_GT(packets.size(), 0u);
@@ -713,7 +654,7 @@
const auto& dumps = packet.profile_packet().process_dumps();
ASSERT_EQ(dumps.size(), 1u);
const protos::gen::ProfilePacket_ProcessHeapSamples& dump = dumps[0];
- EXPECT_EQ(static_cast<pid_t>(dump.pid()), pid);
+ EXPECT_EQ(static_cast<pid_t>(dump.pid()), child.pid());
profile_packets++;
for (const auto& sample : dump.samples()) {
samples++;
@@ -735,34 +676,35 @@
base::Pipe signal_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
base::Pipe ack_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
- setsid();
- pid_t pid = fork();
- switch (pid) {
- case -1:
- PERFETTO_FATAL("Failed to fork.");
- case 0: {
- size_t bytes = kFirstIterationBytes;
- signal_pipe.wr.reset();
- ack_pipe.rd.reset();
- for (;;) {
- AllocateAndFree(bytes);
- char buf[1];
- if (bool(signal_pipe.rd) &&
- read(*signal_pipe.rd, buf, sizeof(buf)) == 0) {
- // make sure the client has noticed that the session has stopped
- AllocateAndFree(bytes);
+ base::Subprocess child;
+ int signal_pipe_rd = *signal_pipe.rd;
+ int ack_pipe_wr = *ack_pipe.wr;
+ child.args.preserve_fds.push_back(signal_pipe_rd);
+ child.args.preserve_fds.push_back(ack_pipe_wr);
+ child.args.entrypoint_for_testing = [signal_pipe_rd, ack_pipe_wr] {
+ // The Subprocess harness takes care of closing all the unused pipe ends.
+ size_t bytes = kFirstIterationBytes;
+ bool signalled = false;
+ for (;;) {
+ AllocateAndFree(bytes);
+ char buf[1];
+ if (!signalled && read(signal_pipe_rd, buf, sizeof(buf)) == 1) {
+ signalled = true;
+ PERFETTO_EINTR(close(signal_pipe_rd));
- bytes = kSecondIterationBytes;
- signal_pipe.rd.reset();
- ack_pipe.wr.reset();
- }
- usleep(10 * kMsToUs);
+ // make sure the client has noticed that the session has stopped
+ AllocateAndFree(bytes);
+
+ bytes = kSecondIterationBytes;
+ PERFETTO_CHECK(PERFETTO_EINTR(write(ack_pipe_wr, "1", 1)) == 1);
+ PERFETTO_EINTR(close(ack_pipe_wr));
}
- PERFETTO_FATAL("Should be unreachable");
+ usleep(10 * kMsToUs);
}
- default:
- break;
- }
+ PERFETTO_FATAL("Should be unreachable");
+ };
+ child.Start();
+ auto pid = child.pid();
signal_pipe.rd.reset();
ack_pipe.wr.reset();
@@ -789,9 +731,10 @@
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
kFirstIterationBytes);
+ PERFETTO_CHECK(PERFETTO_EINTR(write(*signal_pipe.wr, "1", 1)) == 1);
signal_pipe.wr.reset();
char buf[1];
- ASSERT_EQ(read(*ack_pipe.rd, buf, sizeof(buf)), 0);
+ ASSERT_EQ(PERFETTO_EINTR(read(*ack_pipe.rd, buf, sizeof(buf))), 1);
ack_pipe.rd.reset();
// A brief sleep to allow the client to notice that the profiling session is
@@ -806,13 +749,14 @@
ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
kSecondIterationBytes);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
constexpr size_t kAllocSize = 1024;
- pid_t pid = ForkContinuousMalloc(kAllocSize);
+ base::Subprocess child = ForkContinuousMalloc(kAllocSize);
+ const auto pid = child.pid();
TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(10 * 1024);
@@ -856,18 +800,20 @@
ValidateRejectedConcurrent(helper_concurrent.get(),
static_cast<uint64_t>(pid), true);
- KillAssertRunning(pid);
+ KillAssertRunning(&child);
}
TEST_P(HeapprofdEndToEnd, NativeProfilingActiveAtProcessExit) {
constexpr uint64_t kTestAllocSize = 128;
base::Pipe start_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
+ base::Subprocess child;
+ int start_pipe_wr = *start_pipe.wr;
+ child.args.preserve_fds.push_back(start_pipe_wr);
+ child.args.entrypoint_for_testing = [start_pipe_wr] {
+ PERFETTO_CHECK(PERFETTO_EINTR(write(start_pipe_wr, "1", 1)) == 1);
+ PERFETTO_CHECK(PERFETTO_EINTR(close(start_pipe_wr)) == 0);
- setsid();
- pid_t pid = fork();
- if (pid == 0) { // child
- start_pipe.rd.reset();
- start_pipe.wr.reset();
+ // The subprocess harness will take care of closing
for (int i = 0; i < 200; i++) {
// malloc and leak, otherwise the free batching will cause us to filter
// out the allocations (as we don't see the interleaved frees).
@@ -877,10 +823,9 @@
}
usleep(10 * kMsToUs);
}
- exit(0);
- }
-
- ASSERT_NE(pid, -1) << "Failed to fork.";
+ };
+ child.Start();
+ auto pid = child.pid();
start_pipe.wr.reset();
// Construct tracing config (without starting profiling).
@@ -900,20 +845,16 @@
// Wait for child to have been scheduled at least once.
char buf[1] = {};
- ASSERT_EQ(PERFETTO_EINTR(read(*start_pipe.rd, buf, sizeof(buf))), 0);
+ ASSERT_EQ(PERFETTO_EINTR(read(*start_pipe.rd, buf, sizeof(buf))), 1);
start_pipe.rd.reset();
// Trace until child exits.
helper->StartTracing(trace_config);
- siginfo_t siginfo = {};
- int wait_ret =
- PERFETTO_EINTR(waitid(P_PID, static_cast<id_t>(pid), &siginfo, WEXITED));
- ASSERT_FALSE(wait_ret) << "Failed to waitid.";
-
- // Assert that the child exited successfully.
- EXPECT_EQ(siginfo.si_code, CLD_EXITED) << "Child did not exit by itself.";
- EXPECT_EQ(siginfo.si_status, 0) << "Child's exit status not successful.";
+ // Wait for the child and assert that it exited successfully.
+ EXPECT_TRUE(child.Wait(30000));
+ EXPECT_EQ(child.status(), base::Subprocess::kExited);
+ EXPECT_EQ(child.returncode(), 0);
// Assert that we did profile the process.
helper->FlushAndWait(2000);
diff --git a/test/BUILD.gn b/test/BUILD.gn
index f9629b3..771e940 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -35,6 +35,14 @@
"../src/base:test_support",
"../src/traced/probes/ftrace",
]
+
+ # These binaries are requires by the cmdline tests, which invoke perfetto
+ # and trigger_perfetto via Subprocess.
+ data_deps = [
+ "../src/perfetto_cmd:perfetto",
+ "../src/perfetto_cmd:trigger_perfetto",
+ ]
+
sources = [ "end_to_end_integrationtest.cc" ]
if (start_daemons_for_testing) {
cflags = [ "-DPERFETTO_START_DAEMONS_FOR_TESTING" ]
diff --git a/test/end_to_end_integrationtest.cc b/test/end_to_end_integrationtest.cc
index 7dc37b1..4af7896 100644
--- a/test/end_to_end_integrationtest.cc
+++ b/test/end_to_end_integrationtest.cc
@@ -26,12 +26,15 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/subprocess.h"
#include "perfetto/ext/base/temp_file.h"
#include "perfetto/ext/traced/traced.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/ext/tracing/ipc/default_socket.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "src/base/test/test_task_runner.h"
+#include "src/base/test/utils.h"
#include "src/traced/probes/ftrace/ftrace_controller.h"
#include "src/traced/probes/ftrace/ftrace_procfs.h"
#include "test/gtest_and_gmock.h"
@@ -82,181 +85,90 @@
// any necessary threads) in the parent process is complete.
class Exec {
public:
- // Starts the forked process that was created. If not null then |stderr_out
- // will contain the std::cerr output of the process.
+ // Starts the forked process that was created. If not null then |stderr_out|
+ // will contain the stderr of the process.
int Run(std::string* stderr_out = nullptr) {
// We can't be the child process.
- PERFETTO_CHECK(pid_ != 0);
+ PERFETTO_CHECK(getpid() != subprocess_.pid());
+ // Will cause the entrypoint to continue.
+ PERFETTO_CHECK(write(*sync_pipe_.wr, "1", 1) == 1);
+ sync_pipe_.wr.reset();
+ subprocess_.Wait();
- // Send some random bytes so the child process knows the service is up and
- // it can connect and execute.
- PERFETTO_CHECK(PERFETTO_EINTR(write(*start_pipe_.wr, "42", 2)) ==
- static_cast<ssize_t>(2));
- start_pipe_.wr.reset();
-
- // Setup a large enough buffer and read all of stderr (until the process
- // closes the err_pipe on process exit).
- std::string stderr_str = std::string(1024 * 1024, '\0');
- ssize_t rsize = 0;
- size_t stderr_pos = 0;
- while (stderr_pos < stderr_str.size()) {
- rsize = PERFETTO_EINTR(read(*err_pipe_.rd, &stderr_str[stderr_pos],
- stderr_str.size() - stderr_pos - 1));
- if (rsize <= 0)
- break;
- stderr_pos += static_cast<size_t>(rsize);
- }
- stderr_str.resize(stderr_pos);
-
- // Either output the stderr_out to the provided variable or for the record
- // it into the info logs.
if (stderr_out) {
- *stderr_out = stderr_str;
+ *stderr_out = std::move(subprocess_.output());
} else {
- PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"", pid_,
- stderr_str.c_str());
+ PERFETTO_LOG("Child proc %d exited with stderr: \"%s\"",
+ subprocess_.pid(), subprocess_.output().c_str());
}
-
- int status = 1;
- PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid_, &status, 0)) == pid_);
- int exit_code;
- if (WIFEXITED(status)) {
- exit_code = WEXITSTATUS(status);
- } else if (WIFSIGNALED(status)) {
- exit_code = -(WTERMSIG(status));
- PERFETTO_CHECK(exit_code < 0);
- } else {
- PERFETTO_FATAL("Unexpected exit status: %d", status);
- }
- return exit_code;
+ return subprocess_.returncode();
}
private:
- Exec(pid_t pid, base::Pipe err, base::Pipe start)
- : pid_(pid), err_pipe_(std::move(err)), start_pipe_(std::move(start)) {}
+ Exec(const std::string& argv0,
+ std::initializer_list<std::string> args,
+ std::string input = "") {
+ subprocess_.args.stderr_mode = base::Subprocess::kBuffer;
+ subprocess_.args.stdout_mode = base::Subprocess::kDevNull;
+ subprocess_.args.input = input;
- static Exec Create(const std::string& argv0,
- std::initializer_list<std::string> args,
- std::string input = "") {
- if (argv0 != "perfetto" && argv0 != "trigger_perfetto") {
- PERFETTO_FATAL(
- "Received argv0: \"%s\" which isn't supported. Supported binaries "
- "are \"perfetto\" or \"trigger_perfetto\".",
- argv0.c_str());
- }
-
- // |in_pipe| == std::cin, |err_pipe| == std::cerr for the process we're
- // about to fork. |start_pipe| is used to block the process so we can hold
- // it until we're ready (the service has started up).
- base::Pipe in_pipe = base::Pipe::Create();
- base::Pipe err_pipe = base::Pipe::Create();
- base::Pipe start_pipe = base::Pipe::Create();
-
- pid_t pid = fork();
- PERFETTO_CHECK(pid >= 0);
- if (pid == 0) {
- // Child process, we need to block the child process until we've been
- // signaled on the |start_pipe|.
- std::string junk = std::string(4, '\0');
- start_pipe.wr.reset();
- ssize_t rsize = 0;
- rsize = PERFETTO_EINTR(read(*start_pipe.rd, &junk[0], junk.size() - 1));
- PERFETTO_CHECK(rsize >= 0);
- start_pipe.rd.reset();
-
- // We've been signalled to start so execute in a sub function.
- _exit(RunChild(argv0, std::move(args), std::move(in_pipe),
- std::move(err_pipe)));
- } else {
- // Parent, we don't need to write to the childs std::cerr nor do we need
- // to read the start_pipe.
- err_pipe.wr.reset();
- start_pipe.rd.reset();
-
- // This is generally an unsafe pattern because the child process might
- // be blocked on stdout and stall the stdin reads. It's pragmatically
- // okay for our test cases because stdin is not expected to exceed the
- // pipe buffer.
- //
- // We need to write this now up front (rather than in Run(), because in
- // some tests we create multiple Exec classes, and if we don't close the
- // input pipe up front then future Exec's will have a reference and the
- // pipe won't close properly.
- PERFETTO_CHECK(input.size() <= base::kPageSize);
- PERFETTO_CHECK(
- PERFETTO_EINTR(write(*in_pipe.wr, input.data(), input.size())) ==
- static_cast<ssize_t>(input.size()));
- in_pipe.wr.reset();
- // Close the input pipe only after the write so we don't get an EPIPE
- // signal in the cases when the child process earlies out without
- // reading stdin.
- in_pipe.rd.reset();
-
- return Exec(pid, std::move(err_pipe), std::move(start_pipe));
- }
- }
-
- // Wrapper to contain all the work the child process needs to do.
- static int RunChild(const std::string& argv0,
- std::initializer_list<std::string> args,
- base::Pipe in_pipe,
- base::Pipe err_pipe) {
- // This sets up the char** argv buffer we're going to provide to the main
- // function for |argv0| binary.
- std::vector<char> argv_buffer;
- std::vector<size_t> argv_offsets;
- std::vector<char*> argv;
- argv_offsets.push_back(0);
-
- argv_buffer.insert(argv_buffer.end(), argv0.begin(), argv0.end());
- argv_buffer.push_back('\0');
-
- for (const std::string& arg : args) {
- argv_offsets.push_back(argv_buffer.size());
- argv_buffer.insert(argv_buffer.end(), arg.begin(), arg.end());
- argv_buffer.push_back('\0');
- }
-
- for (size_t off : argv_offsets)
- argv.push_back(&argv_buffer[off]);
- argv.push_back(nullptr);
-
- // We aren't reading std::cerr nor writing to std::cin.
- err_pipe.rd.reset();
- in_pipe.wr.reset();
-
- // This makes it so the binaries below will correctly write their std::cin
- // and std::cerr to the right pipes.
- int devnull = open("/dev/null", O_RDWR);
- PERFETTO_CHECK(devnull >= 0);
- PERFETTO_CHECK(dup2(*in_pipe.rd, STDIN_FILENO) != -1);
- PERFETTO_CHECK(dup2(devnull, STDOUT_FILENO) != -1);
- PERFETTO_CHECK(dup2(*err_pipe.wr, STDERR_FILENO) != -1);
#if PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS)
- setenv("PERFETTO_CONSUMER_SOCK_NAME", TestHelper::GetConsumerSocketName(),
- 1);
- setenv("PERFETTO_PRODUCER_SOCK_NAME", TestHelper::GetProducerSocketName(),
- 1);
- if (argv0 == "perfetto") {
- return PerfettoCmdMain(static_cast<int>(argv.size() - 1), argv.data());
- } else if (argv0 == "trigger_perfetto") {
- return TriggerPerfettoMain(static_cast<int>(argv.size() - 1),
- argv.data());
- } else {
- PERFETTO_FATAL("Unknown binary: %s", argv0.c_str());
- return 4;
- }
+ constexpr bool kUseSystemBinaries = false;
#else
- execv((std::string("/system/bin/") + argv0).c_str(), &argv[0]);
- return 3;
+ constexpr bool kUseSystemBinaries = true;
#endif
+
+ std::vector<std::string>& cmd = subprocess_.args.exec_cmd;
+ if (kUseSystemBinaries) {
+ cmd.push_back("/system/bin/" + argv0);
+ cmd.insert(cmd.end(), args.begin(), args.end());
+ } else {
+ subprocess_.args.env.push_back(
+ std::string("PERFETTO_PRODUCER_SOCK_NAME=") +
+ TestHelper::GetProducerSocketName());
+ subprocess_.args.env.push_back(
+ std::string("PERFETTO_CONSUMER_SOCK_NAME=") +
+ TestHelper::GetConsumerSocketName());
+ cmd.push_back(base::GetCurExecutableDir() + "/" + argv0);
+ cmd.insert(cmd.end(), args.begin(), args.end());
+ }
+
+ if (access(cmd[0].c_str(), F_OK)) {
+ PERFETTO_FATAL(
+ "Cannot find %s. Make sure that the target has been built and, on "
+ "Android, pushed to the device.",
+ cmd[0].c_str());
+ }
+
+ // This pipe blocks the execution of the child process until the main test
+ // process calls Run(). There are two conflicting problems here:
+ // 1) We can't fork() subprocesses too late, because the test spawns threads
+ // for hosting the service. fork+threads = bad (see aosp/1089744).
+ // 2) We can't run the subprocess too early, because we need to wait that
+ // the service threads are ready before trying to connect from the child
+ // process.
+ sync_pipe_ = base::Pipe::Create();
+ int sync_pipe_rd = *sync_pipe_.rd;
+ subprocess_.args.preserve_fds.push_back(sync_pipe_rd);
+
+ // This lambda will be called on the forked child process after having
+ // setup pipe redirection and closed all FDs, right before the exec().
+ // The Subprocesss harness will take care of closing also |sync_pipe_.wr|.
+ subprocess_.args.entrypoint_for_testing = [sync_pipe_rd] {
+ // Don't add any logging here, all file descriptors are closed and trying
+ // to log will likely cause undefined behaviors.
+ char ignored = 0;
+ PERFETTO_CHECK(PERFETTO_EINTR(read(sync_pipe_rd, &ignored, 1)) > 0);
+ PERFETTO_CHECK(PERFETTO_EINTR(close(sync_pipe_rd)) == 0);
+ };
+
+ subprocess_.Start();
+ sync_pipe_.rd.reset();
}
friend class PerfettoCmdlineTest;
-
- pid_t pid_;
- base::Pipe err_pipe_;
- base::Pipe start_pipe_;
+ base::Subprocess subprocess_;
+ base::Pipe sync_pipe_;
};
class PerfettoTest : public ::testing::Test {
@@ -304,7 +216,7 @@
// You can not fork after you've started the service due to risk of
// deadlocks.
PERFETTO_CHECK(exec_allowed_);
- return Exec::Create("perfetto", std::move(args), std::move(std_in));
+ return Exec("perfetto", std::move(args), std::move(std_in));
}
// Creates a process that represents the trigger_perfetto binary that will
@@ -315,7 +227,7 @@
// You can not fork after you've started the service due to risk of
// deadlocks.
PERFETTO_CHECK(exec_allowed_);
- return Exec::Create("trigger_perfetto", std::move(args), std::move(std_in));
+ return Exec("trigger_perfetto", std::move(args), std::move(std_in));
}
// Tests are allowed to freely use these variables.
diff --git a/tools/ftrace_proto_gen/proto_gen_utils.cc b/tools/ftrace_proto_gen/proto_gen_utils.cc
index 6fa9e3f..ba29c72 100644
--- a/tools/ftrace_proto_gen/proto_gen_utils.cc
+++ b/tools/ftrace_proto_gen/proto_gen_utils.cc
@@ -28,65 +28,24 @@
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/subprocess.h"
namespace perfetto {
namespace {
std::string RunClangFmt(const std::string& input) {
- std::string output;
- pid_t pid;
- base::Pipe input_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
- base::Pipe output_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
- if ((pid = fork()) == 0) {
- // Child
- PERFETTO_CHECK(dup2(*input_pipe.rd, STDIN_FILENO) != -1);
- PERFETTO_CHECK(dup2(*output_pipe.wr, STDOUT_FILENO) != -1);
- input_pipe.wr.reset();
- output_pipe.rd.reset();
- PERFETTO_CHECK(execl("buildtools/linux64/clang-format", "clang-format",
- nullptr) != -1);
- }
- PERFETTO_CHECK(pid > 0);
- // Parent
- size_t written = 0;
- size_t bytes_read = 0;
- input_pipe.rd.reset();
- output_pipe.wr.reset();
- // This cannot be left uninitialized because there's as continue statement
- // before the first assignment to this in the loop.
- ssize_t r = -1;
- do {
- if (written < input.size()) {
- ssize_t w =
- write(*input_pipe.wr, &(input[written]), input.size() - written);
- if (w == -1) {
- if (errno == EAGAIN || errno == EINTR)
- continue;
- PERFETTO_FATAL("write failed");
- }
- written += static_cast<size_t>(w);
- if (written == input.size())
- input_pipe.wr.reset();
- }
-
- if (bytes_read + base::kPageSize > output.size())
- output.resize(output.size() + base::kPageSize);
- r = read(*output_pipe.rd, &(output[bytes_read]), base::kPageSize);
- if (r == -1) {
- if (errno == EAGAIN || errno == EINTR)
- continue;
- PERFETTO_FATAL("read failed");
- }
- if (r > 0)
- bytes_read += static_cast<size_t>(r);
- } while (r != 0);
- output.resize(bytes_read);
-
- int wstatus;
- waitpid(pid, &wstatus, 0);
- PERFETTO_CHECK(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0);
- return output;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_MACOSX)
+ const std::string platform = "mac";
+#else
+ const std::string platform = "linux64";
+#endif
+ base::Subprocess clang_fmt({"buildtools/" + platform + "/clang-format"});
+ clang_fmt.args.stdout_mode = base::Subprocess::kBuffer;
+ clang_fmt.args.stderr_mode = base::Subprocess::kInherit;
+ clang_fmt.args.input = input;
+ PERFETTO_CHECK(clang_fmt.Call());
+ return std::move(clang_fmt.output());
}
} // namespace
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 95a4c39..7b7b1ee 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -724,6 +724,8 @@
module.generated_headers.update(dep_module.genrule_headers)
module.srcs.update(dep_module.genrule_srcs)
module.shared_libs.update(dep_module.genrule_shared_libs)
+ elif dep_module.type == 'cc_binary':
+ continue # Ignore executables deps (used by cmdline integration tests).
else:
raise Error('Unknown dep %s (%s) for target %s' %
(dep_module.name, dep_module.type, module.name))
diff --git a/tools/run_android_test b/tools/run_android_test
index 27fd273..4707796 100755
--- a/tools/run_android_test
+++ b/tools/run_android_test
@@ -90,6 +90,8 @@
def AdbPush(host, device):
+ if not os.path.exists(host):
+ logging.fatal('Cannot find %s. Was it built?', host)
cmd = [ADB_PATH, 'push', host, device]
print '> adb push ' + ' '.join(cmd[2:])
with open(os.devnull) as devnull:
@@ -154,6 +156,10 @@
# See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt.
AdbPush(test_bin, "/data/nativetest")
+ # These two binaries are required to run perfetto_integrationtests.
+ AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
+ AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
+
if not args.no_data_deps:
for dep in EnumerateDataDeps():
AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)