/** * @file llsys.cpp * @brief Impelementation of the basic system query functions. * * $LicenseInfo:firstyear=2002&license=viewergpl$ * * Copyright (c) 2002-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "llsys.h" #include #ifdef LL_STANDALONE # include #else # include "zlib/zlib.h" #endif #include "llprocessor.h" #if LL_WINDOWS # define WIN32_LEAN_AND_MEAN # include # include #elif LL_DARWIN # include # include # include # include #elif LL_LINUX # include # include # include # include const char MEMINFO_FILE[] = "/proc/meminfo"; const char CPUINFO_FILE[] = "/proc/cpuinfo"; #endif static const S32 CPUINFO_BUFFER_SIZE = 16383; LLCPUInfo gSysCPU; LLOSInfo::LLOSInfo() : mMajorVer(0), mMinorVer(0), mBuild(0) { #if LL_WINDOWS OSVERSIONINFOEX osvi; BOOL bOsVersionInfoEx; // Try calling GetVersionEx using the OSVERSIONINFOEX structure. ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi))) { // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); if(!GetVersionEx( (OSVERSIONINFO *) &osvi)) return; } mMajorVer = osvi.dwMajorVersion; mMinorVer = osvi.dwMinorVersion; mBuild = osvi.dwBuildNumber; switch(osvi.dwPlatformId) { case VER_PLATFORM_WIN32_NT: { // Test for the product. if(osvi.dwMajorVersion <= 4) { mOSStringSimple = "Microsoft Windows NT "; } else if(osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) { mOSStringSimple = "Microsoft Windows 2000 "; } else if(osvi.dwMajorVersion ==5 && osvi.dwMinorVersion == 1) { mOSStringSimple = "Microsoft Windows XP "; } else if(osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2) { if(osvi.wProductType == VER_NT_WORKSTATION) mOSStringSimple = "Microsoft Windows XP x64 Edition "; else mOSStringSimple = "Microsoft Windows Server 2003 "; } else if(osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) { if(osvi.wProductType == VER_NT_WORKSTATION) mOSStringSimple = "Microsoft Windows Vista "; else mOSStringSimple = "Microsoft Windows Vista Server "; } else // Use the registry on early versions of Windows NT. { HKEY hKey; WCHAR szProductType[80]; DWORD dwBufLen; RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\ProductOptions", 0, KEY_QUERY_VALUE, &hKey ); RegQueryValueEx( hKey, L"ProductType", NULL, NULL, (LPBYTE) szProductType, &dwBufLen); RegCloseKey( hKey ); if ( lstrcmpi( L"WINNT", szProductType) == 0 ) { mOSStringSimple += "Professional "; } else if ( lstrcmpi( L"LANMANNT", szProductType) == 0 ) { mOSStringSimple += "Server "; } else if ( lstrcmpi( L"SERVERNT", szProductType) == 0 ) { mOSStringSimple += "Advanced Server "; } } std::string csdversion = utf16str_to_utf8str(osvi.szCSDVersion); // Display version, service pack (if any), and build number. std::string tmpstr; if(osvi.dwMajorVersion <= 4) { tmpstr = llformat("version %d.%d %s (Build %d)", osvi.dwMajorVersion, osvi.dwMinorVersion, csdversion.c_str(), (osvi.dwBuildNumber & 0xffff)); } else { tmpstr = llformat("%s (Build %d)", csdversion.c_str(), (osvi.dwBuildNumber & 0xffff)); } mOSString = mOSStringSimple + tmpstr; } break; case VER_PLATFORM_WIN32_WINDOWS: // Test for the Windows 95 product family. if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) { mOSStringSimple = "Microsoft Windows 95 "; if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' ) { mOSStringSimple += "OSR2 "; } } if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) { mOSStringSimple = "Microsoft Windows 98 "; if ( osvi.szCSDVersion[1] == 'A' ) { mOSStringSimple += "SE "; } } if(osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) { mOSStringSimple = "Microsoft Windows Millennium Edition "; } mOSString = mOSStringSimple; break; } #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 == "Darwin") { // Only care about major Darwin versions, truncate at first '.' S32 idx1 = mOSStringSimple.find_first_of(".", 0); std::string simple = mOSStringSimple.substr(0, idx1); if (simple.length() > 0) mOSStringSimple = simple; } else if (ostype == "Linux") { // Only care about major and minor Linux versions, truncate at second '.' S32 idx1 = mOSStringSimple.find_first_of(".", 0); S32 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 } #ifndef LL_WINDOWS // static S32 LLOSInfo::getMaxOpenFiles() { const S32 OPEN_MAX_GUESS = 256; #ifdef OPEN_MAX static S32 open_max = OPEN_MAX; #else static S32 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 { llerrs << "LLOSInfo::getMaxOpenFiles: sysconf error for _SC_OPEN_MAX" << llendl; } } } 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 S32 STATUS_SIZE = 8192; //static U32 LLOSInfo::getProcessVirtualSizeKB() { U32 virtual_size = 0; #if LL_WINDOWS #endif #if LL_LINUX LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); 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); #elif LL_SOLARIS char proc_ps[LL_MAX_PATH]; sprintf(proc_ps, "/proc/%d/psinfo", (int)getpid()); int proc_fd = -1; if((proc_fd = open(proc_ps, O_RDONLY)) == -1){ llwarns << "unable to open " << proc_ps << llendl; return 0; } psinfo_t proc_psinfo; if(read(proc_fd, &proc_psinfo, sizeof(psinfo_t)) != sizeof(psinfo_t)){ llwarns << "Unable to read " << proc_ps << llendl; close(proc_fd); return 0; } close(proc_fd); virtual_size = proc_psinfo.pr_size; #endif return virtual_size; } //static U32 LLOSInfo::getProcessResidentSizeKB() { U32 resident_size = 0; #if LL_WINDOWS #endif #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); } #elif LL_SOLARIS char proc_ps[LL_MAX_PATH]; sprintf(proc_ps, "/proc/%d/psinfo", (int)getpid()); int proc_fd = -1; if((proc_fd = open(proc_ps, O_RDONLY)) == -1){ llwarns << "unable to open " << proc_ps << llendl; return 0; } psinfo_t proc_psinfo; if(read(proc_fd, &proc_psinfo, sizeof(psinfo_t)) != sizeof(psinfo_t)){ llwarns << "Unable to read " << proc_ps << llendl; close(proc_fd); return 0; } close(proc_fd); resident_size = proc_psinfo.pr_rssize; #endif return resident_size; } LLCPUInfo::LLCPUInfo() { std::ostringstream out; CProcessor proc; const ProcessorInfo* info = proc.GetCPUInfo(); // proc.WriteInfoTextFile("procInfo.txt"); mHasSSE = info->_Ext.SSE_StreamingSIMD_Extensions; mHasSSE2 = info->_Ext.SSE2_StreamingSIMD2_Extensions; mHasAltivec = info->_Ext.Altivec_Extensions; mCPUMhz = (S32)(proc.GetCPUFrequency(50)/1000000.0); mFamily.assign( info->strFamily ); mCPUString = "Unknown"; #if LL_WINDOWS || LL_DARWIN || LL_SOLARIS out << proc.strCPUName; if (200 < mCPUMhz && mCPUMhz < 10000) // *NOTE: cpu speed is often way wrong, do a sanity check { out << " (" << mCPUMhz << " MHz)"; } mCPUString = out.str(); #elif LL_LINUX std::map< std::string, std::string > cpuinfo; LLFILE* cpuinfo_fp = LLFile::fopen(CPUINFO_FILE, "rb"); if(cpuinfo_fp) { char line[MAX_STRING]; memset(line, 0, MAX_STRING); while(fgets(line, MAX_STRING, cpuinfo_fp)) { // /proc/cpuinfo on Linux looks like: // name\t*: value\n char* tabspot = strchr( line, '\t' ); if (tabspot == NULL) continue; char* colspot = strchr( tabspot, ':' ); if (colspot == NULL) continue; char* spacespot = strchr( colspot, ' ' ); if (spacespot == NULL) continue; char* nlspot = strchr( line, '\n' ); if (nlspot == NULL) nlspot = line + strlen( line ); // Fallback to terminating NUL std::string linename( line, tabspot ); std::string llinename(linename); LLStringUtil::toLower(llinename); std::string lineval( spacespot + 1, nlspot ); cpuinfo[ llinename ] = lineval; } fclose(cpuinfo_fp); } # if LL_X86 std::string flags = " " + cpuinfo["flags"] + " "; LLStringUtil::toLower(flags); mHasSSE = ( flags.find( " sse " ) != std::string::npos ); mHasSSE2 = ( flags.find( " sse2 " ) != std::string::npos ); F64 mhz; if (LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhz) && 200.0 < mhz && mhz < 10000.0) { mCPUMhz = (S32)llrint(mhz); } if (!cpuinfo["model name"].empty()) mCPUString = cpuinfo["model name"]; # endif // LL_X86 #endif // LL_LINUX } bool LLCPUInfo::hasAltivec() const { return mHasAltivec; } bool LLCPUInfo::hasSSE() const { return mHasSSE; } bool LLCPUInfo::hasSSE2() const { return mHasSSE2; } S32 LLCPUInfo::getMhz() const { return mCPUMhz; } std::string LLCPUInfo::getCPUString() const { return mCPUString; } void LLCPUInfo::stream(std::ostream& s) const { #if LL_WINDOWS || LL_DARWIN || LL_SOLARIS // gather machine information. char proc_buf[CPUINFO_BUFFER_SIZE]; /* Flawfinder: ignore */ CProcessor proc; if(proc.CPUInfoToText(proc_buf, CPUINFO_BUFFER_SIZE)) { s << proc_buf; } else { s << "Unable to collect processor information" << std::endl; } #else // *NOTE: This works on linux. What will it do on other systems? LLFILE* cpuinfo = LLFile::fopen(CPUINFO_FILE, "rb"); if(cpuinfo) { char line[MAX_STRING]; memset(line, 0, MAX_STRING); while(fgets(line, MAX_STRING, cpuinfo)) { line[strlen(line)-1] = ' '; s << line; } fclose(cpuinfo); s << std::endl; } else { s << "Unable to collect processor information" << std::endl; } #endif // 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 << "->mHasAltivec: " << (U32)mHasAltivec << std::endl; s << "->mCPUMhz: " << mCPUMhz << std::endl; s << "->mCPUString: " << mCPUString << std::endl; } LLMemoryInfo::LLMemoryInfo() { } #if LL_WINDOWS static U32 LLMemoryAdjustKBResult(U32 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 += 1024; return inKB; } #endif U32 LLMemoryInfo::getPhysicalMemoryKB() const { #if LL_WINDOWS MEMORYSTATUSEX state; state.dwLength = sizeof(state); GlobalMemoryStatusEx(&state); return LLMemoryAdjustKBResult((U32)(state.ullTotalPhys >> 10)); #elif LL_DARWIN // 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 (U32)(phys >> 10); #elif LL_LINUX U64 phys = 0; phys = (U64)(getpagesize()) * (U64)(get_phys_pages()); return (U32)(phys >> 10); #elif LL_SOLARIS U64 phys = 0; phys = (U64)(getpagesize()) * (U64)(sysconf(_SC_PHYS_PAGES)); return (U32)(phys >> 10); #else return 0; #endif } U32 LLMemoryInfo::getPhysicalMemoryClamped() const { // Return the total physical memory in bytes, but clamp it // to no more than U32_MAX U32 phys_kb = getPhysicalMemoryKB(); if (phys_kb >= 4194304 /* 4GB in KB */) { return U32_MAX; } else { return phys_kb << 10; } } void LLMemoryInfo::stream(std::ostream& s) const { #if LL_WINDOWS MEMORYSTATUSEX state; state.dwLength = sizeof(state); GlobalMemoryStatusEx(&state); s << "Percent Memory use: " << (U32)state.dwMemoryLoad << '%' << std::endl; s << "Total Physical KB: " << (U32)(state.ullTotalPhys/1024) << std::endl; s << "Avail Physical KB: " << (U32)(state.ullAvailPhys/1024) << std::endl; s << "Total page KB: " << (U32)(state.ullTotalPageFile/1024) << std::endl; s << "Avail page KB: " << (U32)(state.ullAvailPageFile/1024) << std::endl; s << "Total Virtual KB: " << (U32)(state.ullTotalVirtual/1024) << std::endl; s << "Avail Virtual KB: " << (U32)(state.ullAvailVirtual/1024) << std::endl; #elif LL_DARWIN uint64_t phys = 0; size_t len = sizeof(phys); if(sysctlbyname("hw.memsize", &phys, &len, NULL, 0) == 0) { s << "Total Physical KB: " << phys/1024 << std::endl; } else { s << "Unable to collect memory information"; } #elif LL_SOLARIS U64 phys = 0; phys = (U64)(sysconf(_SC_PHYS_PAGES)) * (U64)(sysconf(_SC_PAGESIZE)/1024); s << "Total Physical KB: " << phys << std::endl; #else // *NOTE: This works on linux. What will it do on other systems? LLFILE* meminfo = LLFile::fopen(MEMINFO_FILE,"rb"); if(meminfo) { char line[MAX_STRING]; /* Flawfinder: ignore */ memset(line, 0, MAX_STRING); while(fgets(line, MAX_STRING, meminfo)) { line[strlen(line)-1] = ' '; /*Flawfinder: ignore*/ s << line; } fclose(meminfo); } else { s << "Unable to collect memory information"; } #endif } 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; } 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"; src = gzopen(srcfile.c_str(), "rb"); 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) { llwarns << "Short write on " << tmpfile << ": Wrote " << nwrit << " of " << bytes << " bytes." << llendl; goto err; } } while(gzeof(src) == 0); fclose(dst); dst = NULL; 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"; dst = gzopen(tmpfile.c_str(), "wb"); /* Flawfinder: ignore */ if (! dst) goto err; src = LLFile::fopen(srcfile, "rb"); /* Flawfinder: ignore */ if (! src) goto err; do { bytes = (S32)fread(buffer, sizeof(U8), COMPRESS_BUFFER_SIZE,src); gzwrite(dst, buffer, bytes); } while(feof(src) == 0); 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; }