Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ if(WIN32)
# resolve to the same *W types minwin.h forward-declares (otherwise the two
# disagree and MSVC reports "redefinition; different basic types").
target_compile_definitions(eq_base PUBLIC OS_WIN NOMINMAX UNICODE _UNICODE)
target_link_libraries(eq_base PRIVATE psapi)
if(MSVC)
target_compile_options(eq_base PUBLIC "/utf-8")
endif()
Expand Down
4 changes: 3 additions & 1 deletion base/premake5.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ end
local function base_project()
filter("configurations:Profile")
dependencies("tracysdk")
filter("system:windows")
links("psapi")
filter{}
warnings("High")

Expand Down Expand Up @@ -93,4 +95,4 @@ project("base_memory_unittests")
defines({
"BASE_STRIP_BUGCHECK",
"BASE_MEM_CORE_DEBUG" -- enable additional debug verifications in the mem subsystem
})
})
34 changes: 34 additions & 0 deletions base/process/process_metrics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2026 Vincent Hengel.
// For licensing information see LICENSE at the root of this distribution.
#pragma once

#include <base/arch.h>
#include <base/export.h>

namespace base {

#if defined(OS_WIN)
using ProcessHandle = void*;
#elif defined(OS_MAC) || defined(OS_MACOS)
using ProcessHandle = u32; // mach_port_t
#else
using ProcessHandle = i32; // pid_t
#endif

struct ProcessMemoryUsage {
mem_size resident_set_bytes = 0;
mem_size peak_resident_set_bytes = 0;
};

// Returns a borrowed native handle for the calling process. Callers must not
// close it. External handles remain owned by their creator.
BASE_EXPORT ProcessHandle GetCurrentProcessHandle();

// Queries physical memory resident for |process|. On POSIX the handle is a pid,
// on macOS a Mach task port, and on Windows a process HANDLE with query rights.
// The peak may be zero when the platform cannot provide it. Clears |usage| on
// failure.
BASE_EXPORT bool QueryProcessMemoryUsage(ProcessHandle process,
ProcessMemoryUsage& usage);

} // namespace base
45 changes: 45 additions & 0 deletions base/process/process_metrics_linux.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (C) 2026 Vincent Hengel.
// For licensing information see LICENSE at the root of this distribution.

#include "base/process/process_metrics.h"

#include <unistd.h>

#include <cstdio>

namespace base {

ProcessHandle GetCurrentProcessHandle() {
return static_cast<ProcessHandle>(getpid());
}

bool QueryProcessMemoryUsage(ProcessHandle process, ProcessMemoryUsage& usage) {
usage = {};
if (process <= 0)
return false;

char path[64];
std::snprintf(path, sizeof(path), "/proc/%d/status", process);
std::FILE* status = std::fopen(path, "r");
if (!status)
return false;

bool found_resident = false;
char line[256];
while (std::fgets(line, sizeof(line), status)) {
unsigned long long kib = 0;
if (std::sscanf(line, "VmRSS: %llu kB", &kib) == 1) {
usage.resident_set_bytes = static_cast<mem_size>(kib) * 1024u;
found_resident = true;
} else if (std::sscanf(line, "VmHWM: %llu kB", &kib) == 1) {
usage.peak_resident_set_bytes = static_cast<mem_size>(kib) * 1024u;
}
}
std::fclose(status);
if (found_resident)
return true;
usage = {};
return false;
}

} // namespace base
31 changes: 31 additions & 0 deletions base/process/process_metrics_mac.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (C) 2026 Vincent Hengel.
// For licensing information see LICENSE at the root of this distribution.

#include "base/process/process_metrics.h"

#include <mach/mach.h>
namespace base {

ProcessHandle GetCurrentProcessHandle() {
static_assert(sizeof(ProcessHandle) == sizeof(mach_port_t));
return static_cast<ProcessHandle>(mach_task_self());
}

bool QueryProcessMemoryUsage(ProcessHandle process, ProcessMemoryUsage& usage) {
usage = {};
const mach_port_t task = static_cast<mach_port_t>(process);
if (!MACH_PORT_VALID(task))
return false;

mach_task_basic_info_data_t task_memory{};
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
const kern_return_t result = task_info(
task, MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&task_memory), &count);
if (result != KERN_SUCCESS)
return false;
usage.resident_set_bytes = static_cast<mem_size>(task_memory.resident_size);
usage.peak_resident_set_bytes = static_cast<mem_size>(task_memory.resident_size_max);
return true;
}

} // namespace base
53 changes: 53 additions & 0 deletions base/process/process_metrics_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (C) 2026 Vincent Hengel.
// For licensing information see LICENSE at the root of this distribution.

#include "base/process/process_metrics.h"

#if defined(OS_LINUX)
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#endif

#include <gtest/gtest.h>

namespace base {
namespace {

TEST(ProcessMetricsTest, ReportsCurrentProcessResidentSet) {
ProcessMemoryUsage usage;
ASSERT_TRUE(QueryProcessMemoryUsage(GetCurrentProcessHandle(), usage));
EXPECT_GT(usage.resident_set_bytes, 0u);
if (usage.peak_resident_set_bytes > 0) {
EXPECT_GE(usage.peak_resident_set_bytes, usage.resident_set_bytes);
}
}

TEST(ProcessMetricsTest, RejectsInvalidHandleAndClearsOutput) {
ProcessMemoryUsage usage{1, 1};
EXPECT_FALSE(QueryProcessMemoryUsage(ProcessHandle{}, usage));
EXPECT_EQ(usage.resident_set_bytes, 0u);
EXPECT_EQ(usage.peak_resident_set_bytes, 0u);
}

#if defined(OS_LINUX)
TEST(ProcessMetricsTest, ReportsAnotherProcessResidentSet) {
const pid_t child = fork();
ASSERT_NE(child, -1);
if (child == 0) {
pause();
_exit(0);
}

ProcessMemoryUsage usage;
const bool queried = QueryProcessMemoryUsage(static_cast<ProcessHandle>(child), usage);
kill(child, SIGTERM);
waitpid(child, nullptr, 0);

ASSERT_TRUE(queried);
EXPECT_GT(usage.resident_set_bytes, 0u);
}
#endif

} // namespace
} // namespace base
33 changes: 33 additions & 0 deletions base/process/process_metrics_win.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2026 Vincent Hengel.
// For licensing information see LICENSE at the root of this distribution.

#include "base/process/process_metrics.h"

#include <windows.h>
#include <psapi.h>

namespace base {

ProcessHandle GetCurrentProcessHandle() {
static_assert(sizeof(ProcessHandle) == sizeof(HANDLE));
return GetCurrentProcess();
}

bool QueryProcessMemoryUsage(ProcessHandle process, ProcessMemoryUsage& usage) {
usage = {};
// GetCurrentProcess() is the valid pseudo-handle (HANDLE)-1, the same bit
// pattern as INVALID_HANDLE_VALUE, and GetProcessMemoryInfo accepts it.
if (!process)
return false;

PROCESS_MEMORY_COUNTERS counters{};
counters.cb = static_cast<DWORD>(sizeof(counters));
if (!GetProcessMemoryInfo(process, &counters, static_cast<DWORD>(sizeof(counters)))) {
return false;
}
usage.resident_set_bytes = static_cast<mem_size>(counters.WorkingSetSize);
usage.peak_resident_set_bytes = static_cast<mem_size>(counters.PeakWorkingSetSize);
return true;
}

} // namespace base
5 changes: 4 additions & 1 deletion build/build_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ defines({
"OS_LINUX",
"OS_POSIX" }) -- we also define the POSIX alias here)
buildoptions("-mwaitpkg -mrtm") -- Enable Intel(R) Transactional Synchronization Extensions (-mrtm) and WAITPKG instructions support (-mwaitpkg) on relevant processors
defines("OS_MACOS")
filter("system:macosx")
defines({
"OS_MAC",
"OS_POSIX" })

filter {}
-- -std=c++2b
Expand Down
Loading