/**
 * @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;
        BOOL 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:
    // "Mac OS X 10.6.7"
    {
        const char * DARWIN_PRODUCT_NAME = "Mac OS X";

        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:
    // "Mac OS X 10.6.7 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;
}