blob: b9562bf0b6a95d1c80166aef8c07e4e441b1f9ae [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "src/profiling/memory/client_api_factory.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/base/unix_task_runner.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/base/watchdog.h"
#include "perfetto/ext/tracing/ipc/default_socket.h"
#include "perfetto/profiling/memory/heap_profile.h"
#include "src/profiling/common/proc_utils.h"
#include "src/profiling/memory/heapprofd_producer.h"
#include <string>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
// General approach:
// On loading this library, we fork off a process that runs heapprofd. We
// share a control socket pair (g_child_sock in client, srv_sock in service)
// which is used to:
// * Signal a new profiling session was started by sending a byte to
// g_child_sock. This signal gets received in MonitorFd.
// * For each profiling session, send a new socket from the client to the
// service. This happens in CreateClient.
namespace perfetto {
namespace profiling {
namespace {
base::UnixSocketRaw* g_client_sock;
void MonitorFd() {
PERFETTO_DCHECK(g_client_sock->IsBlocking());
for (;;) {
char buf[1];
ssize_t r = g_client_sock->Receive(buf, sizeof(buf));
if (r >= 1) {
AHeapProfile_initSession(malloc, free);
} else if (r == 0) {
PERFETTO_ELOG("Server disconneced.");
break;
} else {
PERFETTO_PLOG("Receive failed.");
}
}
}
} // namespace
void StartHeapprofdIfStatic() {
pid_t pid = getpid();
std::string cmdline;
if (!GetCmdlineForPID(pid, &cmdline)) {
PERFETTO_ELOG("Failed to get cmdline.");
}
g_client_sock = new base::UnixSocketRaw();
base::UnixSocketRaw srv_sock;
base::UnixSocketRaw cli_sock;
std::tie(cli_sock, srv_sock) = base::UnixSocketRaw::CreatePair(
base::SockFamily::kUnix, base::SockType::kStream);
if (!cli_sock || !srv_sock) {
PERFETTO_ELOG("Failed to create socket pair.");
return;
}
pid_t f = fork();
if (f == -1) {
PERFETTO_PLOG("fork");
return;
}
if (f != 0) {
int wstatus;
if (PERFETTO_EINTR(waitpid(f, &wstatus, 0)) == -1)
PERFETTO_PLOG("waitpid");
*g_client_sock = std::move(cli_sock);
std::thread th(MonitorFd);
th.detach();
return;
}
daemon(/* nochdir= */ 0, /* noclose= */ 1);
cli_sock.ReleaseFd();
// Leave stderr open for logging.
int null = open("/dev/null", O_RDWR); // NOLINT(android-cloexec-open)
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
if (null > STDERR_FILENO)
close(null);
for (int i = STDERR_FILENO + 1; i < 512; ++i) {
if (i != srv_sock.fd())
close(i);
}
srv_sock.SetBlocking(false);
base::UnixTaskRunner task_runner;
base::Watchdog::GetInstance()->Start(); // crash on exceedingly long tasks
HeapprofdProducer producer(HeapprofdMode::kChild, &task_runner,
/* exit_when_done= */ false);
producer.SetTargetProcess(pid, cmdline);
producer.ConnectWithRetries(GetProducerSocket());
// Signal MonitorFd in the parent process to start a session.
producer.SetDataSourceCallback([&srv_sock] { srv_sock.Send("x", 1); });
task_runner.AddFileDescriptorWatch(
srv_sock.fd(), [&task_runner, &producer, &srv_sock] {
base::ScopedFile fd;
char buf[1];
ssize_t r = srv_sock.Receive(buf, sizeof(buf), &fd, 1);
if (r == 0) {
PERFETTO_LOG("Child disconnected.");
producer.TerminateWhenDone();
task_runner.RemoveFileDescriptorWatch(srv_sock.fd());
}
if (r == -1 && !base::IsAgain(errno)) {
PERFETTO_PLOG("Receive");
}
if (fd) {
producer.AdoptSocket(std::move(fd));
}
});
task_runner.Run();
}
// This is called by AHeapProfile_initSession (client_api.cc) to construct a
// client.
std::shared_ptr<Client> ConstructClient(
UnhookedAllocator<perfetto::profiling::Client> unhooked_allocator) {
if (!g_client_sock)
return nullptr;
std::shared_ptr<perfetto::profiling::Client> client;
base::UnixSocketRaw srv_session_sock;
base::UnixSocketRaw client_session_sock;
std::tie(srv_session_sock, client_session_sock) =
base::UnixSocketRaw::CreatePair(base::SockFamily::kUnix,
base::SockType::kStream);
if (!client_session_sock || !srv_session_sock) {
PERFETTO_ELOG("Failed to create socket pair.");
return nullptr;
}
base::ScopedFile srv_fd = srv_session_sock.ReleaseFd();
int fd = srv_fd.get();
// Send the session socket to the service.
g_client_sock->Send(" ", 1, &fd, 1);
return perfetto::profiling::Client::CreateAndHandshake(
std::move(client_session_sock), unhooked_allocator);
}
} // namespace profiling
} // namespace perfetto