diff --git a/core/core_bind.cpp b/core/core_bind.cpp index f2eb7823e27a..c7cd797f4d23 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -425,6 +425,10 @@ uint64_t OS::get_static_memory_peak_usage() const { return ::OS::get_singleton()->get_static_memory_peak_usage(); } +Dictionary OS::get_memory_info() const { + return ::OS::get_singleton()->get_memory_info(); +} + /** This method uses a signed argument for better error reporting as it's used from the scripting API. */ void OS::delay_usec(int p_usec) const { ERR_FAIL_COND_MSG( @@ -582,6 +586,7 @@ void OS::_bind_methods() { ClassDB::bind_method(D_METHOD("get_static_memory_usage"), &OS::get_static_memory_usage); ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &OS::get_static_memory_peak_usage); + ClassDB::bind_method(D_METHOD("get_memory_info"), &OS::get_memory_info); ClassDB::bind_method(D_METHOD("move_to_trash", "path"), &OS::move_to_trash); ClassDB::bind_method(D_METHOD("get_user_data_dir"), &OS::get_user_data_dir); diff --git a/core/core_bind.h b/core/core_bind.h index 675da4859172..c7d597562aca 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -190,6 +190,7 @@ public: uint64_t get_static_memory_usage() const; uint64_t get_static_memory_peak_usage() const; + Dictionary get_memory_info() const; void delay_usec(int p_usec) const; void delay_msec(int p_msec) const; diff --git a/core/os/os.cpp b/core/os/os.cpp index ef7d860d19a0..49b47c6c120c 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -295,8 +295,15 @@ Error OS::set_cwd(const String &p_cwd) { return ERR_CANT_OPEN; } -uint64_t OS::get_free_static_memory() const { - return Memory::get_mem_available(); +Dictionary OS::get_memory_info() const { + Dictionary meminfo; + + meminfo["physical"] = -1; + meminfo["free"] = -1; + meminfo["available"] = -1; + meminfo["stack"] = -1; + + return meminfo; } void OS::yield() { diff --git a/core/os/os.h b/core/os/os.h index d77890d89dd9..076a9db55c36 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -229,7 +229,7 @@ public: virtual uint64_t get_static_memory_usage() const; virtual uint64_t get_static_memory_peak_usage() const; - virtual uint64_t get_free_static_memory() const; + virtual Dictionary get_memory_info() const; RenderThreadMode get_render_thread_mode() const { return _render_thread_mode; } diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 28c6247338f8..12c3e573e343 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -277,6 +277,16 @@ [b]Note:[/b] Thread IDs are not deterministic and may be reused across application restarts. + + + + Returns the [Dictionary] with the following keys: + [code]"physical"[/code] - total amount of usable physical memory, in bytes or [code]-1[/code] if unknown. This value can be slightly less than the actual physical memory amount, since it does not include memory reserved by kernel and devices. + [code]"free"[/code] - amount of physical memory, that can be immediately allocated without disk access or other costly operation, in bytes or [code]-1[/code] if unknown. The process might be able to allocate more physical memory, but such allocation will require moving inactive pages to disk and can take some time. + [code]"available"[/code] - amount of memory, that can be allocated without extending the swap file(s), in bytes or [code]-1[/code] if unknown. This value include both physical memory and swap. + [code]"stack"[/code] - size of the current thread stack, in bytes or [code]-1[/code] if unknown. + + diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 178f01b18558..967699c5f381 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -41,9 +41,12 @@ #include "drivers/unix/thread_posix.h" #include "servers/rendering_server.h" -#ifdef __APPLE__ +#if defined(__APPLE__) #include +#include +#include #include +#include #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) @@ -51,6 +54,19 @@ #include #endif +#if defined(__FreeBSD__) +#include +#endif + +#if defined(__OpenBSD__) +#include +#include +#endif + +#if defined(__NetBSD__) +#include +#endif + #include #include #include @@ -59,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -274,6 +291,192 @@ uint64_t OS_Unix::get_ticks_usec() const { return longtime; } +Dictionary OS_Unix::get_memory_info() const { + Dictionary meminfo; + + meminfo["physical"] = -1; + meminfo["free"] = -1; + meminfo["available"] = -1; + meminfo["stack"] = -1; + +#if defined(__APPLE__) + int pagesize = 0; + size_t len = sizeof(pagesize); + if (sysctlbyname("vm.pagesize", &pagesize, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get vm.pagesize, error code: %d - %s", errno, strerror(errno))); + } + + int64_t phy_mem = 0; + len = sizeof(phy_mem); + if (sysctlbyname("hw.memsize", &phy_mem, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get hw.memsize, error code: %d - %s", errno, strerror(errno))); + } + + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_statistics64_data_t vmstat; + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) { + ERR_PRINT(vformat("Could not get host vm statistics.")); + } + struct xsw_usage swap_used; + len = sizeof(swap_used); + if (sysctlbyname("vm.swapusage", &swap_used, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get vm.swapusage, error code: %d - %s", errno, strerror(errno))); + } + + if (phy_mem != 0) { + meminfo["physical"] = phy_mem; + } + if (vmstat.free_count * (int64_t)pagesize != 0) { + meminfo["free"] = vmstat.free_count * (int64_t)pagesize; + } + if (swap_used.xsu_avail + vmstat.free_count * (int64_t)pagesize != 0) { + meminfo["available"] = swap_used.xsu_avail + vmstat.free_count * (int64_t)pagesize; + } +#elif defined(__FreeBSD__) + int pagesize = 0; + size_t len = sizeof(pagesize); + if (sysctlbyname("vm.stats.vm.v_page_size", &pagesize, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_size, error code: %d - %s", errno, strerror(errno))); + } + + uint64_t mtotal = 0; + len = sizeof(mtotal); + if (sysctlbyname("vm.stats.vm.v_page_count", &mtotal, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_count, error code: %d - %s", errno, strerror(errno))); + } + uint64_t mfree = 0; + len = sizeof(mfree); + if (sysctlbyname("vm.stats.vm.v_free_count", &mfree, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get vm.stats.vm.v_free_count, error code: %d - %s", errno, strerror(errno))); + } + + uint64_t stotal = 0; + uint64_t sused = 0; + char errmsg[_POSIX2_LINE_MAX] = {}; + kvm_t *kd = kvm_openfiles(nullptr, "/dev/null", nullptr, 0, errmsg); + if (kd == nullptr) { + ERR_PRINT(vformat("kvm_openfiles failed, error: %s", errmsg)); + } else { + struct kvm_swap swap_info[32]; + int count = kvm_getswapinfo(kd, swap_info, 32, 0); + for (int i = 0; i < count; i++) { + stotal += swap_info[i].ksw_total; + sused += swap_info[i].ksw_used; + } + kvm_close(kd); + } + + if (mtotal * pagesize != 0) { + meminfo["physical"] = mtotal * pagesize; + } + if (mfree * pagesize != 0) { + meminfo["free"] = mfree * pagesize; + } + if ((mfree + stotal - sused) * pagesize != 0) { + meminfo["available"] = (mfree + stotal - sused) * pagesize; + } +#elif defined(__OpenBSD__) + int pagesize = sysconf(_SC_PAGESIZE); + + const int mib[] = { CTL_VM, VM_UVMEXP }; + uvmexp uvmexp_info; + size_t len = sizeof(uvmexp_info); + if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP, error code: %d - %s", errno, strerror(errno))); + } + + uint64_t stotal = 0; + uint64_t sused = 0; + int count = swapctl(SWAP_NSWAP, 0, 0); + if (count > 0) { + swapent swap_info[count]; + count = swapctl(SWAP_STATS, swap_info, count); + + for (int i = 0; i < count; i++) { + if (swap_info[i].se_flags & SWF_ENABLE) { + sused += swap_info[i].se_inuse; + stotal += swap_info[i].se_nblks; + } + } + } + + if (uvmexp_info.npages * pagesize != 0) { + meminfo["physical"] = uvmexp_info.npages * pagesize; + } + if (uvmexp_info.free * pagesize != 0) { + meminfo["free"] = uvmexp_info.free * pagesize; + } + if ((uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE != 0) { + meminfo["available"] = (uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE; + } +#elif defined(__NetBSD__) + int pagesize = sysconf(_SC_PAGESIZE); + + const int mib[] = { CTL_VM, VM_UVMEXP2 }; + uvmexp_sysctl uvmexp_info; + size_t len = sizeof(uvmexp_info); + if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) { + ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP2, error code: %d - %s", errno, strerror(errno))); + } + + if (uvmexp_info.npages * pagesize != 0) { + meminfo["physical"] = uvmexp_info.npages * pagesize; + } + if (uvmexp_info.free * pagesize != 0) { + meminfo["free"] = uvmexp_info.free * pagesize; + } + if ((uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize != 0) { + meminfo["available"] = (uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize; + } +#else + Error err; + Ref f = FileAccess::open("/proc/meminfo", FileAccess::READ, &err); + uint64_t mtotal = 0; + uint64_t mfree = 0; + uint64_t sfree = 0; + while (f.is_valid() && !f->eof_reached()) { + String s = f->get_line().strip_edges(); + if (s.begins_with("MemTotal:")) { + Vector stok = s.replace("MemTotal:", "").strip_edges().split(" "); + if (stok.size() == 2) { + mtotal = stok[0].to_int() * 1024; + } + } + if (s.begins_with("MemFree:")) { + Vector stok = s.replace("MemFree:", "").strip_edges().split(" "); + if (stok.size() == 2) { + mfree = stok[0].to_int() * 1024; + } + } + if (s.begins_with("SwapFree:")) { + Vector stok = s.replace("SwapFree:", "").strip_edges().split(" "); + if (stok.size() == 2) { + sfree = stok[0].to_int() * 1024; + } + } + } + + if (mtotal != 0) { + meminfo["physical"] = mtotal; + } + if (mfree != 0) { + meminfo["free"] = mfree; + } + if (mfree + sfree != 0) { + meminfo["available"] = mfree + sfree; + } +#endif + + rlimit stackinfo = {}; + getrlimit(RLIMIT_STACK, &stackinfo); + + if (stackinfo.rlim_cur != 0) { + meminfo["stack"] = (int64_t)stackinfo.rlim_cur; + } + + return meminfo; +} + Error OS_Unix::execute(const String &p_path, const List &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { #ifdef __EMSCRIPTEN__ // Don't compile this code at all to avoid undefined references. diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index 03429622ae34..86b0e38e9286 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -73,6 +73,8 @@ public: virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override; + virtual Dictionary get_memory_info() const override; + virtual Error execute(const String &p_path, const List &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 0bebd29f8978..dadc03685bfe 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -463,6 +463,9 @@ def configure(env: "Environment"): else: env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"]) + if platform.system() == "FreeBSD": + env.Append(LINKFLAGS=["-lkvm"]) + ## Cross-compilation # TODO: Support cross-compilation on architectures other than x86. host_is_64_bit = sys.maxsize > 2**32 diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 456240ba2d19..b237c94072e8 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -683,6 +684,43 @@ static void _append_to_pipe(char *p_bytes, int p_size, String *r_pipe, Mutex *p_ } } +Dictionary OS_Windows::get_memory_info() const { + Dictionary meminfo; + + meminfo["physical"] = -1; + meminfo["free"] = -1; + meminfo["available"] = -1; + meminfo["stack"] = -1; + + PERFORMANCE_INFORMATION pref_info; + pref_info.cb = sizeof(pref_info); + GetPerformanceInfo(&pref_info, sizeof(pref_info)); + + typedef void(WINAPI * PGetCurrentThreadStackLimits)(PULONG_PTR, PULONG_PTR); + PGetCurrentThreadStackLimits GetCurrentThreadStackLimits = (PGetCurrentThreadStackLimits)GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetCurrentThreadStackLimits"); + + ULONG_PTR LowLimit = 0; + ULONG_PTR HighLimit = 0; + if (GetCurrentThreadStackLimits) { + GetCurrentThreadStackLimits(&LowLimit, &HighLimit); + } + + if (pref_info.PhysicalTotal * pref_info.PageSize != 0) { + meminfo["physical"] = pref_info.PhysicalTotal * pref_info.PageSize; + } + if (pref_info.PhysicalAvailable * pref_info.PageSize != 0) { + meminfo["free"] = pref_info.PhysicalAvailable * pref_info.PageSize; + } + if (pref_info.CommitLimit * pref_info.PageSize != 0) { + meminfo["available"] = pref_info.CommitLimit * pref_info.PageSize; + } + if (HighLimit - LowLimit != 0) { + meminfo["stack"] = HighLimit - LowLimit; + } + + return meminfo; +} + Error OS_Windows::execute(const String &p_path, const List &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { String path = p_path.replace("/", "\\"); String command = _quote_command_line_argument(path); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 05110c261409..5dee83769b49 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -178,6 +178,8 @@ public: virtual void delay_usec(uint32_t p_usec) const override; virtual uint64_t get_ticks_usec() const override; + virtual Dictionary get_memory_info() const override; + virtual Error execute(const String &p_path, const List &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; virtual Error create_process(const String &p_path, const List &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override;