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.
This commit is contained in:
Timothy Flynn 2024-04-20 23:35:17 -04:00 committed by Andrew Kaster
parent ac594fae5e
commit 5dd3b91f0e
Notes: sideshowbarker 2024-07-16 19:42:24 +09:00
17 changed files with 176 additions and 126 deletions

View File

@ -6,7 +6,7 @@
#include "MachPortServer.h"
#include <AK/Debug.h>
#include <LibWebView/Platform/ProcessStatisticsMach.h>
#include <LibCore/Platform/ProcessStatisticsMach.h>
namespace Ladybird {
@ -56,7 +56,7 @@ void MachPortServer::thread_loop()
{
while (!m_should_stop.load(MemoryOrder::memory_order_acquire)) {
WebView::ParentPortMessage message {};
Core::Platform::ParentPortMessage message {};
// Get the pid of the child from the audit trailer so we can associate the port w/it
mach_msg_options_t const options = MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
@ -68,7 +68,7 @@ void MachPortServer::thread_loop()
break;
}
if (message.header.msgh_id != WebView::SELF_TASK_PORT_MESSAGE_ID) {
if (message.header.msgh_id != Core::Platform::SELF_TASK_PORT_MESSAGE_ID) {
dbgln("Received message with id {}, ignoring", message.header.msgh_id);
continue;
}

View File

@ -46,7 +46,7 @@
#endif
#if defined(AK_OS_MACOS)
# include <LibWebView/Platform/ProcessStatisticsMach.h>
# include <LibCore/Platform/ProcessStatisticsMach.h>
#endif
static ErrorOr<void> load_content_filters();
@ -132,7 +132,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
#if defined(AK_OS_MACOS)
if (!mach_server_name.is_empty()) {
WebView::register_with_mach_server(mach_server_name);
Core::Platform::register_with_mach_server(mach_server_name);
}
#endif

View File

@ -72,6 +72,8 @@ source_set("sources") {
"NetworkResponse.h",
"Notifier.cpp",
"Notifier.h",
"Platform/ProcessInfo.h",
"Platform/ProcessStatistics.h",
"Process.cpp",
"Process.h",
"ProcessStatisticsReader.cpp",
@ -119,6 +121,19 @@ source_set("sources") {
if (current_os == "mac") {
sources += [ "MachPort.cpp" ]
}
if (current_os == "serenity") {
sources += [ "Platform/ProcessStatisticsSerenity.cpp" ]
} else if (current_os == "linux") {
sources += [ "Platform/ProcessStatisticsLinux.cpp" ]
} else if (current_os == "mac") {
sources += [
"Platform/ProcessStatisticsMach.cpp",
"Platform/ProcessStatisticsMach.h",
]
} else {
sources += [ "Platform/ProcessStatisticsUnimplemented.cpp" ]
}
}
source_set("filewatcher") {

View File

@ -146,15 +146,6 @@ shared_library("LibWebView") {
}
if (current_os == "serenity") {
sources += [
"OutOfProcessWebView.cpp",
"Platform/ProcessStatisticsSerenity.cpp",
]
} else if (current_os == "linux") {
sources += [ "Platform/ProcessStatisticsLinux.cpp" ]
} else if (current_os == "mac") {
sources += [ "Platform/ProcessStatisticsMach.cpp" ]
} else {
sources += [ "Platform/ProcessStatisticsNoop.cpp" ]
sources += [ "OutOfProcessWebView.cpp" ]
}
}

View File

@ -60,13 +60,25 @@ endif()
# FIXME: Implement Core::FileWatcher for *BSD and Windows.
if (SERENITYOS)
list(APPEND SOURCES FileWatcherSerenity.cpp)
list(APPEND SOURCES
FileWatcherSerenity.cpp
Platform/ProcessStatisticsSerenity.cpp
)
elseif (LINUX AND NOT EMSCRIPTEN)
list(APPEND SOURCES FileWatcherLinux.cpp)
list(APPEND SOURCES
FileWatcherLinux.cpp
Platform/ProcessStatisticsLinux.cpp
)
elseif (APPLE AND NOT IOS)
list(APPEND SOURCES FileWatcherMacOS.mm)
list(APPEND SOURCES
FileWatcherMacOS.mm
Platform/ProcessStatisticsMach.cpp
)
else()
list(APPEND SOURCES FileWatcherUnimplemented.cpp)
list(APPEND SOURCES
FileWatcherUnimplemented.cpp
Platform/ProcessStatisticsUnimplemented.cpp
)
endif()
if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "GNU")

View File

@ -12,39 +12,31 @@
# include <LibCore/MachPort.h>
#endif
namespace WebView {
enum class ProcessType {
Chrome,
WebContent,
WebWorker,
SQLServer,
RequestServer,
ImageDecoder,
};
namespace Core::Platform {
struct ProcessInfo {
ProcessInfo(ProcessType type, pid_t pid)
: type(type)
, pid(pid)
explicit ProcessInfo(pid_t pid)
: pid(pid)
{
}
ProcessType type = ProcessType::WebContent;
pid_t pid;
u64 memory_usage_bytes = 0;
float cpu_percent = 0.0f;
virtual ~ProcessInfo() = default;
u64 time_spent_in_process = 0;
pid_t pid { 0 };
u64 memory_usage_bytes { 0 };
float cpu_percent { 0.0f };
u64 time_spent_in_process { 0 };
#if defined(AK_OS_MACH)
Core::MachPort child_task_port;
ProcessInfo(pid_t pid, Core::MachPort&& port)
: pid(pid)
, child_task_port(move(port))
{
}
Core::MachPort child_task_port;
#endif
};

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/TypeCasts.h>
#include <AK/Vector.h>
#include <LibCore/Platform/ProcessInfo.h>
namespace Core::Platform {
struct ProcessStatistics {
template<typename ProcessInfoType, typename Callback>
void for_each_process(Callback&& callback)
{
for (auto& process : processes)
callback(verify_cast<ProcessInfoType>(*process));
}
u64 total_time_scheduled { 0 };
Vector<NonnullOwnPtr<ProcessInfo>> processes;
};
ErrorOr<void> update_process_statistics(ProcessStatistics&);
}

View File

@ -6,10 +6,10 @@
#include <AK/String.h>
#include <LibCore/File.h>
#include <LibWebView/Platform/ProcessStatistics.h>
#include <LibCore/Platform/ProcessStatistics.h>
#include <unistd.h>
namespace WebView {
namespace Core::Platform {
static auto user_hz = sysconf(_SC_CLK_TCK);
static auto page_size = sysconf(_SC_PAGESIZE);
@ -43,7 +43,7 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
statistics.total_time_scheduled = total_time_scheduled;
for (auto& process : statistics.processes) {
auto proc_pid_stat_or_error = Core::File::open(MUST(String::formatted("/proc/{}/stat", process.pid)), Core::File::OpenMode::Read);
auto proc_pid_stat_or_error = Core::File::open(MUST(String::formatted("/proc/{}/stat", process->pid)), Core::File::OpenMode::Read);
if (proc_pid_stat_or_error.is_error()) {
// FIXME: Remove stale process from process list?
continue;
@ -60,15 +60,15 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
if (res != 3)
return Error::from_string_literal("Failed to parse /proc/pid/stat");
process.memory_usage_bytes = rss * page_size;
process->memory_usage_bytes = rss * page_size;
u64 const time_process = utime + stime;
float const time_scheduled_diff = time_process - process.time_spent_in_process;
process.time_spent_in_process = time_process;
float const time_scheduled_diff = time_process - process->time_spent_in_process;
process->time_spent_in_process = time_process;
process.cpu_percent = 0.0;
process->cpu_percent = 0.0;
if (total_time_scheduled_diff > 0) {
process.cpu_percent = time_scheduled_diff / (total_time_scheduled_diff / ncpu_online) * 100.0f;
process->cpu_percent = time_scheduled_diff / (total_time_scheduled_diff / ncpu_online) * 100.0f;
}
}

View File

@ -13,9 +13,9 @@
#include <AK/ByteString.h>
#include <AK/Time.h>
#include <LibCore/MachPort.h>
#include <LibWebView/Platform/ProcessStatisticsMach.h>
#include <LibCore/Platform/ProcessStatisticsMach.h>
namespace WebView {
namespace Core::Platform {
static auto user_hz = sysconf(_SC_CLK_TCK);
@ -43,19 +43,19 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
for (auto& process : statistics.processes) {
mach_task_basic_info_data_t basic_info {};
count = MACH_TASK_BASIC_INFO_COUNT;
res = task_info(process.child_task_port.port(), MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&basic_info), &count);
res = task_info(process->child_task_port.port(), MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&basic_info), &count);
if (res != KERN_SUCCESS) {
dbgln("Failed to get task info for pid {}: {}", process.pid, mach_error_string(res));
dbgln("Failed to get task info for pid {}: {}", process->pid, mach_error_string(res));
return Core::mach_error_to_error(res);
}
process.memory_usage_bytes = basic_info.resident_size;
process->memory_usage_bytes = basic_info.resident_size;
task_thread_times_info_data_t time_info {};
count = TASK_THREAD_TIMES_INFO_COUNT;
res = task_info(process.child_task_port.port(), TASK_THREAD_TIMES_INFO, reinterpret_cast<task_info_t>(&time_info), &count);
res = task_info(process->child_task_port.port(), TASK_THREAD_TIMES_INFO, reinterpret_cast<task_info_t>(&time_info), &count);
if (res != KERN_SUCCESS) {
dbgln("Failed to get thread times info for pid {}: {}", process.pid, mach_error_string(res));
dbgln("Failed to get thread times info for pid {}: {}", process->pid, mach_error_string(res));
return Core::mach_error_to_error(res);
}
@ -64,12 +64,12 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
scratch_timeval = { static_cast<time_t>(time_info.system_time.seconds), static_cast<suseconds_t>(time_info.system_time.microseconds) };
time_in_process += Duration::from_timeval(scratch_timeval);
auto time_diff_process = time_in_process - Duration::from_microseconds(process.time_spent_in_process);
process.time_spent_in_process = time_in_process.to_microseconds();
auto time_diff_process = time_in_process - Duration::from_microseconds(process->time_spent_in_process);
process->time_spent_in_process = time_in_process.to_microseconds();
process.cpu_percent = 0.0f;
process->cpu_percent = 0.0f;
if (time_diff_process > Duration::zero())
process.cpu_percent = 100.0f * static_cast<float>(time_diff_process.to_microseconds()) / total_cpu_micro_diff;
process->cpu_percent = 100.0f * static_cast<float>(time_diff_process.to_microseconds()) / total_cpu_micro_diff;
}
return {};

View File

@ -12,10 +12,10 @@
# error "This file is only available on Mach platforms"
#endif
#include <LibWebView/Platform/ProcessStatistics.h>
#include <LibCore/Platform/ProcessStatistics.h>
#include <mach/mach.h>
namespace WebView {
namespace Core::Platform {
struct ChildPortMessage {
mach_msg_header_t header;

View File

@ -11,11 +11,10 @@
#endif
#include <LibCore/File.h>
#include <LibCore/Platform/ProcessStatistics.h>
#include <LibCore/ProcessStatisticsReader.h>
#include <LibWebView/Platform/ProcessInfo.h>
#include <LibWebView/Platform/ProcessStatistics.h>
namespace WebView {
namespace Core::Platform {
ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
{
@ -28,25 +27,25 @@ ErrorOr<void> update_process_statistics(ProcessStatistics& statistics)
statistics.total_time_scheduled = total_time_scheduled;
for (auto& process : statistics.processes) {
auto it = all_processes.processes.find_if([&](auto& entry) { return entry.pid == process.pid; });
auto it = all_processes.processes.find_if([&](auto& entry) { return entry.pid == process->pid; });
if (!it.is_end()) {
process.memory_usage_bytes = it->amount_resident;
process->memory_usage_bytes = it->amount_resident;
u64 time_process = 0;
for (auto& thread : it->threads) {
time_process += thread.time_user + thread.time_kernel;
}
u64 time_scheduled_diff = time_process - process.time_spent_in_process;
u64 time_scheduled_diff = time_process - process->time_spent_in_process;
process.time_spent_in_process = time_process;
process.cpu_percent = 0.0;
process->time_spent_in_process = time_process;
process->cpu_percent = 0.0;
if (total_time_scheduled_diff > 0) {
process.cpu_percent = static_cast<float>((time_scheduled_diff * 1000) / total_time_scheduled_diff) / 10.0f;
process->cpu_percent = static_cast<float>((time_scheduled_diff * 1000) / total_time_scheduled_diff) / 10.0f;
}
} else {
process.memory_usage_bytes = 0;
process.cpu_percent = 0.0;
process.time_spent_in_process = 0;
process->memory_usage_bytes = 0;
process->cpu_percent = 0.0;
process->time_spent_in_process = 0;
}
}

View File

@ -4,9 +4,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWebView/Platform/ProcessStatistics.h>
#include <LibCore/Platform/ProcessStatistics.h>
namespace WebView {
namespace Core::Platform {
ErrorOr<void> update_process_statistics(ProcessStatistics&)
{

View File

@ -21,22 +21,7 @@ set(SOURCES
set(GENERATED_SOURCES ${CURRENT_LIB_GENERATED})
if (SERENITYOS)
list(APPEND SOURCES
OutOfProcessWebView.cpp
Platform/ProcessStatisticsSerenity.cpp
)
elseif(LINUX AND NOT ANDROID)
list(APPEND SOURCES
Platform/ProcessStatisticsLinux.cpp
)
elseif(APPLE)
list(APPEND SOURCES
Platform/ProcessStatisticsMach.cpp
)
else()
list(APPEND SOURCES
Platform/ProcessStatisticsNoop.cpp
)
list(APPEND SOURCES OutOfProcessWebView.cpp)
endif()
embed_as_string_view(

View File

@ -1,22 +0,0 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <AK/Vector.h>
#include <LibWebView/Platform/ProcessInfo.h>
namespace WebView {
struct ProcessStatistics {
u64 total_time_scheduled = 0;
Vector<ProcessInfo> processes;
};
ErrorOr<void> update_process_statistics(ProcessStatistics&);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <LibCore/Platform/ProcessInfo.h>
#if defined(AK_OS_MACH)
# include <LibCore/MachPort.h>
#endif
namespace WebView {
enum class ProcessType {
Chrome,
WebContent,
WebWorker,
SQLServer,
RequestServer,
ImageDecoder,
};
struct ProcessInfo : public Core::Platform::ProcessInfo {
using Core::Platform::ProcessInfo::ProcessInfo;
ProcessInfo(ProcessType type, pid_t pid)
: Core::Platform::ProcessInfo(pid)
, type(type)
{
}
ProcessType type { ProcessType::WebContent };
};
}

View File

@ -88,15 +88,23 @@ void ProcessManager::initialize()
#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 = m_statistics.processes.find_if([&](auto& info) { return info.pid == pid; }); !existing_process.is_end()) {
if (auto* existing_process = find_process(pid)) {
existing_process->type = type;
return;
}
m_statistics.processes.append({ type, pid });
m_statistics.processes.append(make<ProcessInfo>(type, pid));
}
#if defined(AK_OS_MACH)
@ -104,20 +112,21 @@ 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 = m_statistics.processes.find_if([&](auto& info) { return info.pid == pid; }); !existing_process.is_end()) {
if (auto* existing_process = find_process(pid)) {
existing_process->child_task_port = move(port);
return;
}
m_statistics.processes.append({ pid, move(port) });
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& info) {
if (info.pid == pid) {
dbgln("ProcessManager: Remove process {} ({})", process_name_from_type(info.type), pid);
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;
@ -146,7 +155,6 @@ String ProcessManager::generate_html()
{
Threading::MutexLocker locker { m_lock };
StringBuilder builder;
auto const& processes = m_statistics.processes;
builder.append(R"(
<html>
@ -197,7 +205,7 @@ String ProcessManager::generate_html()
<tbody>
)"sv);
for (auto const& process : processes) {
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));
@ -212,7 +220,7 @@ String ProcessManager::generate_html()
builder.append(MUST(String::formatted("{:.1f}", process.cpu_percent)));
builder.append("</td>"sv);
builder.append("</tr>"sv);
}
});
builder.append(R"(
</tbody>

View File

@ -9,9 +9,10 @@
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibCore/EventReceiver.h>
#include <LibCore/Platform/ProcessStatistics.h>
#include <LibThreading/Mutex.h>
#include <LibWebView/Forward.h>
#include <LibWebView/Platform/ProcessStatistics.h>
#include <LibWebView/ProcessInfo.h>
namespace WebView {
@ -25,21 +26,20 @@ public:
void add_process(WebView::ProcessType, pid_t);
void remove_process(pid_t);
ProcessInfo* find_process(pid_t);
#if defined(AK_OS_MACH)
void add_process(pid_t, Core::MachPort&&);
#endif
void update_all_processes();
Vector<ProcessInfo> const& processes() const { return m_statistics.processes; }
String generate_html();
private:
ProcessManager();
~ProcessManager();
ProcessStatistics m_statistics;
Core::Platform::ProcessStatistics m_statistics;
Threading::Mutex m_lock;
};