summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/llmemory.cpp119
-rw-r--r--indra/llcommon/llmemory.h17
-rw-r--r--indra/llcommon/llsys.cpp80
-rw-r--r--indra/llcommon/llsys.h9
-rw-r--r--indra/llcommon/llwatchdog.cpp5
-rw-r--r--indra/llcommon/llwatchdog.h2
7 files changed, 188 insertions, 46 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 0aaa7433ce..1f7b3163c6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -303,7 +303,7 @@ elseif ($ENV{MSYSTEM_CARCH} MATCHES aarch64)
target_include_directories(llcommon PUBLIC ${prefix_result}/../include/sse2neon)
endif ()
-if (${LINUX_DISTRO} MATCHES debian OR (${LINUX_DISTRO} MATCHES ubuntu) OR (${LINUX_DISTRO} MATCHES opensuse-tumbleweed))
+if (${LINUX_DISTRO} MATCHES debian OR (${LINUX_DISTRO} MATCHES ubuntu) OR (${LINUX_DISTRO} MATCHES opensuse-tumbleweed) OR CMAKE_OSX_ARCHITECTURES MATCHES x86_64 OR ($ENV{MSYSTEM_CARCH} MATCHES aarch64))
target_include_directories(llcommon PUBLIC ${LIBS_PREBUILT_DIR}/include)
endif ()
diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp
index ba48319a16..4e44b9a56a 100644
--- a/indra/llcommon/llmemory.cpp
+++ b/indra/llcommon/llmemory.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -52,6 +52,9 @@
//static
+// On windows commit charge information is vital for OOM diagnosis.
+U32Megabytes LLMemory::sAvailCommitMemInMB(U32_MAX);
+
// most important memory metric for texture streaming
// On Windows, this should agree with resource monitor -> performance -> memory -> available
// On OS X, this should be activity monitor -> memory -> (physical memory - memory used)
@@ -70,6 +73,12 @@ U32Kilobytes LLMemory::sAllocatedMemInKB(0);
U32Kilobytes LLMemory::sAllocatedPageSizeInKB(0);
+LLFrameTimer LLMemory::sMemoryCheckTimer;
+F32 LLMemory::sSysMemoryFactor = 1.f;
+U32 LLMemory::sFactorLastFrameCount = 0;
+
+static const S32Megabytes MEM_LOW_THRESHOLD = S32Megabytes(256);
+
static LLTrace::SampleStatHandle<F64Megabytes> sAllocatedMem("allocated_mem", "active memory in use by application");
static LLTrace::SampleStatHandle<F64Megabytes> sVirtualMem("virtual_mem", "virtual memory assigned to application");
@@ -102,23 +111,14 @@ void LLMemory::updateMemoryInfo()
{
LL_PROFILE_ZONE_SCOPED;
+ sMemoryCheckTimer.reset();
sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB();
- U32Kilobytes avail_mem;
- LLMemoryInfo::getAvailableMemoryKB(avail_mem);
- sAvailPhysicalMemInKB = avail_mem;
+ LLMemoryInfo::updateAvailableMemory();
#if LL_WINDOWS
- PROCESS_MEMORY_COUNTERS counters;
-
- if (!GetProcessMemoryInfo(GetCurrentProcess(), &counters, sizeof(counters)))
- {
- LL_WARNS() << "GetProcessMemoryInfo failed" << LL_ENDL;
- return ;
- }
-
- sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(counters.WorkingSetSize));
- sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(counters.PagefileUsage));
+ // On windows updateAvailableMemory fills sAvailPhysicalMemInKB,
+ //sAllocatedMemInKB and sAllocatedPageSizeInKB
sample(sVirtualMem, sAllocatedPageSizeInKB);
#elif defined(LL_DARWIN)
@@ -201,6 +201,97 @@ void LLMemory::logMemoryInfo(bool update)
LL_INFOS() << llformat("Current max usable memory: %.2f MB", sMaxPhysicalMemInKB / 1024.0) << LL_ENDL;
}
+void LLMemory::updateFreeSystemMemory()
+{
+ if (sMemoryCheckTimer.getElapsedTimeF32() >= 1.f) //once per second.
+ {
+ LLMemory::updateMemoryInfo(); // resets the timer
+ }
+}
+
+F32 LLMemory::getSystemMemoryBudgetFactor()
+{
+ // Only update once per frame
+ U32 current_frame = LLFrameTimer::getFrameCount();
+ if (sFactorLastFrameCount == current_frame)
+ {
+ return sSysMemoryFactor;
+ }
+ sFactorLastFrameCount = current_frame;
+
+ updateFreeSystemMemory();
+#if LL_WINDOWS
+ S32Megabytes free_sys_mem = getAvailableCommitMemMB();
+#else
+ S32Megabytes free_sys_mem = getAvailableMemKB();
+#endif
+ bool is_sys_low = free_sys_mem < MEM_LOW_THRESHOLD;
+ static bool was_low = false;
+
+ // sSysMemoryFactor affects draw distance
+ //
+ // We only decrement when more than 406MB is free, but increment
+ // when below 256MB free. This should provide a stable value
+ // in the 256-406MB range to avoid draw range fluctuations.
+ //
+ // Draw range reduction is a last resort, texture bias is supposed
+ // to free at least some memory before we get here.
+ // Note: textures were mostly moved to vram, we might want to
+ // detach texture bias from system memory.
+ if (is_sys_low)
+ {
+ // debt is a negative value since MIN_FREE_MAIN_MEMORY > free memory.
+ S32Megabytes sys_budget_debt = free_sys_mem - MEM_LOW_THRESHOLD;
+
+ // Leave some padding, otherwise we will crash out of memory before hitting factor 2.
+ const S32Megabytes PAD_BUFFER(32);
+ S32Megabytes budget_target = MEM_LOW_THRESHOLD - PAD_BUFFER;
+ if (!was_low)
+ {
+ // Result should range from 1 at 0 debt to 2 at -224 debt, 2.14 at -256MB
+ F32 new_factor = 1.f - (F32)sys_budget_debt.value() / (F32)budget_target.value();
+ sSysMemoryFactor = llmax(sSysMemoryFactor, new_factor);
+ }
+ else
+ {
+ // Slowly ramp up factor to free memory (increasing factor decreases draw range)
+ constexpr F32 MAX_INCREMENT = 0.05f;
+ F32 increment = MAX_INCREMENT * llmax(-(F32)sys_budget_debt.value() / (F32)budget_target.value(), 0.f);
+ sSysMemoryFactor += increment * LLFrameTimer::getFrameDeltaTimeF32();
+ }
+ sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f);
+ }
+ else
+ {
+ // Only start ramping down when we have breathing room.
+ // This should be under the value of isSystemMemoryLow to not throw texture
+ // bias into 1.5+ territory each time we fluctuate around isSystemMemoryLow's
+ // threshold.
+ const S32Megabytes MEM_THRESHOLD = MEM_LOW_THRESHOLD + S32Megabytes(150);
+ if (free_sys_mem > MEM_THRESHOLD && sSysMemoryFactor > 1.f)
+ {
+ // Ramp down factor over time.
+ constexpr F32 DECREMENT = 0.02f;
+ sSysMemoryFactor -= DECREMENT * LLFrameTimer::getFrameDeltaTimeF32();
+ sSysMemoryFactor = llclamp(sSysMemoryFactor, 1.f, 2.f);
+ }
+ }
+ was_low = is_sys_low;
+
+ return sSysMemoryFactor;
+}
+
+#if LL_WINDOWS
+//static
+U32Megabytes LLMemory::getAvailableCommitMemMB()
+{
+ // Commit charge combines page file and ram,
+ // theoretical limit is 128TB on 64bit windows.
+ // Store as MB instead of KB to prevent overflow.
+ return sAvailCommitMemInMB;
+}
+#endif
+
//static
U32Kilobytes LLMemory::getAvailableMemKB()
{
diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h
index c06ae93766..5ac114cac9 100644
--- a/indra/llcommon/llmemory.h
+++ b/indra/llcommon/llmemory.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -27,6 +27,7 @@
#define LLMEMORY_H
#include "linden_common.h"
+#include "llframetimer.h"
#include "llunits.h"
#include "stdtypes.h"
#if !LL_WINDOWS
@@ -428,17 +429,31 @@ public:
static void initMaxHeapSizeGB(F32Gigabytes max_heap_size);
static void updateMemoryInfo() ;
static void logMemoryInfo(bool update = false);
+ static F32 getSystemMemoryBudgetFactor();
+#if LL_WINDOWS
+ // Commit charge is a Windows-only concept, combines page file and ram
+ static U32Megabytes getAvailableCommitMemMB();
+#endif
static U32Kilobytes getAvailableMemKB() ;
static U32Kilobytes getMaxMemKB() ;
static U32Kilobytes getAllocatedMemKB() ;
private:
+ static void updateFreeSystemMemory();
+ // LLMemoryInfo directly updates memory stats
+ friend class LLMemoryInfo;
+
+ static U32Megabytes sAvailCommitMemInMB;
static U32Kilobytes sAvailPhysicalMemInKB ;
static U32Kilobytes sMaxPhysicalMemInKB ;
static U32Kilobytes sAllocatedMemInKB;
static U32Kilobytes sAllocatedPageSizeInKB ;
static U32Kilobytes sMaxHeapSizeInKB;
+
+ static LLFrameTimer sMemoryCheckTimer;
+ static F32 sSysMemoryFactor;
+ static U32 sFactorLastFrameCount;
};
// LLRefCount moved to llrefcount.h
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp
index 542de4ee67..452acf4322 100644
--- a/indra/llcommon/llsys.cpp
+++ b/indra/llcommon/llsys.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -43,6 +43,7 @@
#include "llerrorcontrol.h"
#include "llevents.h"
#include "llformat.h"
+#include "llmemory.h"
#include "llregex.h"
#include "lltimer.h"
#include "llsdserialize.h"
@@ -811,15 +812,13 @@ U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
}
//static
-void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb)
+void LLMemoryInfo::updateAvailableMemory()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY;
#if LL_WINDOWS
- // Sigh, this shouldn't be a static method, then we wouldn't have to
- // reload this data separately from refresh()
- LLSD statsMap(loadStatsMap());
-
- avail_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger();
+ // On windows loadStatsMap will fill sAvailPhysicalMemInKB,
+ // sAvailCommitMemInMB, sAllocatedMemInKB and sAllocatedPageSizeInKB
+ loadStatsMap();
#elif LL_DARWIN
// use host_statistics64 to get memory info
@@ -831,11 +830,11 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb)
kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast<host_info_t>(&vmstat), &count);
if (result == KERN_SUCCESS)
{
- avail_mem_kb = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size);
+ LLMemory::sAvailPhysicalMemInKB = U64Bytes((vmstat.free_count + vmstat.inactive_count) * page_size);
}
else
{
- avail_mem_kb = (U32Kilobytes)-1;
+ LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1;
}
#elif LL_LINUX
@@ -889,12 +888,12 @@ void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_mem_kb)
// (could also run 'free', but easier to read a file than run a program)
LLSD statsMap(loadStatsMap());
- avail_mem_kb = (U32Kilobytes)statsMap["MemFree"].asInteger();
+ LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)statsMap["MemFree"].asInteger();
#else
//do not know how to collect available memory info for other systems.
//leave it blank here for now.
- avail_mem_kb = (U32Kilobytes)-1 ;
+ LLMemory::sAvailPhysicalMemInKB = (U32Kilobytes)-1 ;
#endif
}
@@ -965,15 +964,24 @@ LLSD LLMemoryInfo::loadStatsMap()
state.dwLength = sizeof(state);
GlobalMemoryStatusEx(&state);
- DWORDLONG div = 1024;
+ static constexpr DWORDLONG div = 1024;
- stats.add("Percent Memory use", state.dwMemoryLoad/div);
+ stats.add("Percent Memory use", state.dwMemoryLoad);
stats.add("Total Physical KB", state.ullTotalPhys/div);
stats.add("Avail Physical KB", state.ullAvailPhys/div);
+
+ // Despite the confusing naming "PageFile" , these values
+ // actually represent the committed memory limit for
+ // the system or the current process, whichever is smaller.
stats.add("Total page KB", state.ullTotalPageFile/div);
stats.add("Avail page KB", state.ullAvailPageFile/div);
- stats.add("Total Virtual KB", state.ullTotalVirtual/div);
- stats.add("Avail Virtual KB", state.ullAvailVirtual/div);
+
+ static constexpr DWORDLONG mb_div = 1024 * 1024;
+ stats.add("Total Virtual MB", state.ullTotalVirtual/mb_div); // ~134 million MB
+ stats.add("Avail Virtual MB", state.ullAvailVirtual/mb_div);
+
+ LLMemory::sAvailPhysicalMemInKB = U32Kilobytes::convert(U64Bytes(state.ullAvailPhys));
+ LLMemory::sAvailCommitMemInMB = U32Megabytes::convert(U64Bytes(state.ullAvailPageFile));
// SL-12122 - Call to GetPerformanceInfo() was removed here. Took
// on order of 10 ms, causing unacceptable frame time spike every
@@ -989,18 +997,38 @@ LLSD LLMemoryInfo::loadStatsMap()
// specifically accepts PROCESS_MEMORY_COUNTERS*, and since this is a
// classic-C API, PROCESS_MEMORY_COUNTERS_EX isn't a subclass. Cast the
// pointer.
- GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*) &pmem, sizeof(pmem));
+ if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmem, sizeof(pmem)))
+ {
+ LLMemory::sAllocatedMemInKB = U32Kilobytes::convert(U64Bytes(pmem.WorkingSetSize));
+ LLMemory::sAllocatedPageSizeInKB = U32Kilobytes::convert(U64Bytes(pmem.PagefileUsage));
- stats.add("Page Fault Count", pmem.PageFaultCount);
- stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize/div);
- stats.add("WorkingSetSize KB", pmem.WorkingSetSize/div);
- stats.add("QutaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage/div);
- stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage/div);
- stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage/div);
- stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage/div);
- stats.add("PagefileUsage KB", pmem.PagefileUsage/div);
- stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage/div);
- stats.add("PrivateUsage KB", pmem.PrivateUsage/div);
+ stats.add("Page Fault Count", pmem.PageFaultCount);
+ stats.add("PeakWorkingSetSize KB", pmem.PeakWorkingSetSize / div);
+ stats.add("WorkingSetSize KB", pmem.WorkingSetSize / div);
+ stats.add("QuotaPeakPagedPoolUsage KB", pmem.QuotaPeakPagedPoolUsage / div);
+ stats.add("QuotaPagedPoolUsage KB", pmem.QuotaPagedPoolUsage / div);
+ stats.add("QuotaPeakNonPagedPoolUsage KB", pmem.QuotaPeakNonPagedPoolUsage / div);
+ stats.add("QuotaNonPagedPoolUsage KB", pmem.QuotaNonPagedPoolUsage / div);
+ stats.add("PagefileUsage KB", pmem.PagefileUsage / div);
+ stats.add("PeakPagefileUsage KB", pmem.PeakPagefileUsage / div);
+ stats.add("PrivateUsage KB", pmem.PrivateUsage / div);
+ }
+ else
+ {
+ LLMemory::sAllocatedMemInKB = U32Kilobytes(0);
+ LLMemory::sAllocatedPageSizeInKB = U32Kilobytes(0);
+
+ stats.add("Page Fault Count", 0);
+ stats.add("PeakWorkingSetSize KB", 0);
+ stats.add("WorkingSetSize KB", 0);
+ stats.add("QuotaPeakPagedPoolUsage KB", 0);
+ stats.add("QuotaPagedPoolUsage KB", 0);
+ stats.add("QuotaPeakNonPagedPoolUsage KB", 0);
+ stats.add("QuotaNonPagedPoolUsage KB", 0);
+ stats.add("PagefileUsage KB", 0);
+ stats.add("PeakPagefileUsage KB", 0);
+ stats.add("PrivateUsage KB", 0);
+ }
#elif LL_DARWIN
diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h
index c8ff4dd98a..91fa2754ab 100644
--- a/indra/llcommon/llsys.h
+++ b/indra/llcommon/llsys.h
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -134,11 +134,12 @@ public:
static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib.
#endif
- //get the available memory in KiloBytes.
- static void getAvailableMemoryKB(U32Kilobytes& avail_mem_kb);
+ // Updates LLMemory's values.
+ static void updateAvailableMemory();
// Retrieve a map of memory statistics. The keys of the map are platform-
- // dependent. The values are in kilobytes to try to avoid integer overflow.
+ // dependent.
+ // On Windows updates LLMemory values.
LLSD getStatsMap() const;
// Re-fetch memory data (as reported by stream() and getStatsMap()) from the
diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp
index 66b565c763..886f19366c 100644
--- a/indra/llcommon/llwatchdog.cpp
+++ b/indra/llcommon/llwatchdog.cpp
@@ -116,6 +116,11 @@ bool LLWatchdogTimeout::isAlive() const
return (mTimer.getStarted() && !mTimer.hasExpired());
}
+bool LLWatchdogTimeout::started() const
+{
+ return mTimer.getStarted();
+}
+
void LLWatchdogTimeout::reset()
{
mTimer.setTimerExpirySec(mTimeout);
diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h
index f138fbccb0..d55bf434f3 100644
--- a/indra/llcommon/llwatchdog.h
+++ b/indra/llcommon/llwatchdog.h
@@ -47,6 +47,7 @@ public:
// This may mean that resources used by
// isAlive and other method may need synchronization.
virtual bool isAlive() const = 0;
+ virtual bool started() const = 0;
virtual void reset() = 0;
virtual void start();
virtual void stop();
@@ -66,6 +67,7 @@ public:
virtual ~LLWatchdogTimeout();
bool isAlive() const override;
+ bool started() const override;
void reset() override;
void start() override { start(""); }
void stop() override;