LLSys merge and cleanup.
This commit is contained in:
@@ -24,6 +24,10 @@
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
|
||||
#endif
|
||||
|
||||
#include "linden_common.h"
|
||||
|
||||
#include "llsys.h"
|
||||
@@ -36,22 +40,45 @@
|
||||
#endif
|
||||
|
||||
#include "llprocessor.h"
|
||||
#include "llerrorcontrol.h"
|
||||
#include "llevents.h"
|
||||
#include "lltimer.h"
|
||||
#include "llsdserialize.h"
|
||||
#include "llsdutil.h"
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/range.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <boost/type_traits/is_integral.hpp>
|
||||
#include <boost/type_traits/is_float.hpp>
|
||||
|
||||
using namespace llsd;
|
||||
|
||||
#if LL_WINDOWS
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <winsock2.h>
|
||||
# include <windows.h>
|
||||
# include <psapi.h> // GetPerformanceInfo() et al.
|
||||
#elif LL_DARWIN
|
||||
# include <errno.h>
|
||||
# include <sys/sysctl.h>
|
||||
# include <sys/utsname.h>
|
||||
# include <stdint.h>
|
||||
# include <Carbon/Carbon.h>
|
||||
# include <stdexcept>
|
||||
# include <mach/host_info.h>
|
||||
# include <mach/mach_host.h>
|
||||
# include <mach/task.h>
|
||||
# include <mach/task_info.h>
|
||||
#elif LL_LINUX
|
||||
# include <errno.h>
|
||||
# include <sys/utsname.h>
|
||||
# include <unistd.h>
|
||||
# include <sys/sysinfo.h>
|
||||
# include <stdexcept>
|
||||
const char MEMINFO_FILE[] = "/proc/meminfo";
|
||||
#elif LL_SOLARIS
|
||||
# include <stdio.h>
|
||||
@@ -70,6 +97,15 @@ extern int errno;
|
||||
static const S32 CPUINFO_BUFFER_SIZE = 16383;
|
||||
LLCPUInfo gSysCPU;
|
||||
|
||||
// Don't log memory info any more often than this. It also serves as our
|
||||
// framerate sample size.
|
||||
static const F32 MEM_INFO_THROTTLE = 20;
|
||||
// Sliding window of samples. We intentionally limit the length of time we
|
||||
// remember "the slowest" framerate because framerate is very slow at login.
|
||||
// If we only triggered FrameWatcher logging when the session framerate
|
||||
// dropped below the login framerate, we'd have very little additional data.
|
||||
static const F32 MEM_INFO_WINDOW = 10*60;
|
||||
|
||||
#if LL_WINDOWS
|
||||
#ifndef DLLVERSIONINFO
|
||||
typedef struct _DllVersionInfo
|
||||
@@ -572,6 +608,7 @@ LLCPUInfo::LLCPUInfo()
|
||||
out << " (" << mCPUMHz << " MHz)";
|
||||
}
|
||||
mCPUString = out.str();
|
||||
LLStringUtil::trim(mCPUString);
|
||||
}
|
||||
|
||||
bool LLCPUInfo::hasAltivec() const
|
||||
@@ -613,8 +650,78 @@ void LLCPUInfo::stream(std::ostream& s) const
|
||||
s << "->mCPUString: " << mCPUString << std::endl;
|
||||
}
|
||||
|
||||
// Helper class for LLMemoryInfo: accumulate stats in the form we store for
|
||||
// LLMemoryInfo::getStatsMap().
|
||||
class Stats
|
||||
{
|
||||
public:
|
||||
Stats():
|
||||
mStats(LLSD::emptyMap())
|
||||
{}
|
||||
|
||||
// Store every integer type as LLSD::Integer.
|
||||
template <class T>
|
||||
void add(const LLSD::String& name, const T& value,
|
||||
typename boost::enable_if<boost::is_integral<T> >::type* = 0)
|
||||
{
|
||||
mStats[name] = LLSD::Integer(value);
|
||||
}
|
||||
|
||||
// Store every floating-point type as LLSD::Real.
|
||||
template <class T>
|
||||
void add(const LLSD::String& name, const T& value,
|
||||
typename boost::enable_if<boost::is_float<T> >::type* = 0)
|
||||
{
|
||||
mStats[name] = LLSD::Real(value);
|
||||
}
|
||||
|
||||
// Hope that LLSD::Date values are sufficiently unambiguous.
|
||||
void add(const LLSD::String& name, const LLSD::Date& value)
|
||||
{
|
||||
mStats[name] = value;
|
||||
}
|
||||
|
||||
LLSD get() const { return mStats; }
|
||||
|
||||
private:
|
||||
LLSD mStats;
|
||||
};
|
||||
|
||||
// Wrap boost::regex_match() with a function that doesn't throw.
|
||||
template <typename S, typename M, typename R>
|
||||
static bool regex_match_no_exc(const S& string, M& match, const R& regex)
|
||||
{
|
||||
try
|
||||
{
|
||||
return boost::regex_match(string, match, regex);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "error matching with '" << regex.str() << "': "
|
||||
<< e.what() << ":\n'" << string << "'" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap boost::regex_search() with a function that doesn't throw.
|
||||
template <typename S, typename M, typename R>
|
||||
static bool regex_search_no_exc(const S& string, M& match, const R& regex)
|
||||
{
|
||||
try
|
||||
{
|
||||
return boost::regex_search(string, match, regex);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "error searching with '" << regex.str() << "': "
|
||||
<< e.what() << ":\n'" << string << "'" << LL_ENDL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LLMemoryInfo::LLMemoryInfo()
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
||||
#if LL_WINDOWS
|
||||
@@ -638,11 +745,7 @@ static U32 LLMemoryAdjustKBResult(U32 inKB)
|
||||
U32 LLMemoryInfo::getPhysicalMemoryKB() const
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
MEMORYSTATUSEX state;
|
||||
state.dwLength = sizeof(state);
|
||||
GlobalMemoryStatusEx(&state);
|
||||
|
||||
return LLMemoryAdjustKBResult((U32)(state.ullTotalPhys >> 10));
|
||||
return LLMemoryAdjustKBResult(mStatsMap["Total Physical KB"].asInteger());
|
||||
|
||||
#elif LL_DARWIN
|
||||
// This might work on Linux as well. Someone check...
|
||||
@@ -690,12 +793,82 @@ U32 LLMemoryInfo::getPhysicalMemoryClamped() const
|
||||
void LLMemoryInfo::getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_virtual_mem_kb)
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
MEMORYSTATUSEX state;
|
||||
state.dwLength = sizeof(state);
|
||||
GlobalMemoryStatusEx(&state);
|
||||
// Sigh, this shouldn't be a static method, then we wouldn't have to
|
||||
// reload this data separately from refresh()
|
||||
LLSD statsMap(loadStatsMap());
|
||||
|
||||
avail_physical_mem_kb = (U32)(state.ullAvailPhys/1024) ;
|
||||
avail_virtual_mem_kb = (U32)(state.ullAvailVirtual/1024) ;
|
||||
avail_physical_mem_kb = statsMap["Avail Physical KB"].asInteger();
|
||||
avail_virtual_mem_kb = statsMap["Avail Virtual KB"].asInteger();
|
||||
|
||||
#elif LL_DARWIN
|
||||
// mStatsMap is derived from vm_stat, look for (e.g.) "kb free":
|
||||
// $ vm_stat
|
||||
// Mach Virtual Memory Statistics: (page size of 4096 bytes)
|
||||
// Pages free: 462078.
|
||||
// Pages active: 142010.
|
||||
// Pages inactive: 220007.
|
||||
// Pages wired down: 159552.
|
||||
// "Translation faults": 220825184.
|
||||
// Pages copy-on-write: 2104153.
|
||||
// Pages zero filled: 167034876.
|
||||
// Pages reactivated: 65153.
|
||||
// Pageins: 2097212.
|
||||
// Pageouts: 41759.
|
||||
// Object cache: 841598 hits of 7629869 lookups (11% hit rate)
|
||||
avail_physical_mem_kb = -1 ;
|
||||
avail_virtual_mem_kb = -1 ;
|
||||
|
||||
#elif LL_LINUX
|
||||
// mStatsMap is derived from MEMINFO_FILE:
|
||||
// $ cat /proc/meminfo
|
||||
// MemTotal: 4108424 kB
|
||||
// MemFree: 1244064 kB
|
||||
// Buffers: 85164 kB
|
||||
// Cached: 1990264 kB
|
||||
// SwapCached: 0 kB
|
||||
// Active: 1176648 kB
|
||||
// Inactive: 1427532 kB
|
||||
// Active(anon): 529152 kB
|
||||
// Inactive(anon): 15924 kB
|
||||
// Active(file): 647496 kB
|
||||
// Inactive(file): 1411608 kB
|
||||
// Unevictable: 16 kB
|
||||
// Mlocked: 16 kB
|
||||
// HighTotal: 3266316 kB
|
||||
// HighFree: 721308 kB
|
||||
// LowTotal: 842108 kB
|
||||
// LowFree: 522756 kB
|
||||
// SwapTotal: 6384632 kB
|
||||
// SwapFree: 6384632 kB
|
||||
// Dirty: 28 kB
|
||||
// Writeback: 0 kB
|
||||
// AnonPages: 528820 kB
|
||||
// Mapped: 89472 kB
|
||||
// Shmem: 16324 kB
|
||||
// Slab: 159624 kB
|
||||
// SReclaimable: 145168 kB
|
||||
// SUnreclaim: 14456 kB
|
||||
// KernelStack: 2560 kB
|
||||
// PageTables: 5560 kB
|
||||
// NFS_Unstable: 0 kB
|
||||
// Bounce: 0 kB
|
||||
// WritebackTmp: 0 kB
|
||||
// CommitLimit: 8438844 kB
|
||||
// Committed_AS: 1271596 kB
|
||||
// VmallocTotal: 122880 kB
|
||||
// VmallocUsed: 65252 kB
|
||||
// VmallocChunk: 52356 kB
|
||||
// HardwareCorrupted: 0 kB
|
||||
// HugePages_Total: 0
|
||||
// HugePages_Free: 0
|
||||
// HugePages_Rsvd: 0
|
||||
// HugePages_Surp: 0
|
||||
// Hugepagesize: 2048 kB
|
||||
// DirectMap4k: 434168 kB
|
||||
// DirectMap2M: 477184 kB
|
||||
// (could also run 'free', but easier to read a file than run a program)
|
||||
avail_physical_mem_kb = -1 ;
|
||||
avail_virtual_mem_kb = -1 ;
|
||||
|
||||
#else
|
||||
//do not know how to collect available memory info for other systems.
|
||||
@@ -708,56 +881,285 @@ void LLMemoryInfo::getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_v
|
||||
|
||||
void LLMemoryInfo::stream(std::ostream& s) const
|
||||
{
|
||||
// We want these memory stats to be easy to grep from the log, along with
|
||||
// the timestamp. So preface each line with the timestamp and a
|
||||
// distinctive marker. Without that, we'd have to search the log for the
|
||||
// introducer line, then read subsequent lines, etc...
|
||||
std::string pfx(LLError::utcTime() + " <mem> ");
|
||||
|
||||
// Max key length
|
||||
size_t key_width(0);
|
||||
BOOST_FOREACH(const MapEntry& pair, inMap(mStatsMap))
|
||||
{
|
||||
size_t len(pair.first.length());
|
||||
if (len > key_width)
|
||||
{
|
||||
key_width = len;
|
||||
}
|
||||
}
|
||||
|
||||
// Now stream stats
|
||||
BOOST_FOREACH(const MapEntry& pair, inMap(mStatsMap))
|
||||
{
|
||||
s << pfx << std::setw(key_width+1) << (pair.first + ':') << ' ';
|
||||
LLSD value(pair.second);
|
||||
if (value.isInteger())
|
||||
s << std::setw(12) << value.asInteger();
|
||||
else if (value.isReal())
|
||||
s << std::fixed << std::setprecision(1) << value.asReal();
|
||||
else if (value.isDate())
|
||||
value.asDate().toStream(s);
|
||||
else
|
||||
s << value; // just use default LLSD formatting
|
||||
s << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
LLSD LLMemoryInfo::getStatsMap() const
|
||||
{
|
||||
return mStatsMap;
|
||||
}
|
||||
|
||||
LLMemoryInfo& LLMemoryInfo::refresh()
|
||||
{
|
||||
mStatsMap = loadStatsMap();
|
||||
|
||||
LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n";
|
||||
LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT);
|
||||
LL_ENDL;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
LLSD LLMemoryInfo::loadStatsMap()
|
||||
{
|
||||
// This implementation is derived from stream() code (as of 2011-06-29).
|
||||
Stats stats;
|
||||
|
||||
// associate timestamp for analysis over time
|
||||
stats.add("timestamp", LLDate::now());
|
||||
|
||||
#if LL_WINDOWS
|
||||
MEMORYSTATUSEX state;
|
||||
state.dwLength = sizeof(state);
|
||||
GlobalMemoryStatusEx(&state);
|
||||
|
||||
s << "Percent Memory use: " << (U32)state.dwMemoryLoad << '%' << std::endl;
|
||||
s << "Total Physical KB: " << (U32)(state.ullTotalPhys/1024) << std::endl;
|
||||
s << "Avail Physical KB: " << (U32)(state.ullAvailPhys/1024) << std::endl;
|
||||
s << "Total page KB: " << (U32)(state.ullTotalPageFile/1024) << std::endl;
|
||||
s << "Avail page KB: " << (U32)(state.ullAvailPageFile/1024) << std::endl;
|
||||
s << "Total Virtual KB: " << (U32)(state.ullTotalVirtual/1024) << std::endl;
|
||||
s << "Avail Virtual KB: " << (U32)(state.ullAvailVirtual/1024) << std::endl;
|
||||
DWORDLONG div = 1024;
|
||||
|
||||
stats.add("Percent Memory use", state.dwMemoryLoad/div);
|
||||
stats.add("Total Physical KB", state.ullTotalPhys/div);
|
||||
stats.add("Avail Physical KB", state.ullAvailPhys/div);
|
||||
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);
|
||||
|
||||
PERFORMANCE_INFORMATION perf;
|
||||
perf.cb = sizeof(perf);
|
||||
GetPerformanceInfo(&perf, sizeof(perf));
|
||||
|
||||
SIZE_T pagekb(perf.PageSize/1024);
|
||||
stats.add("CommitTotal KB", perf.CommitTotal * pagekb);
|
||||
stats.add("CommitLimit KB", perf.CommitLimit * pagekb);
|
||||
stats.add("CommitPeak KB", perf.CommitPeak * pagekb);
|
||||
stats.add("PhysicalTotal KB", perf.PhysicalTotal * pagekb);
|
||||
stats.add("PhysicalAvail KB", perf.PhysicalAvailable * pagekb);
|
||||
stats.add("SystemCache KB", perf.SystemCache * pagekb);
|
||||
stats.add("KernelTotal KB", perf.KernelTotal * pagekb);
|
||||
stats.add("KernelPaged KB", perf.KernelPaged * pagekb);
|
||||
stats.add("KernelNonpaged KB", perf.KernelNonpaged * pagekb);
|
||||
stats.add("PageSize KB", pagekb);
|
||||
stats.add("HandleCount", perf.HandleCount);
|
||||
stats.add("ProcessCount", perf.ProcessCount);
|
||||
stats.add("ThreadCount", perf.ThreadCount);
|
||||
|
||||
PROCESS_MEMORY_COUNTERS_EX pmem;
|
||||
pmem.cb = sizeof(pmem);
|
||||
// GetProcessMemoryInfo() is documented to accept either
|
||||
// PROCESS_MEMORY_COUNTERS* or PROCESS_MEMORY_COUNTERS_EX*, presumably
|
||||
// using the redundant size info to distinguish. But its prototype
|
||||
// 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(), PPROCESS_MEMORY_COUNTERS(&pmem), sizeof(pmem));
|
||||
|
||||
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);
|
||||
|
||||
#elif LL_DARWIN
|
||||
uint64_t phys = 0;
|
||||
|
||||
size_t len = sizeof(phys);
|
||||
const vm_size_t pagekb(vm_page_size / 1024);
|
||||
|
||||
//
|
||||
// Collect the vm_stat's
|
||||
//
|
||||
|
||||
if(sysctlbyname("hw.memsize", &phys, &len, NULL, 0) == 0)
|
||||
{
|
||||
s << "Total Physical KB: " << phys/1024 << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
s << "Unable to collect memory information";
|
||||
}
|
||||
#elif LL_SOLARIS
|
||||
U64 phys = 0;
|
||||
vm_statistics_data_t vmstat;
|
||||
mach_msg_type_number_t vmstatCount = HOST_VM_INFO_COUNT;
|
||||
|
||||
phys = (U64)(sysconf(_SC_PHYS_PAGES)) * (U64)(sysconf(_SC_PAGESIZE)/1024);
|
||||
|
||||
s << "Total Physical KB: " << phys << std::endl;
|
||||
#else
|
||||
// *NOTE: This works on linux. What will it do on other systems?
|
||||
LLFILE* meminfo = LLFile::fopen(MEMINFO_FILE,"rb");
|
||||
if(meminfo)
|
||||
if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t) &vmstat, &vmstatCount) != KERN_SUCCESS)
|
||||
{
|
||||
char line[MAX_STRING]; /* Flawfinder: ignore */
|
||||
memset(line, 0, MAX_STRING);
|
||||
while(fgets(line, MAX_STRING, meminfo))
|
||||
{
|
||||
line[strlen(line)-1] = ' '; /*Flawfinder: ignore*/
|
||||
s << line;
|
||||
LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.add("Pages free KB", pagekb * vmstat.free_count);
|
||||
stats.add("Pages active KB", pagekb * vmstat.active_count);
|
||||
stats.add("Pages inactive KB", pagekb * vmstat.inactive_count);
|
||||
stats.add("Pages wired KB", pagekb * vmstat.wire_count);
|
||||
|
||||
stats.add("Pages zero fill", vmstat.zero_fill_count);
|
||||
stats.add("Page reactivations", vmstat.reactivations);
|
||||
stats.add("Page-ins", vmstat.pageins);
|
||||
stats.add("Page-outs", vmstat.pageouts);
|
||||
|
||||
stats.add("Faults", vmstat.faults);
|
||||
stats.add("Faults copy-on-write", vmstat.cow_faults);
|
||||
|
||||
stats.add("Cache lookups", vmstat.lookups);
|
||||
stats.add("Cache hits", vmstat.hits);
|
||||
|
||||
stats.add("Page purgeable count", vmstat.purgeable_count);
|
||||
stats.add("Page purges", vmstat.purges);
|
||||
|
||||
stats.add("Page speculative reads", vmstat.speculative_count);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Collect the misc task info
|
||||
//
|
||||
|
||||
{
|
||||
task_events_info_data_t taskinfo;
|
||||
unsigned taskinfoSize = sizeof(taskinfo);
|
||||
|
||||
if (task_info(mach_task_self(), TASK_EVENTS_INFO, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS)
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.add("Task page-ins", taskinfo.pageins);
|
||||
stats.add("Task copy-on-write faults", taskinfo.cow_faults);
|
||||
stats.add("Task messages sent", taskinfo.messages_sent);
|
||||
stats.add("Task messages received", taskinfo.messages_received);
|
||||
stats.add("Task mach system call count", taskinfo.syscalls_mach);
|
||||
stats.add("Task unix system call count", taskinfo.syscalls_unix);
|
||||
stats.add("Task context switch count", taskinfo.csw);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Collect the basic task info
|
||||
//
|
||||
|
||||
{
|
||||
task_basic_info_64_data_t taskinfo;
|
||||
unsigned taskinfoSize = sizeof(taskinfo);
|
||||
|
||||
if (task_info(mach_task_self(), TASK_BASIC_INFO_64, (task_info_t) &taskinfo, &taskinfoSize) != KERN_SUCCESS)
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
stats.add("Basic suspend count", taskinfo.suspend_count);
|
||||
stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024);
|
||||
stats.add("Basic resident memory KB", taskinfo.resident_size / 1024);
|
||||
stats.add("Basic new thread policy", taskinfo.policy);
|
||||
}
|
||||
}
|
||||
|
||||
#elif LL_SOLARIS
|
||||
U64 phys = 0;
|
||||
|
||||
phys = (U64)(sysconf(_SC_PHYS_PAGES)) * (U64)(sysconf(_SC_PAGESIZE)/1024);
|
||||
|
||||
stats.add("Total Physical KB", phys);
|
||||
|
||||
#elif LL_LINUX
|
||||
std::ifstream meminfo(MEMINFO_FILE);
|
||||
if (meminfo.is_open())
|
||||
{
|
||||
// MemTotal: 4108424 kB
|
||||
// MemFree: 1244064 kB
|
||||
// Buffers: 85164 kB
|
||||
// Cached: 1990264 kB
|
||||
// SwapCached: 0 kB
|
||||
// Active: 1176648 kB
|
||||
// Inactive: 1427532 kB
|
||||
// ...
|
||||
// VmallocTotal: 122880 kB
|
||||
// VmallocUsed: 65252 kB
|
||||
// VmallocChunk: 52356 kB
|
||||
// HardwareCorrupted: 0 kB
|
||||
// HugePages_Total: 0
|
||||
// HugePages_Free: 0
|
||||
// HugePages_Rsvd: 0
|
||||
// HugePages_Surp: 0
|
||||
// Hugepagesize: 2048 kB
|
||||
// DirectMap4k: 434168 kB
|
||||
// DirectMap2M: 477184 kB
|
||||
|
||||
// Intentionally don't pass the boost::no_except flag. This
|
||||
// boost::regex object is constructed with a string literal, so it
|
||||
// should be valid every time. If it becomes invalid, we WANT an
|
||||
// exception, hopefully even before the dev checks in.
|
||||
boost::regex stat_rx("(.+): +([0-9]+)( kB)?");
|
||||
boost::smatch matched;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(meminfo, line))
|
||||
{
|
||||
LL_DEBUGS("LLMemoryInfo") << line << LL_ENDL;
|
||||
if (regex_match_no_exc(line, matched, stat_rx))
|
||||
{
|
||||
// e.g. "MemTotal: 4108424 kB"
|
||||
LLSD::String key(matched[1].first, matched[1].second);
|
||||
LLSD::String value_str(matched[2].first, matched[2].second);
|
||||
LLSD::Integer value(0);
|
||||
try
|
||||
{
|
||||
value = boost::lexical_cast<LLSD::Integer>(value_str);
|
||||
}
|
||||
catch (const boost::bad_lexical_cast&)
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "couldn't parse '" << value_str
|
||||
<< "' in " << MEMINFO_FILE << " line: "
|
||||
<< line << LL_ENDL;
|
||||
continue;
|
||||
}
|
||||
// Store this statistic.
|
||||
stats.add(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("LLMemoryInfo") << "unrecognized " << MEMINFO_FILE << " line: "
|
||||
<< line << LL_ENDL;
|
||||
}
|
||||
}
|
||||
fclose(meminfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
s << "Unable to collect memory information";
|
||||
LL_WARNS("LLMemoryInfo") << "Unable to collect memory information" << LL_ENDL;
|
||||
}
|
||||
|
||||
#else
|
||||
LL_WARNS("LLMemoryInfo") << "Unknown system; unable to collect memory information" << LL_ENDL;
|
||||
|
||||
#endif
|
||||
|
||||
return stats.get();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, const LLOSInfo& info)
|
||||
@@ -778,6 +1180,143 @@ std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info)
|
||||
return s;
|
||||
}
|
||||
|
||||
class FrameWatcher
|
||||
{
|
||||
public:
|
||||
FrameWatcher():
|
||||
// Hooking onto the "mainloop" event pump gets us one call per frame.
|
||||
mConnection(LLEventPumps::instance()
|
||||
.obtain("mainloop")
|
||||
.listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))),
|
||||
// Initializing mSampleStart to an invalid timestamp alerts us to skip
|
||||
// trying to compute framerate on the first call.
|
||||
mSampleStart(-1),
|
||||
// Initializing mSampleEnd to 0 ensures that we treat the first call
|
||||
// as the completion of a sample window.
|
||||
mSampleEnd(0),
|
||||
mFrames(0),
|
||||
// Both MEM_INFO_WINDOW and MEM_INFO_THROTTLE are in seconds. We need
|
||||
// the number of integer MEM_INFO_THROTTLE sample slots that will fit
|
||||
// in MEM_INFO_WINDOW. Round up.
|
||||
mSamples(int((MEM_INFO_WINDOW / MEM_INFO_THROTTLE) + 0.7)),
|
||||
// Initializing to F32_MAX means that the first real frame will become
|
||||
// the slowest ever, which sounds like a good idea.
|
||||
mSlowest(F32_MAX)
|
||||
{}
|
||||
|
||||
bool tick(const LLSD&)
|
||||
{
|
||||
F32 timestamp(mTimer.getElapsedTimeF32());
|
||||
|
||||
// Count this frame in the interval just completed.
|
||||
++mFrames;
|
||||
|
||||
// Have we finished a sample window yet?
|
||||
if (timestamp < mSampleEnd)
|
||||
{
|
||||
// no, just keep waiting
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up for next sample window. Capture values for previous frame in
|
||||
// local variables and reset data members.
|
||||
U32 frames(mFrames);
|
||||
F32 sampleStart(mSampleStart);
|
||||
// No frames yet in next window
|
||||
mFrames = 0;
|
||||
// which starts right now
|
||||
mSampleStart = timestamp;
|
||||
// and ends MEM_INFO_THROTTLE seconds in the future
|
||||
mSampleEnd = mSampleStart + MEM_INFO_THROTTLE;
|
||||
|
||||
// On the very first call, that's all we can do, no framerate
|
||||
// computation is possible.
|
||||
if (sampleStart < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// How long did this actually take? As framerate slows, the duration
|
||||
// of the frame we just finished could push us WELL beyond our desired
|
||||
// sample window size.
|
||||
F32 elapsed(timestamp - sampleStart);
|
||||
F32 framerate(frames/elapsed);
|
||||
|
||||
// Remember previous slowest framerate because we're just about to
|
||||
// update it.
|
||||
F32 slowest(mSlowest);
|
||||
// Remember previous number of samples.
|
||||
boost::circular_buffer<F32>::size_type prevSize(mSamples.size());
|
||||
|
||||
// Capture new framerate in our samples buffer. Once the buffer is
|
||||
// full (after MEM_INFO_WINDOW seconds), this will displace the oldest
|
||||
// sample. ("So they all rolled over, and one fell out...")
|
||||
mSamples.push_back(framerate);
|
||||
|
||||
// Calculate the new minimum framerate. I know of no way to update a
|
||||
// rolling minimum without ever rescanning the buffer. But since there
|
||||
// are only a few tens of items in this buffer, rescanning it is
|
||||
// probably cheaper (and certainly easier to reason about) than
|
||||
// attempting to optimize away some of the scans.
|
||||
mSlowest = framerate; // pick an arbitrary entry to start
|
||||
for (boost::circular_buffer<F32>::const_iterator si(mSamples.begin()), send(mSamples.end());
|
||||
si != send; ++si)
|
||||
{
|
||||
if (*si < mSlowest)
|
||||
{
|
||||
mSlowest = *si;
|
||||
}
|
||||
}
|
||||
|
||||
// We're especially interested in memory as framerate drops. Only log
|
||||
// when framerate drops below the slowest framerate we remember.
|
||||
// (Should always be true for the end of the very first sample
|
||||
// window.)
|
||||
if (framerate >= slowest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Congratulations, we've hit a new low. :-P
|
||||
|
||||
LL_INFOS("FrameWatcher") << ' ';
|
||||
if (! prevSize)
|
||||
{
|
||||
LL_CONT << "initial framerate ";
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_CONT << "slowest framerate for last " << int(prevSize * MEM_INFO_THROTTLE)
|
||||
<< " seconds ";
|
||||
}
|
||||
LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n'
|
||||
<< LLMemoryInfo() << LL_ENDL;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Storing the connection in an LLTempBoundListener ensures it will be
|
||||
// disconnected when we're destroyed.
|
||||
LLTempBoundListener mConnection;
|
||||
// Track elapsed time
|
||||
LLTimer mTimer;
|
||||
// Some of what you see here is in fact redundant with functionality you
|
||||
// can get from LLTimer. Unfortunately the LLTimer API is missing the
|
||||
// feature we need: has at least the stated interval elapsed, and if so,
|
||||
// exactly how long has passed? So we have to do it by hand, sigh.
|
||||
// Time at start, end of sample window
|
||||
F32 mSampleStart, mSampleEnd;
|
||||
// Frames this sample window
|
||||
U32 mFrames;
|
||||
// Sliding window of framerate samples
|
||||
boost::circular_buffer<F32> mSamples;
|
||||
// Slowest framerate in mSamples
|
||||
F32 mSlowest;
|
||||
};
|
||||
|
||||
// Need an instance of FrameWatcher before it does any good
|
||||
static FrameWatcher sFrameWatcher;
|
||||
|
||||
BOOL gunzip_file(const std::string& srcfile, const std::string& dstfile)
|
||||
{
|
||||
std::string tmpfile;
|
||||
|
||||
@@ -117,6 +117,27 @@ public:
|
||||
|
||||
//get the available memory infomation in KiloBytes.
|
||||
static void getAvailableMemoryKB(U32& avail_physical_mem_kb, U32& avail_virtual_mem_kb);
|
||||
|
||||
// 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.
|
||||
LLSD getStatsMap() const;
|
||||
|
||||
// Re-fetch memory data (as reported by stream() and getStatsMap()) from the
|
||||
// system. Normally this is fetched at construction time. Return (*this)
|
||||
// to permit usage of the form:
|
||||
// @code
|
||||
// LLMemoryInfo info;
|
||||
// ...
|
||||
// info.refresh().getStatsMap();
|
||||
// @endcode
|
||||
LLMemoryInfo& refresh();
|
||||
|
||||
private:
|
||||
// set mStatsMap
|
||||
static LLSD loadStatsMap();
|
||||
|
||||
// Memory stats for getStatsMap().
|
||||
LLSD mStatsMap;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user