diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/llcommon/llsys.cpp | |
parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) |
Fix line endlings
Diffstat (limited to 'indra/llcommon/llsys.cpp')
-rw-r--r-- | indra/llcommon/llsys.cpp | 2828 |
1 files changed, 1414 insertions, 1414 deletions
diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 02fd0733e5..496ec0d869 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1,1414 +1,1414 @@ -/**
- * @file llsys.cpp
- * @brief Implementation of the basic system query functions.
- *
- * $LicenseInfo:firstyear=2002&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, 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
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#if LL_WINDOWS
-#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
-#endif
-
-#include "linden_common.h"
-
-#include "llsys.h"
-
-#include <iostream>
-#ifdef LL_USESYSTEMLIBS
-# include <zlib.h>
-#else
-# include "zlib-ng/zlib.h"
-#endif
-
-#include "llprocessor.h"
-#include "llerrorcontrol.h"
-#include "llevents.h"
-#include "llformat.h"
-#include "llregex.h"
-#include "lltimer.h"
-#include "llsdserialize.h"
-#include "llsdutil.h"
-#include <boost/bind.hpp>
-#include <boost/circular_buffer.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>
-#include "llfasttimer.h"
-
-using namespace llsd;
-
-#if LL_WINDOWS
-# include "llwin32headerslean.h"
-# include <psapi.h> // GetPerformanceInfo() et al.
-# include <VersionHelpers.h>
-#elif LL_DARWIN
-# include "llsys_objc.h"
-# include <errno.h>
-# include <sys/sysctl.h>
-# include <sys/utsname.h>
-# include <stdint.h>
-# include <CoreServices/CoreServices.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";
-# include <gnu/libc-version.h>
-#endif
-
-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;
-
-LLOSInfo::LLOSInfo() :
- mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("")
-{
-
-#if LL_WINDOWS
-
- if (IsWindows10OrGreater())
- {
- mMajorVer = 10;
- mMinorVer = 0;
- mOSStringSimple = "Microsoft Windows 10 ";
- }
- else if (IsWindows8Point1OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 3;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2012 R2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 8.1 ";
- }
- }
- else if (IsWindows8OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 2;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2012 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 8 ";
- }
- }
- else if (IsWindows7SP1OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 1;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 R2 SP1 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 7 SP1 ";
- }
- }
- else if (IsWindows7OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 1;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 R2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows 7 ";
- }
- }
- else if (IsWindowsVistaSP2OrGreater())
- {
- mMajorVer = 6;
- mMinorVer = 0;
- if (IsWindowsServer())
- {
- mOSStringSimple = "Windows Server 2008 SP2 ";
- }
- else
- {
- mOSStringSimple = "Microsoft Windows Vista SP2 ";
- }
- }
- else
- {
- mOSStringSimple = "Unsupported Windows version ";
- }
-
- ///get native system info if available..
- typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo
- SYSTEM_INFO si; //System Info object file contains architecture info
- PGNSI pGNSI; //pointer object
- ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information
- pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function
- if (NULL != pGNSI) //check if it has failed
- pGNSI(&si); //success
- else
- GetSystemInfo(&si); //if it fails get regular system info
- //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load)
-
- // Try calling GetVersionEx using the OSVERSIONINFOEX structure.
- OSVERSIONINFOEX osvi;
- ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
- if (GetVersionEx((OSVERSIONINFO *)&osvi))
- {
- mBuild = osvi.dwBuildNumber & 0xffff;
- }
- else
- {
- // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
- osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- if (GetVersionEx((OSVERSIONINFO *)&osvi))
- {
- mBuild = osvi.dwBuildNumber & 0xffff;
- }
- }
-
- S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry
- if (mMajorVer == 10)
- {
- DWORD cbData(sizeof(DWORD));
- DWORD data(0);
- HKEY key;
- LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key);
- if (ERROR_SUCCESS == ret_code)
- {
- ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData);
- if (ERROR_SUCCESS == ret_code)
- {
- ubr = data;
- }
- }
-
- if (mBuild >= 22000)
- {
- // At release Windows 11 version was 10.0.22000.194
- // Windows 10 version was 10.0.19043.1266
- // There is no warranty that Win10 build won't increase,
- // so until better solution is found or Microsoft updates
- // SDK with IsWindows11OrGreater(), indicate "10/11"
- //
- // Current alternatives:
- // Query WMI's Win32_OperatingSystem for OS string. Slow
- // and likely to return 'compatibility' string.
- // Check presence of dlls/libs or may be their version.
- mOSStringSimple = "Microsoft Windows 10/11 ";
- }
- }
-
- //msdn microsoft finds 32 bit and 64 bit flavors this way..
- //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors
- //of windows than this code does (in case it is needed for the future)
- if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit
- {
- mOSStringSimple += "64-bit ";
- }
- else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
- {
- mOSStringSimple += "32-bit ";
- }
-
- mOSString = mOSStringSimple;
- if (mBuild > 0)
- {
- mOSString += llformat("(Build %d", mBuild);
- if (ubr > 0)
- {
- mOSString += llformat(".%d", ubr);
- }
- mOSString += ")";
- }
-
- LLStringUtil::trim(mOSStringSimple);
- LLStringUtil::trim(mOSString);
-
-#elif LL_DARWIN
-
- // Initialize mOSStringSimple to something like:
- // "macOS 10.13.1"
- {
- const char * DARWIN_PRODUCT_NAME = "macOS";
-
- int64_t major_version, minor_version, bugfix_version = 0;
-
- if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version))
- {
- mMajorVer = major_version;
- mMinorVer = minor_version;
- mBuild = bugfix_version;
-
- std::stringstream os_version_string;
- os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild;
-
- // Put it in the OS string we are compiling
- mOSStringSimple.append(os_version_string.str());
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- }
- }
-
- // Initialize mOSString to something like:
- // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386"
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.sysname);
- mOSString.append(" ");
- mOSString.append(un.release);
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
- }
- else
- {
- mOSString = mOSStringSimple;
- }
-
-#elif LL_LINUX
-
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSStringSimple.append(un.sysname);
- mOSStringSimple.append(" ");
- mOSStringSimple.append(un.release);
-
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
-
- // Simplify 'Simple'
- std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
- if (ostype == "Linux")
- {
- // Only care about major and minor Linux versions, truncate at second '.'
- std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
- std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
- std::string simple = mOSStringSimple.substr(0, idx2);
- if (simple.length() > 0)
- mOSStringSimple = simple;
- }
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- mOSString = mOSStringSimple;
- }
-
- const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?";
- boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION);
- boost::smatch matched;
-
- std::string glibc_version(gnu_get_libc_version());
- if ( ll_regex_match(glibc_version, matched, os_version_parse) )
- {
- LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL;
-
- std::string version_value;
-
- if ( matched[1].matched ) // Major version
- {
- version_value.assign(matched[1].first, matched[1].second);
- if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1)
- {
- LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_ERRS("AppInit")
- << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
- << "' returned true, but major version [1] did not match"
- << LL_ENDL;
- }
-
- if ( matched[2].matched ) // Minor version
- {
- version_value.assign(matched[2].first, matched[2].second);
- if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1)
- {
- LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_ERRS("AppInit")
- << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION
- << "' returned true, but minor version [1] did not match"
- << LL_ENDL;
- }
-
- if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.'
- {
- version_value.assign(matched[4].first, matched[4].second);
- if (sscanf(version_value.c_str(), "%d", &mBuild) != 1)
- {
- LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL;
- }
- }
- else
- {
- LL_INFOS("AppInit")
- << "OS build version not provided; using zero"
- << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL;
- }
-
-#else
-
- struct utsname un;
- if(uname(&un) != -1)
- {
- mOSStringSimple.append(un.sysname);
- mOSStringSimple.append(" ");
- mOSStringSimple.append(un.release);
-
- mOSString = mOSStringSimple;
- mOSString.append(" ");
- mOSString.append(un.version);
- mOSString.append(" ");
- mOSString.append(un.machine);
-
- // Simplify 'Simple'
- std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0));
- if (ostype == "Linux")
- {
- // Only care about major and minor Linux versions, truncate at second '.'
- std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0);
- std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos;
- std::string simple = mOSStringSimple.substr(0, idx2);
- if (simple.length() > 0)
- mOSStringSimple = simple;
- }
- }
- else
- {
- mOSStringSimple.append("Unable to collect OS info");
- mOSString = mOSStringSimple;
- }
-
-#endif
-
- std::stringstream dotted_version_string;
- dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild;
- mOSVersionString.append(dotted_version_string.str());
-
- mOSBitness = is64Bit() ? 64 : 32;
- LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL;
-}
-
-#ifndef LL_WINDOWS
-// static
-long LLOSInfo::getMaxOpenFiles()
-{
- const long OPEN_MAX_GUESS = 256;
-
-#ifdef OPEN_MAX
- static long open_max = OPEN_MAX;
-#else
- static long open_max = 0;
-#endif
-
- if (0 == open_max)
- {
- // First time through.
- errno = 0;
- if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0)
- {
- if (0 == errno)
- {
- // Indeterminate.
- open_max = OPEN_MAX_GUESS;
- }
- else
- {
- LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL;
- }
- }
- }
- return open_max;
-}
-#endif
-
-void LLOSInfo::stream(std::ostream& s) const
-{
- s << mOSString;
-}
-
-const std::string& LLOSInfo::getOSString() const
-{
- return mOSString;
-}
-
-const std::string& LLOSInfo::getOSStringSimple() const
-{
- return mOSStringSimple;
-}
-
-const std::string& LLOSInfo::getOSVersionString() const
-{
- return mOSVersionString;
-}
-
-const S32 LLOSInfo::getOSBitness() const
-{
- return mOSBitness;
-}
-
-//static
-U32 LLOSInfo::getProcessVirtualSizeKB()
-{
- U32 virtual_size = 0;
-#if LL_LINUX
-# define STATUS_SIZE 2048
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
-
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
-
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmSize:");
- if (memp)
- {
- numRead += sscanf(memp, "%*s %u", &virtual_size);
- }
- fclose(status_filep);
- }
-#endif
- return virtual_size;
-}
-
-//static
-U32 LLOSInfo::getProcessResidentSizeKB()
-{
- U32 resident_size = 0;
-#if LL_LINUX
- LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb");
- if (status_filep != NULL)
- {
- S32 numRead = 0;
- char buff[STATUS_SIZE]; /* Flawfinder: ignore */
-
- size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep);
- buff[nbytes] = '\0';
-
- // All these guys return numbers in KB
- char *memp = strstr(buff, "VmRSS:");
- if (memp)
- {
- numRead += sscanf(memp, "%*s %u", &resident_size);
- }
- fclose(status_filep);
- }
-#endif
- return resident_size;
-}
-
-//static
-bool LLOSInfo::is64Bit()
-{
-#if LL_WINDOWS
-#if defined(_WIN64)
- return true;
-#elif defined(_WIN32)
- // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate
- bool f64 = false;
- return IsWow64Process(GetCurrentProcess(), &f64) && f64;
-#else
- return false;
-#endif
-#else // ! LL_WINDOWS
- // we only build a 64-bit mac viewer and currently we don't build for linux at all
- return true;
-#endif
-}
-
-LLCPUInfo::LLCPUInfo()
-{
- std::ostringstream out;
- LLProcessorInfo proc;
- // proc.WriteInfoTextFile("procInfo.txt");
- mHasSSE = proc.hasSSE();
- mHasSSE2 = proc.hasSSE2();
- mHasSSE3 = proc.hasSSE3();
- mHasSSE3S = proc.hasSSE3S();
- mHasSSE41 = proc.hasSSE41();
- mHasSSE42 = proc.hasSSE42();
- mHasSSE4a = proc.hasSSE4a();
- mHasAltivec = proc.hasAltivec();
- mCPUMHz = (F64)proc.getCPUFrequency();
- mFamily = proc.getCPUFamilyName();
- mCPUString = "Unknown";
-
- out << proc.getCPUBrandName();
- if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check
- {
- out << " (" << mCPUMHz << " MHz)";
- }
- mCPUString = out.str();
- LLStringUtil::trim(mCPUString);
-
- if (mHasSSE)
- {
- mSSEVersions.append("1");
- }
- if (mHasSSE2)
- {
- mSSEVersions.append("2");
- }
- if (mHasSSE3)
- {
- mSSEVersions.append("3");
- }
- if (mHasSSE3S)
- {
- mSSEVersions.append("3S");
- }
- if (mHasSSE41)
- {
- mSSEVersions.append("4.1");
- }
- if (mHasSSE42)
- {
- mSSEVersions.append("4.2");
- }
- if (mHasSSE4a)
- {
- mSSEVersions.append("4a");
- }
-}
-
-bool LLCPUInfo::hasAltivec() const
-{
- return mHasAltivec;
-}
-
-bool LLCPUInfo::hasSSE() const
-{
- return mHasSSE;
-}
-
-bool LLCPUInfo::hasSSE2() const
-{
- return mHasSSE2;
-}
-
-bool LLCPUInfo::hasSSE3() const
-{
- return mHasSSE3;
-}
-
-bool LLCPUInfo::hasSSE3S() const
-{
- return mHasSSE3S;
-}
-
-bool LLCPUInfo::hasSSE41() const
-{
- return mHasSSE41;
-}
-
-bool LLCPUInfo::hasSSE42() const
-{
- return mHasSSE42;
-}
-
-bool LLCPUInfo::hasSSE4a() const
-{
- return mHasSSE4a;
-}
-
-F64 LLCPUInfo::getMHz() const
-{
- return mCPUMHz;
-}
-
-std::string LLCPUInfo::getCPUString() const
-{
- return mCPUString;
-}
-
-const LLSD& LLCPUInfo::getSSEVersions() const
-{
- return mSSEVersions;
-}
-
-void LLCPUInfo::stream(std::ostream& s) const
-{
- // gather machine information.
- s << LLProcessorInfo().getCPUFeatureDescription();
-
- // These are interesting as they reflect our internal view of the
- // CPU's attributes regardless of platform
- s << "->mHasSSE: " << (U32)mHasSSE << std::endl;
- s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl;
- s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl;
- s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl;
- s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl;
- s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl;
- s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl;
- s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl;
- s << "->mCPUMHz: " << mCPUMHz << std::endl;
- 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;
-};
-
-LLMemoryInfo::LLMemoryInfo()
-{
- refresh();
-}
-
-#if LL_WINDOWS
-static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB)
-{
- // Moved this here from llfloaterabout.cpp
-
- //! \bug
- // For some reason, the reported amount of memory is always wrong.
- // The original adjustment assumes it's always off by one meg, however
- // errors of as much as 2520 KB have been observed in the value
- // returned from the GetMemoryStatusEx function. Here we keep the
- // original adjustment from llfoaterabout.cpp until this can be
- // fixed somehow.
- inKB += U32Megabytes(1);
-
- return inKB;
-}
-#endif
-
-#if LL_DARWIN
-// static
-U32Kilobytes LLMemoryInfo::getHardwareMemSize()
-{
- // This might work on Linux as well. Someone check...
- uint64_t phys = 0;
- int mib[2] = { CTL_HW, HW_MEMSIZE };
-
- size_t len = sizeof(phys);
- sysctl(mib, 2, &phys, &len, NULL, 0);
-
- return U64Bytes(phys);
-}
-#endif
-
-U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const
-{
-#if LL_WINDOWS
- return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger()));
-
-#elif LL_DARWIN
- return getHardwareMemSize();
-
-#elif LL_LINUX
- U64 phys = 0;
- phys = (U64)(getpagesize()) * (U64)(get_phys_pages());
- return U64Bytes(phys);
-
-#else
- return 0;
-
-#endif
-}
-
-//static
-void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb)
-{
-#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_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger();
- avail_virtual_mem_kb = (U32Kilobytes)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 = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-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 = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-1 ;
-
-#else
- //do not know how to collect available memory info for other systems.
- //leave it blank here for now.
-
- avail_physical_mem_kb = (U32Kilobytes)-1 ;
- avail_virtual_mem_kb = (U32Kilobytes)-1 ;
-#endif
-}
-
-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);
- for (const auto& [key, value] : inMap(mStatsMap))
- {
- size_t len(key.length());
- if (len > key_width)
- {
- key_width = len;
- }
- }
-
- // Now stream stats
- for (const auto& [key, value] : inMap(mStatsMap))
- {
- s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' ';
- 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()
-{
- LL_PROFILE_ZONE_SCOPED
- mStatsMap = loadStatsMap();
-
- LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n";
- LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT);
- LL_ENDL;
-
- return *this;
-}
-
-LLSD LLMemoryInfo::loadStatsMap()
-{
- LL_PROFILE_ZONE_SCOPED;
-
- // 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);
-
- 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);
-
- // SL-12122 - Call to GetPerformanceInfo() was removed here. Took
- // on order of 10 ms, causing unacceptable frame time spike every
- // second, and results were never used. If this is needed in the
- // future, must find a way to avoid frame time impact (e.g. move
- // to another thread, call much less often).
-
- 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
-
- const vm_size_t pagekb(vm_page_size / 1024);
-
- //
- // Collect the vm_stat's
- //
-
- {
- vm_statistics64_data_t vmstat;
- mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT;
-
- if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS)
- {
- 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
- //
-
- {
- mach_task_basic_info_data_t taskinfo;
- mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT;
- if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS)
- {
- LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL;
- }
- else
- {
- stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024);
- stats.add("Basic resident memory KB", taskinfo.resident_size / 1024);
- stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024);
- stats.add("Basic new thread policy", taskinfo.policy);
- stats.add("Basic suspend count", taskinfo.suspend_count);
- }
- }
-
-#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 (ll_regex_match(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;
- }
- }
- }
- else
- {
- 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)
-{
- info.stream(s);
- return s;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info)
-{
- info.stream(s);
- return s;
-}
-
-std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info)
-{
- info.stream(s);
- 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 ";
- }
-
- auto precision = LL_CONT.precision();
-
- LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n'
- << LLMemoryInfo();
-
- LL_CONT.precision(precision);
- LL_CONT << 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;
- const S32 UNCOMPRESS_BUFFER_SIZE = 32768;
- bool retval = false;
- gzFile src = NULL;
- U8 buffer[UNCOMPRESS_BUFFER_SIZE];
- LLFILE *dst = NULL;
- S32 bytes = 0;
- tmpfile = dstfile + ".t";
-#ifdef LL_WINDOWS
- llutf16string utf16filename = utf8str_to_utf16str(srcfile);
- src = gzopen_w(utf16filename.c_str(), "rb");
-#else
- src = gzopen(srcfile.c_str(), "rb");
-#endif
- if (! src) goto err;
- dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */
- if (! dst) goto err;
- do
- {
- bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE);
- size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst);
- if (nwrit < (size_t) bytes)
- {
- LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL;
- goto err;
- }
- } while(gzeof(src) == 0);
- fclose(dst);
- dst = NULL;
-#if LL_WINDOWS
- // Rename in windows needs the dstfile to not exist.
- LLFile::remove(dstfile, ENOENT);
-#endif
- if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
- retval = true;
-err:
- if (src != NULL) gzclose(src);
- if (dst != NULL) fclose(dst);
- return retval;
-}
-
-bool gzip_file(const std::string& srcfile, const std::string& dstfile)
-{
- const S32 COMPRESS_BUFFER_SIZE = 32768;
- std::string tmpfile;
- bool retval = false;
- U8 buffer[COMPRESS_BUFFER_SIZE];
- gzFile dst = NULL;
- LLFILE *src = NULL;
- S32 bytes = 0;
- tmpfile = dstfile + ".t";
-
-#ifdef LL_WINDOWS
- llutf16string utf16filename = utf8str_to_utf16str(tmpfile);
- dst = gzopen_w(utf16filename.c_str(), "wb");
-#else
- dst = gzopen(tmpfile.c_str(), "wb");
-#endif
-
- if (! dst) goto err;
- src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */
- if (! src) goto err;
-
- while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0)
- {
- if (gzwrite(dst, buffer, bytes) <= 0)
- {
- LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL;
- goto err;
- }
- }
-
- if (ferror(src))
- {
- LL_WARNS() << "Error reading " << srcfile << LL_ENDL;
- goto err;
- }
-
- gzclose(dst);
- dst = NULL;
-#if LL_WINDOWS
- // Rename in windows needs the dstfile to not exist.
- LLFile::remove(dstfile);
-#endif
- if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */
- retval = true;
- err:
- if (src != NULL) fclose(src);
- if (dst != NULL) gzclose(dst);
- return retval;
-}
+/** + * @file llsys.cpp + * @brief Implementation of the basic system query functions. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, 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 + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + +#include "linden_common.h" + +#include "llsys.h" + +#include <iostream> +#ifdef LL_USESYSTEMLIBS +# include <zlib.h> +#else +# include "zlib-ng/zlib.h" +#endif + +#include "llprocessor.h" +#include "llerrorcontrol.h" +#include "llevents.h" +#include "llformat.h" +#include "llregex.h" +#include "lltimer.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include <boost/bind.hpp> +#include <boost/circular_buffer.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> +#include "llfasttimer.h" + +using namespace llsd; + +#if LL_WINDOWS +# include "llwin32headerslean.h" +# include <psapi.h> // GetPerformanceInfo() et al. +# include <VersionHelpers.h> +#elif LL_DARWIN +# include "llsys_objc.h" +# include <errno.h> +# include <sys/sysctl.h> +# include <sys/utsname.h> +# include <stdint.h> +# include <CoreServices/CoreServices.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"; +# include <gnu/libc-version.h> +#endif + +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; + +LLOSInfo::LLOSInfo() : + mMajorVer(0), mMinorVer(0), mBuild(0), mOSVersionString("") +{ + +#if LL_WINDOWS + + if (IsWindows10OrGreater()) + { + mMajorVer = 10; + mMinorVer = 0; + mOSStringSimple = "Microsoft Windows 10 "; + } + else if (IsWindows8Point1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 3; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8.1 "; + } + } + else if (IsWindows8OrGreater()) + { + mMajorVer = 6; + mMinorVer = 2; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2012 "; + } + else + { + mOSStringSimple = "Microsoft Windows 8 "; + } + } + else if (IsWindows7SP1OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 SP1 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 SP1 "; + } + } + else if (IsWindows7OrGreater()) + { + mMajorVer = 6; + mMinorVer = 1; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 R2 "; + } + else + { + mOSStringSimple = "Microsoft Windows 7 "; + } + } + else if (IsWindowsVistaSP2OrGreater()) + { + mMajorVer = 6; + mMinorVer = 0; + if (IsWindowsServer()) + { + mOSStringSimple = "Windows Server 2008 SP2 "; + } + else + { + mOSStringSimple = "Microsoft Windows Vista SP2 "; + } + } + else + { + mOSStringSimple = "Unsupported Windows version "; + } + + ///get native system info if available.. + typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); ///function pointer for loading GetNativeSystemInfo + SYSTEM_INFO si; //System Info object file contains architecture info + PGNSI pGNSI; //pointer object + ZeroMemory(&si, sizeof(SYSTEM_INFO)); //zero out the memory in information + pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); //load kernel32 get function + if (NULL != pGNSI) //check if it has failed + pGNSI(&si); //success + else + GetSystemInfo(&si); //if it fails get regular system info + //(Warning: If GetSystemInfo it may result in incorrect information in a WOW64 machine, if the kernel fails to load) + + // Try calling GetVersionEx using the OSVERSIONINFOEX structure. + OSVERSIONINFOEX osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + else + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (GetVersionEx((OSVERSIONINFO *)&osvi)) + { + mBuild = osvi.dwBuildNumber & 0xffff; + } + } + + S32 ubr = 0; // Windows 10 Update Build Revision, can be retrieved from a registry + if (mMajorVer == 10) + { + DWORD cbData(sizeof(DWORD)); + DWORD data(0); + HKEY key; + LSTATUS ret_code = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &key); + if (ERROR_SUCCESS == ret_code) + { + ret_code = RegQueryValueExW(key, L"UBR", 0, NULL, reinterpret_cast<LPBYTE>(&data), &cbData); + if (ERROR_SUCCESS == ret_code) + { + ubr = data; + } + } + + if (mBuild >= 22000) + { + // At release Windows 11 version was 10.0.22000.194 + // Windows 10 version was 10.0.19043.1266 + // There is no warranty that Win10 build won't increase, + // so until better solution is found or Microsoft updates + // SDK with IsWindows11OrGreater(), indicate "10/11" + // + // Current alternatives: + // Query WMI's Win32_OperatingSystem for OS string. Slow + // and likely to return 'compatibility' string. + // Check presence of dlls/libs or may be their version. + mOSStringSimple = "Microsoft Windows 10/11 "; + } + } + + //msdn microsoft finds 32 bit and 64 bit flavors this way.. + //http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx (example code that contains quite a few more flavors + //of windows than this code does (in case it is needed for the future) + if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) //check for 64 bit + { + mOSStringSimple += "64-bit "; + } + else if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) + { + mOSStringSimple += "32-bit "; + } + + mOSString = mOSStringSimple; + if (mBuild > 0) + { + mOSString += llformat("(Build %d", mBuild); + if (ubr > 0) + { + mOSString += llformat(".%d", ubr); + } + mOSString += ")"; + } + + LLStringUtil::trim(mOSStringSimple); + LLStringUtil::trim(mOSString); + +#elif LL_DARWIN + + // Initialize mOSStringSimple to something like: + // "macOS 10.13.1" + { + const char * DARWIN_PRODUCT_NAME = "macOS"; + + int64_t major_version, minor_version, bugfix_version = 0; + + if (LLGetDarwinOSInfo(major_version, minor_version, bugfix_version)) + { + mMajorVer = major_version; + mMinorVer = minor_version; + mBuild = bugfix_version; + + std::stringstream os_version_string; + os_version_string << DARWIN_PRODUCT_NAME << " " << mMajorVer << "." << mMinorVer << "." << mBuild; + + // Put it in the OS string we are compiling + mOSStringSimple.append(os_version_string.str()); + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + } + } + + // Initialize mOSString to something like: + // "macOS 10.13.1 Darwin Kernel Version 10.7.0: Sat Jan 29 15:17:16 PST 2011; root:xnu-1504.9.37~1/RELEASE_I386 i386" + struct utsname un; + if(uname(&un) != -1) + { + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.sysname); + mOSString.append(" "); + mOSString.append(un.release); + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + } + else + { + mOSString = mOSStringSimple; + } + +#elif LL_LINUX + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + + const char OS_VERSION_MATCH_EXPRESSION[] = "([0-9]+)\\.([0-9]+)(\\.([0-9]+))?"; + boost::regex os_version_parse(OS_VERSION_MATCH_EXPRESSION); + boost::smatch matched; + + std::string glibc_version(gnu_get_libc_version()); + if ( ll_regex_match(glibc_version, matched, os_version_parse) ) + { + LL_INFOS("AppInit") << "Using glibc version '" << glibc_version << "' as OS version" << LL_ENDL; + + std::string version_value; + + if ( matched[1].matched ) // Major version + { + version_value.assign(matched[1].first, matched[1].second); + if (sscanf(version_value.c_str(), "%d", &mMajorVer) != 1) + { + LL_WARNS("AppInit") << "failed to parse major version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but major version [1] did not match" + << LL_ENDL; + } + + if ( matched[2].matched ) // Minor version + { + version_value.assign(matched[2].first, matched[2].second); + if (sscanf(version_value.c_str(), "%d", &mMinorVer) != 1) + { + LL_ERRS("AppInit") << "failed to parse minor version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_ERRS("AppInit") + << "OS version regex '" << OS_VERSION_MATCH_EXPRESSION + << "' returned true, but minor version [1] did not match" + << LL_ENDL; + } + + if ( matched[4].matched ) // Build version (optional) - note that [3] includes the '.' + { + version_value.assign(matched[4].first, matched[4].second); + if (sscanf(version_value.c_str(), "%d", &mBuild) != 1) + { + LL_ERRS("AppInit") << "failed to parse build version '" << version_value << "' as a number" << LL_ENDL; + } + } + else + { + LL_INFOS("AppInit") + << "OS build version not provided; using zero" + << LL_ENDL; + } + } + else + { + LL_WARNS("AppInit") << "glibc version '" << glibc_version << "' cannot be parsed to three numbers; using all zeros" << LL_ENDL; + } + +#else + + struct utsname un; + if(uname(&un) != -1) + { + mOSStringSimple.append(un.sysname); + mOSStringSimple.append(" "); + mOSStringSimple.append(un.release); + + mOSString = mOSStringSimple; + mOSString.append(" "); + mOSString.append(un.version); + mOSString.append(" "); + mOSString.append(un.machine); + + // Simplify 'Simple' + std::string ostype = mOSStringSimple.substr(0, mOSStringSimple.find_first_of(" ", 0)); + if (ostype == "Linux") + { + // Only care about major and minor Linux versions, truncate at second '.' + std::string::size_type idx1 = mOSStringSimple.find_first_of(".", 0); + std::string::size_type idx2 = (idx1 != std::string::npos) ? mOSStringSimple.find_first_of(".", idx1+1) : std::string::npos; + std::string simple = mOSStringSimple.substr(0, idx2); + if (simple.length() > 0) + mOSStringSimple = simple; + } + } + else + { + mOSStringSimple.append("Unable to collect OS info"); + mOSString = mOSStringSimple; + } + +#endif + + std::stringstream dotted_version_string; + dotted_version_string << mMajorVer << "." << mMinorVer << "." << mBuild; + mOSVersionString.append(dotted_version_string.str()); + + mOSBitness = is64Bit() ? 64 : 32; + LL_INFOS("LLOSInfo") << "OS bitness: " << mOSBitness << LL_ENDL; +} + +#ifndef LL_WINDOWS +// static +long LLOSInfo::getMaxOpenFiles() +{ + const long OPEN_MAX_GUESS = 256; + +#ifdef OPEN_MAX + static long open_max = OPEN_MAX; +#else + static long open_max = 0; +#endif + + if (0 == open_max) + { + // First time through. + errno = 0; + if ( (open_max = sysconf(_SC_OPEN_MAX)) < 0) + { + if (0 == errno) + { + // Indeterminate. + open_max = OPEN_MAX_GUESS; + } + else + { + LL_ERRS() << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << LL_ENDL; + } + } + } + return open_max; +} +#endif + +void LLOSInfo::stream(std::ostream& s) const +{ + s << mOSString; +} + +const std::string& LLOSInfo::getOSString() const +{ + return mOSString; +} + +const std::string& LLOSInfo::getOSStringSimple() const +{ + return mOSStringSimple; +} + +const std::string& LLOSInfo::getOSVersionString() const +{ + return mOSVersionString; +} + +const S32 LLOSInfo::getOSBitness() const +{ + return mOSBitness; +} + +//static +U32 LLOSInfo::getProcessVirtualSizeKB() +{ + U32 virtual_size = 0; +#if LL_LINUX +# define STATUS_SIZE 2048 + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmSize:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &virtual_size); + } + fclose(status_filep); + } +#endif + return virtual_size; +} + +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + U32 resident_size = 0; +#if LL_LINUX + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep != NULL) + { + S32 numRead = 0; + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, "VmRSS:"); + if (memp) + { + numRead += sscanf(memp, "%*s %u", &resident_size); + } + fclose(status_filep); + } +#endif + return resident_size; +} + +//static +bool LLOSInfo::is64Bit() +{ +#if LL_WINDOWS +#if defined(_WIN64) + return true; +#elif defined(_WIN32) + // 32-bit viewer may be run on both 32-bit and 64-bit Windows, need to elaborate + bool f64 = false; + return IsWow64Process(GetCurrentProcess(), &f64) && f64; +#else + return false; +#endif +#else // ! LL_WINDOWS + // we only build a 64-bit mac viewer and currently we don't build for linux at all + return true; +#endif +} + +LLCPUInfo::LLCPUInfo() +{ + std::ostringstream out; + LLProcessorInfo proc; + // proc.WriteInfoTextFile("procInfo.txt"); + mHasSSE = proc.hasSSE(); + mHasSSE2 = proc.hasSSE2(); + mHasSSE3 = proc.hasSSE3(); + mHasSSE3S = proc.hasSSE3S(); + mHasSSE41 = proc.hasSSE41(); + mHasSSE42 = proc.hasSSE42(); + mHasSSE4a = proc.hasSSE4a(); + mHasAltivec = proc.hasAltivec(); + mCPUMHz = (F64)proc.getCPUFrequency(); + mFamily = proc.getCPUFamilyName(); + mCPUString = "Unknown"; + + out << proc.getCPUBrandName(); + if (200 < mCPUMHz && mCPUMHz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check + { + out << " (" << mCPUMHz << " MHz)"; + } + mCPUString = out.str(); + LLStringUtil::trim(mCPUString); + + if (mHasSSE) + { + mSSEVersions.append("1"); + } + if (mHasSSE2) + { + mSSEVersions.append("2"); + } + if (mHasSSE3) + { + mSSEVersions.append("3"); + } + if (mHasSSE3S) + { + mSSEVersions.append("3S"); + } + if (mHasSSE41) + { + mSSEVersions.append("4.1"); + } + if (mHasSSE42) + { + mSSEVersions.append("4.2"); + } + if (mHasSSE4a) + { + mSSEVersions.append("4a"); + } +} + +bool LLCPUInfo::hasAltivec() const +{ + return mHasAltivec; +} + +bool LLCPUInfo::hasSSE() const +{ + return mHasSSE; +} + +bool LLCPUInfo::hasSSE2() const +{ + return mHasSSE2; +} + +bool LLCPUInfo::hasSSE3() const +{ + return mHasSSE3; +} + +bool LLCPUInfo::hasSSE3S() const +{ + return mHasSSE3S; +} + +bool LLCPUInfo::hasSSE41() const +{ + return mHasSSE41; +} + +bool LLCPUInfo::hasSSE42() const +{ + return mHasSSE42; +} + +bool LLCPUInfo::hasSSE4a() const +{ + return mHasSSE4a; +} + +F64 LLCPUInfo::getMHz() const +{ + return mCPUMHz; +} + +std::string LLCPUInfo::getCPUString() const +{ + return mCPUString; +} + +const LLSD& LLCPUInfo::getSSEVersions() const +{ + return mSSEVersions; +} + +void LLCPUInfo::stream(std::ostream& s) const +{ + // gather machine information. + s << LLProcessorInfo().getCPUFeatureDescription(); + + // These are interesting as they reflect our internal view of the + // CPU's attributes regardless of platform + s << "->mHasSSE: " << (U32)mHasSSE << std::endl; + s << "->mHasSSE2: " << (U32)mHasSSE2 << std::endl; + s << "->mHasSSE3: " << (U32)mHasSSE3 << std::endl; + s << "->mHasSSE3S: " << (U32)mHasSSE3S << std::endl; + s << "->mHasSSE41: " << (U32)mHasSSE41 << std::endl; + s << "->mHasSSE42: " << (U32)mHasSSE42 << std::endl; + s << "->mHasSSE4a: " << (U32)mHasSSE4a << std::endl; + s << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; + s << "->mCPUMHz: " << mCPUMHz << std::endl; + 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; +}; + +LLMemoryInfo::LLMemoryInfo() +{ + refresh(); +} + +#if LL_WINDOWS +static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) +{ + // Moved this here from llfloaterabout.cpp + + //! \bug + // For some reason, the reported amount of memory is always wrong. + // The original adjustment assumes it's always off by one meg, however + // errors of as much as 2520 KB have been observed in the value + // returned from the GetMemoryStatusEx function. Here we keep the + // original adjustment from llfoaterabout.cpp until this can be + // fixed somehow. + inKB += U32Megabytes(1); + + return inKB; +} +#endif + +#if LL_DARWIN +// static +U32Kilobytes LLMemoryInfo::getHardwareMemSize() +{ + // This might work on Linux as well. Someone check... + uint64_t phys = 0; + int mib[2] = { CTL_HW, HW_MEMSIZE }; + + size_t len = sizeof(phys); + sysctl(mib, 2, &phys, &len, NULL, 0); + + return U64Bytes(phys); +} +#endif + +U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const +{ +#if LL_WINDOWS + return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); + +#elif LL_DARWIN + return getHardwareMemSize(); + +#elif LL_LINUX + U64 phys = 0; + phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); + return U64Bytes(phys); + +#else + return 0; + +#endif +} + +//static +void LLMemoryInfo::getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb) +{ +#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_physical_mem_kb = (U32Kilobytes)statsMap["Avail Physical KB"].asInteger(); + avail_virtual_mem_kb = (U32Kilobytes)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 = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-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 = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; + +#else + //do not know how to collect available memory info for other systems. + //leave it blank here for now. + + avail_physical_mem_kb = (U32Kilobytes)-1 ; + avail_virtual_mem_kb = (U32Kilobytes)-1 ; +#endif +} + +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); + for (const auto& [key, value] : inMap(mStatsMap)) + { + size_t len(key.length()); + if (len > key_width) + { + key_width = len; + } + } + + // Now stream stats + for (const auto& [key, value] : inMap(mStatsMap)) + { + s << pfx << std::setw(narrow<size_t>(key_width+1)) << (key + ':') << ' '; + 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() +{ + LL_PROFILE_ZONE_SCOPED + mStatsMap = loadStatsMap(); + + LL_DEBUGS("LLMemoryInfo") << "Populated mStatsMap:\n"; + LLSDSerialize::toPrettyXML(mStatsMap, LL_CONT); + LL_ENDL; + + return *this; +} + +LLSD LLMemoryInfo::loadStatsMap() +{ + LL_PROFILE_ZONE_SCOPED; + + // 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); + + 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); + + // SL-12122 - Call to GetPerformanceInfo() was removed here. Took + // on order of 10 ms, causing unacceptable frame time spike every + // second, and results were never used. If this is needed in the + // future, must find a way to avoid frame time impact (e.g. move + // to another thread, call much less often). + + 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 + + const vm_size_t pagekb(vm_page_size / 1024); + + // + // Collect the vm_stat's + // + + { + vm_statistics64_data_t vmstat; + mach_msg_type_number_t vmstatCount = HOST_VM_INFO64_COUNT; + + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t) &vmstat, &vmstatCount) != KERN_SUCCESS) + { + 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 + // + + { + mach_task_basic_info_data_t taskinfo; + mach_msg_type_number_t task_count = MACH_TASK_BASIC_INFO_COUNT; + if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t) &taskinfo, &task_count) != KERN_SUCCESS) + { + LL_WARNS("LLMemoryInfo") << "Unable to collect task information" << LL_ENDL; + } + else + { + stats.add("Basic virtual memory KB", taskinfo.virtual_size / 1024); + stats.add("Basic resident memory KB", taskinfo.resident_size / 1024); + stats.add("Basic max resident memory KB", taskinfo.resident_size_max / 1024); + stats.add("Basic new thread policy", taskinfo.policy); + stats.add("Basic suspend count", taskinfo.suspend_count); + } + } + +#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 (ll_regex_match(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; + } + } + } + else + { + 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) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLCPUInfo& info) +{ + info.stream(s); + return s; +} + +std::ostream& operator<<(std::ostream& s, const LLMemoryInfo& info) +{ + info.stream(s); + 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 "; + } + + auto precision = LL_CONT.precision(); + + LL_CONT << std::fixed << std::setprecision(1) << framerate << '\n' + << LLMemoryInfo(); + + LL_CONT.precision(precision); + LL_CONT << 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; + const S32 UNCOMPRESS_BUFFER_SIZE = 32768; + bool retval = false; + gzFile src = NULL; + U8 buffer[UNCOMPRESS_BUFFER_SIZE]; + LLFILE *dst = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(srcfile); + src = gzopen_w(utf16filename.c_str(), "rb"); +#else + src = gzopen(srcfile.c_str(), "rb"); +#endif + if (! src) goto err; + dst = LLFile::fopen(tmpfile, "wb"); /* Flawfinder: ignore */ + if (! dst) goto err; + do + { + bytes = gzread(src, buffer, UNCOMPRESS_BUFFER_SIZE); + size_t nwrit = fwrite(buffer, sizeof(U8), bytes, dst); + if (nwrit < (size_t) bytes) + { + LL_WARNS() << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << LL_ENDL; + goto err; + } + } while(gzeof(src) == 0); + fclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile, ENOENT); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; +err: + if (src != NULL) gzclose(src); + if (dst != NULL) fclose(dst); + return retval; +} + +bool gzip_file(const std::string& srcfile, const std::string& dstfile) +{ + const S32 COMPRESS_BUFFER_SIZE = 32768; + std::string tmpfile; + bool retval = false; + U8 buffer[COMPRESS_BUFFER_SIZE]; + gzFile dst = NULL; + LLFILE *src = NULL; + S32 bytes = 0; + tmpfile = dstfile + ".t"; + +#ifdef LL_WINDOWS + llutf16string utf16filename = utf8str_to_utf16str(tmpfile); + dst = gzopen_w(utf16filename.c_str(), "wb"); +#else + dst = gzopen(tmpfile.c_str(), "wb"); +#endif + + if (! dst) goto err; + src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ + if (! src) goto err; + + while ((bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE, src)) > 0) + { + if (gzwrite(dst, buffer, bytes) <= 0) + { + LL_WARNS() << "gzwrite failed: " << gzerror(dst, NULL) << LL_ENDL; + goto err; + } + } + + if (ferror(src)) + { + LL_WARNS() << "Error reading " << srcfile << LL_ENDL; + goto err; + } + + gzclose(dst); + dst = NULL; +#if LL_WINDOWS + // Rename in windows needs the dstfile to not exist. + LLFile::remove(dstfile); +#endif + if (LLFile::rename(tmpfile, dstfile) == -1) goto err; /* Flawfinder: ignore */ + retval = true; + err: + if (src != NULL) fclose(src); + if (dst != NULL) gzclose(dst); + return retval; +} |