ladybird/Userland/Libraries/LibWebView/ProcessManager.cpp
Timothy Flynn 5dd3b91f0e LibCore+LibWebView: Move process statistics to LibCore
This will be needed to collect statistics from processes that do not
have anything to do with LibWebView. The ProcessInfo structure must be
virtual to allow callers to add application-specific information.
2024-04-22 14:46:10 -06:00

235 lines
6.7 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/NumberFormat.h>
#include <AK/String.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
#include <LibWebView/ProcessManager.h>
namespace WebView {
static sig_atomic_t s_received_sigchld = 0;
ProcessType process_type_from_name(StringView name)
{
if (name == "Chrome"sv)
return ProcessType::Chrome;
if (name == "WebContent"sv)
return ProcessType::WebContent;
if (name == "WebWorker"sv)
return ProcessType::WebWorker;
if (name == "SQLServer"sv)
return ProcessType::SQLServer;
if (name == "RequestServer"sv)
return ProcessType::RequestServer;
if (name == "ImageDecoder"sv)
return ProcessType::ImageDecoder;
dbgln("Unknown process type: '{}'", name);
VERIFY_NOT_REACHED();
}
StringView process_name_from_type(ProcessType type)
{
switch (type) {
case ProcessType::Chrome:
return "Chrome"sv;
case ProcessType::WebContent:
return "WebContent"sv;
case ProcessType::WebWorker:
return "WebWorker"sv;
case ProcessType::SQLServer:
return "SQLServer"sv;
case ProcessType::RequestServer:
return "RequestServer"sv;
case ProcessType::ImageDecoder:
return "ImageDecoder"sv;
}
VERIFY_NOT_REACHED();
}
ProcessManager::ProcessManager()
{
}
ProcessManager::~ProcessManager()
{
}
ProcessManager& ProcessManager::the()
{
static ProcessManager s_the;
return s_the;
}
void ProcessManager::initialize()
{
// FIXME: Should we change this to call EventLoop::register_signal?
// Note that only EventLoopImplementationUnix has a working register_signal
struct sigaction action { };
action.sa_flags = SA_RESTART;
action.sa_sigaction = [](int, auto*, auto) {
s_received_sigchld = 1;
};
MUST(Core::System::sigaction(SIGCHLD, &action, nullptr));
the().add_process(WebView::ProcessType::Chrome, getpid());
#ifdef AK_OS_MACH
auto self_send_port = mach_task_self();
auto res = mach_port_mod_refs(mach_task_self(), self_send_port, MACH_PORT_RIGHT_SEND, +1);
VERIFY(res == KERN_SUCCESS);
the().add_process(getpid(), Core::MachPort::adopt_right(self_send_port, Core::MachPort::PortRight::Send));
#endif
}
ProcessInfo* ProcessManager::find_process(pid_t pid)
{
if (auto existing_process = m_statistics.processes.find_if([&](auto& info) { return info->pid == pid; }); !existing_process.is_end())
return verify_cast<ProcessInfo>(existing_process->ptr());
return nullptr;
}
void ProcessManager::add_process(ProcessType type, pid_t pid)
{
Threading::MutexLocker locker { m_lock };
dbgln("ProcessManager::add_process({}, {})", process_name_from_type(type), pid);
if (auto* existing_process = find_process(pid)) {
existing_process->type = type;
return;
}
m_statistics.processes.append(make<ProcessInfo>(type, pid));
}
#if defined(AK_OS_MACH)
void ProcessManager::add_process(pid_t pid, Core::MachPort&& port)
{
Threading::MutexLocker locker { m_lock };
dbgln("ProcessManager::add_process({}, {:p})", pid, port.port());
if (auto* existing_process = find_process(pid)) {
existing_process->child_task_port = move(port);
return;
}
m_statistics.processes.append(make<ProcessInfo>(pid, move(port)));
}
#endif
void ProcessManager::remove_process(pid_t pid)
{
Threading::MutexLocker locker { m_lock };
m_statistics.processes.remove_first_matching([&](auto const& info) {
if (info->pid == pid) {
auto type = verify_cast<ProcessInfo>(*info).type;
dbgln("ProcessManager: Remove process {} ({})", process_name_from_type(type), pid);
return true;
}
return false;
});
}
void ProcessManager::update_all_processes()
{
if (s_received_sigchld) {
s_received_sigchld = 0;
auto result = Core::System::waitpid(-1, WNOHANG);
while (!result.is_error() && result.value().pid > 0) {
auto& [pid, status] = result.value();
if (WIFEXITED(status) || WIFSIGNALED(status)) {
remove_process(pid);
}
result = Core::System::waitpid(-1, WNOHANG);
}
}
Threading::MutexLocker locker { m_lock };
(void)update_process_statistics(m_statistics);
}
String ProcessManager::generate_html()
{
Threading::MutexLocker locker { m_lock };
StringBuilder builder;
builder.append(R"(
<html>
<head>
<style>
@media (prefers-color-scheme: dark) {
/* FIXME: We should be able to remove the HTML style when "color-scheme" is supported */
html {
background-color: rgb(30, 30, 30);
color: white;
}
tr:nth-child(even) {
background: rgb(57, 57, 57);
}
}
@media (prefers-color-scheme: light) {
tr:nth-child(even) {
background: #f7f7f7;
}
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
border-bottom: 1px solid #aaa;
}
td, th {
padding: 4px;
border: 1px solid #aaa;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Type</th>
<th>PID</th>
<th>Memory Usage</th>
<th>CPU %</th>
</tr>
</thead>
<tbody>
)"sv);
m_statistics.for_each_process<ProcessInfo>([&](auto const& process) {
builder.append("<tr>"sv);
builder.append("<td>"sv);
builder.append(WebView::process_name_from_type(process.type));
builder.append("</td>"sv);
builder.append("<td>"sv);
builder.append(MUST(String::number(process.pid)));
builder.append("</td>"sv);
builder.append("<td>"sv);
builder.append(human_readable_size(process.memory_usage_bytes));
builder.append("</td>"sv);
builder.append("<td>"sv);
builder.append(MUST(String::formatted("{:.1f}", process.cpu_percent)));
builder.append("</td>"sv);
builder.append("</tr>"sv);
});
builder.append(R"(
</tbody>
</table>
</body>
</html>
)"sv);
return builder.to_string_without_validation();
}
}