CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
360 lines
12 KiB
C++
360 lines
12 KiB
C++
// Copyright (C) 2024-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
#ifndef SYSMEM_H
|
|
#define SYSMEM_H
|
|
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <shared_mutex>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/sysinfo.h>
|
|
#endif
|
|
|
|
static constexpr size_t ONE_GiB = 1 << 30;
|
|
static constexpr size_t ONE_MiB = 1 << 20;
|
|
static constexpr size_t ONE_KiB = 1 << 10;
|
|
|
|
inline size_t bytes_to_GiB(const size_t bytes)
|
|
{
|
|
return bytes == 0 ? 0 : (bytes - 1 + ONE_GiB) / ONE_GiB;
|
|
}
|
|
|
|
// returns a string reporting a byte size in a readable format,
|
|
// rounding it up to the nearest KiB (MiB if `byte_sz` exceeds 1 GiB)
|
|
static std::string byte_size_to_str(size_t byte_sz)
|
|
{
|
|
if(byte_sz == 0)
|
|
return "0 B";
|
|
std::string ret;
|
|
const std::pair<size_t, std::string> mem_units[]
|
|
= {{ONE_GiB, "GiB"}, {ONE_MiB, "MiB"}, {ONE_KiB, "KiB"}};
|
|
for(const auto& u : mem_units)
|
|
{
|
|
auto tmp = byte_sz / u.first;
|
|
byte_sz -= tmp * u.first;
|
|
if(byte_sz > 0 && (u.first == ONE_KiB || !ret.empty()))
|
|
{
|
|
// round up to nearest relevant unit and finish
|
|
tmp++;
|
|
byte_sz = 0;
|
|
}
|
|
if(tmp > 0)
|
|
{
|
|
if(!ret.empty())
|
|
ret += " ";
|
|
ret += std::to_string(tmp) + " " + u.second;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// return a string reporting a vec of byte sizes in a readable format.
|
|
static std::string byte_sizes_to_str(const std::vector<size_t>& byte_sizes)
|
|
{
|
|
std::string ret;
|
|
for(auto i : byte_sizes)
|
|
{
|
|
if(!ret.empty())
|
|
ret.push_back(',');
|
|
ret += byte_size_to_str(i);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
struct system_memory
|
|
{
|
|
public:
|
|
// acquire a reference to a singleton of this struct
|
|
static system_memory& singleton()
|
|
{
|
|
static system_memory mem;
|
|
return mem;
|
|
}
|
|
|
|
size_t get_total_bytes() const
|
|
{
|
|
return total_bytes;
|
|
}
|
|
|
|
size_t get_total_gbytes() const
|
|
{
|
|
return bytes_to_GiB(get_total_bytes());
|
|
}
|
|
|
|
void set_limit_bytes(size_t limit_bytes_)
|
|
{
|
|
std::unique_lock lock(sys_memory_mutex);
|
|
// Don't let limit use the total available memory, leave at
|
|
// least a 1GiB buffer, otherwise process may get OOM killed.
|
|
limit_bytes = std::min(limit_bytes_, total_bytes >= ONE_GiB ? total_bytes - ONE_GiB : 0);
|
|
}
|
|
|
|
void set_limit_gbytes(size_t limit_gbytes_)
|
|
{
|
|
set_limit_bytes(limit_gbytes_ * ONE_GiB);
|
|
}
|
|
|
|
// The non-default specialization should *never* be used by non-members
|
|
template <bool accountant_mutex_is_locked = false>
|
|
size_t get_usable_bytes()
|
|
{
|
|
update_free_bytes<accountant_mutex_is_locked>();
|
|
|
|
// Limit the amount of usable memory. If we are too aggressive
|
|
// with host memory usage, the host process may get OOM killed
|
|
// on systems with little or no swap space.
|
|
using lock_t = std::shared_lock<decltype(sys_memory_mutex)>;
|
|
std::optional<lock_t> lock;
|
|
if constexpr(!accountant_mutex_is_locked)
|
|
lock = std::make_optional<lock_t>(sys_memory_mutex);
|
|
return std::min(free_bytes < ONE_GiB ? 0 : free_bytes,
|
|
used_bytes < limit_bytes ? limit_bytes - used_bytes : 0);
|
|
}
|
|
|
|
size_t get_limit_bytes() const
|
|
{
|
|
std::shared_lock lock(sys_memory_mutex);
|
|
return limit_bytes;
|
|
}
|
|
|
|
void record_used_bytes(size_t allocation_size)
|
|
{
|
|
std::unique_lock lock(sys_memory_mutex);
|
|
used_bytes += allocation_size;
|
|
}
|
|
void release_used_bytes(size_t allocation_size)
|
|
{
|
|
std::unique_lock lock(sys_memory_mutex);
|
|
// prevent underflows
|
|
used_bytes -= std::min(allocation_size, used_bytes);
|
|
}
|
|
|
|
// The non-default specialization should *never* be used by non-members
|
|
template <bool accountant_mutex_is_locked = false>
|
|
std::string get_details(bool double_tab = false)
|
|
{
|
|
const auto usable_bytes = get_usable_bytes<accountant_mutex_is_locked>();
|
|
using lock_t = std::shared_lock<decltype(sys_memory_mutex)>;
|
|
std::optional<lock_t> lock;
|
|
if constexpr(!accountant_mutex_is_locked)
|
|
lock = std::make_optional<lock_t>(sys_memory_mutex);
|
|
std::stringstream ss;
|
|
const auto incr = (double_tab ? "\t\t" : "\t");
|
|
ss << incr << "Usable system memory: " << byte_size_to_str(usable_bytes) << "\n"
|
|
<< incr << "Free system memory: " << byte_size_to_str(free_bytes) << "\n"
|
|
<< incr << "Used system memory: " << byte_size_to_str(used_bytes) << "\n"
|
|
<< incr << "Enforced limit on memory usage: " << byte_size_to_str(limit_bytes);
|
|
return ss.str();
|
|
}
|
|
|
|
// Structure effectively reserving a chunk of system memory by a given amount, estimated
|
|
// for untracked/nonowned needs, e.g., by some black-box dependency.
|
|
struct nonowned_reservation_t
|
|
{
|
|
nonowned_reservation_t(size_t block_byte_size = 0)
|
|
: byte_size(0)
|
|
{
|
|
if(block_byte_size > 0)
|
|
set_desired_size(block_byte_size);
|
|
}
|
|
// disable copies
|
|
nonowned_reservation_t(const nonowned_reservation_t&) = delete;
|
|
nonowned_reservation_t& operator=(const nonowned_reservation_t&) = delete;
|
|
|
|
void release()
|
|
{
|
|
if(byte_size > 0)
|
|
{
|
|
auto& accountant = system_memory::singleton();
|
|
std::unique_lock lock(accountant.sys_memory_mutex);
|
|
accountant.limit_bytes += byte_size;
|
|
byte_size = 0;
|
|
}
|
|
}
|
|
|
|
// An std::invalid_argument exception is thrown if the desired size cannot be reserved reliably.
|
|
void set_desired_size(size_t desired_byte_size)
|
|
{
|
|
release();
|
|
auto& accountant = system_memory::singleton();
|
|
std::unique_lock lock(accountant.sys_memory_mutex);
|
|
constexpr bool accountant_mutex_is_locked = true;
|
|
const auto usable_bytes = accountant.get_usable_bytes<accountant_mutex_is_locked>();
|
|
if(desired_byte_size > usable_bytes)
|
|
{
|
|
throw std::invalid_argument(
|
|
"Desired reservation of " + byte_size_to_str(desired_byte_size)
|
|
+ " of system memory cannot be honored reliably as only "
|
|
+ byte_size_to_str(usable_bytes) + " of system memory is usable.\n"
|
|
+ accountant.get_details<accountant_mutex_is_locked>());
|
|
}
|
|
accountant.limit_bytes -= desired_byte_size;
|
|
byte_size = desired_byte_size;
|
|
}
|
|
|
|
size_t size() const
|
|
{
|
|
return byte_size;
|
|
}
|
|
|
|
~nonowned_reservation_t()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void swap(nonowned_reservation_t& other)
|
|
{
|
|
std::swap(byte_size, other.byte_size);
|
|
}
|
|
|
|
private:
|
|
size_t byte_size;
|
|
};
|
|
|
|
private:
|
|
const size_t total_bytes;
|
|
size_t free_bytes;
|
|
size_t limit_bytes;
|
|
size_t used_bytes;
|
|
|
|
mutable std::shared_mutex sys_memory_mutex;
|
|
|
|
system_memory()
|
|
: total_bytes(read_sys_mem<sys_mem_label::TOTAL>())
|
|
, free_bytes(read_sys_mem<sys_mem_label::FREE>())
|
|
, limit_bytes(total_bytes)
|
|
, used_bytes(0)
|
|
{
|
|
}
|
|
|
|
template <bool accountant_mutex_is_locked>
|
|
void update_free_bytes()
|
|
{
|
|
using lock_t = std::unique_lock<decltype(sys_memory_mutex)>;
|
|
std::optional<lock_t> lock;
|
|
if constexpr(!accountant_mutex_is_locked)
|
|
lock = std::make_optional<lock_t>(sys_memory_mutex);
|
|
free_bytes = read_sys_mem<sys_mem_label::FREE>();
|
|
}
|
|
|
|
enum class sys_mem_label
|
|
{
|
|
TOTAL,
|
|
FREE
|
|
};
|
|
|
|
template <sys_mem_label mem_label>
|
|
static size_t read_sys_mem()
|
|
{
|
|
static_assert(mem_label == sys_mem_label::TOTAL || mem_label == sys_mem_label::FREE);
|
|
size_t ret = 0;
|
|
#ifdef _WIN32
|
|
MEMORYSTATUSEX info;
|
|
info.dwLength = sizeof(info);
|
|
if(!GlobalMemoryStatusEx(&info))
|
|
return ret;
|
|
if constexpr(mem_label == sys_mem_label::TOTAL)
|
|
ret = info.ullTotalPhys;
|
|
else
|
|
ret = info.ullAvailPhys;
|
|
#else
|
|
// Read from /proc/meminfo if possible
|
|
try
|
|
{
|
|
std::ifstream proc_meminfo("/proc/meminfo");
|
|
std::string proc_line;
|
|
while(std::getline(proc_meminfo, proc_line))
|
|
{
|
|
// meminfo counts in KiB
|
|
if constexpr(mem_label == sys_mem_label::TOTAL)
|
|
{
|
|
if(proc_line.compare(0, 9, "MemTotal:") == 0)
|
|
ret = std::stoull(proc_line.substr(9)) * 1024;
|
|
}
|
|
else
|
|
{
|
|
if(proc_line.compare(0, 13, "MemAvailable:") == 0)
|
|
ret = std::stoull(proc_line.substr(13)) * 1024;
|
|
}
|
|
if(ret)
|
|
break;
|
|
}
|
|
}
|
|
catch(std::exception&)
|
|
{
|
|
// If there was a problem, we'll fall back to sysinfo
|
|
}
|
|
|
|
if(ret == 0)
|
|
{
|
|
// Either something couldn't be parsed or proc is not
|
|
// mounted. Fall back to sysinfo which can tell total +
|
|
// free, but not "available".
|
|
struct sysinfo info;
|
|
if(sysinfo(&info) != 0)
|
|
return ret;
|
|
if constexpr(mem_label == sys_mem_label::TOTAL)
|
|
ret = info.totalram * info.mem_unit;
|
|
else
|
|
ret = (info.freeram + info.bufferram) * info.mem_unit;
|
|
}
|
|
|
|
// top-level memory cgroup may restrict this further
|
|
const std::pair<std::string, std::string> memcg1_paths
|
|
= {"/sys/fs/cgroup/memory/memory.limit_in_bytes",
|
|
"/sys/fs/cgroup/memory/memory.usage_in_bytes"};
|
|
const std::pair<std::string, std::string> memcg2_paths
|
|
= {"/sys/fs/cgroup/memory.max", "/sys/fs/cgroup/memory.current"};
|
|
for(const auto& memcg_paths : {memcg1_paths, memcg2_paths})
|
|
{
|
|
std::ifstream memcg_limit_file(memcg_paths.first.c_str());
|
|
std::ifstream memcg_usage_file(memcg_paths.second.c_str());
|
|
size_t memcg_limit_bytes;
|
|
size_t memcg_usage_bytes;
|
|
// use cgroup limit if we can read the cgroup files and it's smaller
|
|
if((memcg_limit_file >> memcg_limit_bytes) && (memcg_usage_file >> memcg_usage_bytes))
|
|
{
|
|
if constexpr(mem_label == sys_mem_label::TOTAL)
|
|
ret = std::min(ret, memcg_limit_bytes);
|
|
else
|
|
ret = std::min(ret,
|
|
memcg_usage_bytes <= memcg_limit_bytes
|
|
? memcg_limit_bytes - memcg_usage_bytes
|
|
: 0);
|
|
}
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
#endif // SYSMEM_H
|