Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/Common/Private/UbaEnvironment.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

506 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaEnvironment.h"
#include "UbaStringBuffer.h"
#if PLATFORM_WINDOWS
#include <powerbase.h>
#pragma comment(lib, "Powrprof.lib")
#elif PLATFORM_MAC
#include <mach/vm_statistics.h>
#include <mach/mach_types.h>
#include <mach/mach_init.h>
#include <mach/mach_host.h>
#include <mach/mach.h>
#include <sys/sysctl.h>
#endif
#if PLATFORM_WINDOWS
typedef struct _PROCESSOR_POWER_INFORMATION {
ULONG Number;
ULONG MaxMhz;
ULONG CurrentMhz;
ULONG MhzLimit;
ULONG MaxIdleState;
ULONG CurrentIdleState;
} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION;
#endif
namespace uba
{
MutexHandle CreateMutexW(bool bInitialOwner, const tchar* lpName)
{
#if PLATFORM_WINDOWS
return (MutexHandle)(u64)::CreateMutexW(NULL, bInitialOwner, lpName);
#else
// TODO: This is used to check for exclusivity and also for trace streams (only created by host and read by visualizer)
SetLastError(ERROR_SUCCESS);
return ((MutexHandle)(u64)1337); // Just some random value
#endif
}
void ReleaseMutex(MutexHandle mutex)
{
if (mutex == InvalidMutexHandle)
return;
#if PLATFORM_WINDOWS
::ReleaseMutex((HANDLE)mutex);
#else
#endif
}
void CloseMutex(MutexHandle mutex)
{
if (mutex == InvalidMutexHandle)
return;
#if PLATFORM_WINDOWS
::CloseHandle((HANDLE)mutex);
#else
#endif
}
u32 GetEnvironmentVariableW(const tchar* name, tchar* buffer, u32 nSize)
{
#if PLATFORM_WINDOWS
return ::GetEnvironmentVariableW(name, buffer, nSize);
#else
const char* env = getenv(name);
if (!env)
{
SetLastError(203); // ERROR_ENVVAR_NOT_FOUND
return 0;
}
auto envLen = strlen(env);
if (nSize <= envLen)
return envLen + 1;
memcpy(buffer, env, envLen + 1);
return envLen;
#endif
}
bool SetEnvironmentVariableW(const tchar* name, const tchar* value)
{
#if PLATFORM_WINDOWS
return ::SetEnvironmentVariableW(name, value);
#else
return setenv(name, value, 1) == 0;
#endif
}
u32 ExpandEnvironmentStringsW(const tchar* lpSrc, tchar* lpDst, u32 nSize)
{
#if PLATFORM_WINDOWS
return ::ExpandEnvironmentStringsW(lpSrc, lpDst, nSize);
#else
UBA_ASSERTF(false, TC("ExpandEnvironmentStringsW not implemented (%s)"), lpSrc);
return 0;
#endif
}
ProcHandle GetCurrentProcessHandle()
{
#if PLATFORM_WINDOWS
return (ProcHandle)(u64)GetCurrentProcess();
#else
UBA_ASSERTF(false, TC("GetCurrentProcessHandle not implemented"));
return (ProcHandle)0;
#endif
}
u32 GetLogicalProcessorCount()
{
#if PLATFORM_WINDOWS
return ::GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
#else
return (u32)sysconf(_SC_NPROCESSORS_ONLN);
#endif
}
u32 GetProcessorGroupCount()
{
#if PLATFORM_WINDOWS
static u32 s_processorGroupCount = u32(GetActiveProcessorGroupCount());
if (s_processorGroupCount)
return s_processorGroupCount;
#endif
return 1u;
}
void ElevateCurrentThreadPriority()
{
#if PLATFORM_WINDOWS
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
#endif
}
#if PLATFORM_WINDOWS
u64 GetMaxPageFileSize(Logger& logger, MEMORYSTATUSEX& memStatus)
{
tchar systemDrive = 'c';
{
tchar temp[32];
if (GetEnvironmentVariableW(TC("SystemDrive"), temp, 32))
systemDrive = ToLower(temp[0]);
}
u64 maxPageSize = 0;
wchar_t str[1024];
DWORD strBytes = sizeof(str);
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, TC("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"), TC("PagingFiles"), RRF_RT_REG_MULTI_SZ, NULL, str, &strBytes);
if (res == ERROR_SUCCESS)
{
DWORD pagefileOnOsVolume = 0;
DWORD pagefileOnOsVolumeSize = 4;
RegGetValueW(HKEY_LOCAL_MACHINE, TC("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"), TC("PagefileOnOsVolume"), RRF_RT_DWORD, NULL, &pagefileOnOsVolume, &pagefileOnOsVolumeSize);
wchar_t* line = str;
for (; size_t lineLen = wcslen(line); line += lineLen + 1)
{
if (lineLen < 3)
continue;
u64 maxSizeMb = 0;
StringBuffer<8> drive;
drive.Append(line, 3); // Get drive root path
if (drive[0] == '?') // Drive '?' can exist when "Automatically manage paging file size for all drives"..
{
// We can use ExistingPageFiles registry key to figure out which drive...
// This key can contain multiple page files normally.. have no idea if it can contain multiple when drive is '?'.. but for now, just use the first
wchar_t str2[1024];
DWORD str2Bytes = sizeof(str2);
res = RegGetValueW(HKEY_LOCAL_MACHINE, TC("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management"), TC("ExistingPageFiles"), RRF_RT_REG_MULTI_SZ, NULL, str2, &str2Bytes);
if (res != ERROR_SUCCESS)
continue;
auto colon = wcschr(str2, ':'); // Path is something like \??\C:\pagefile.sys or similar.. let's search for : and use character in front of it.
if (colon == nullptr || colon == str2)
continue;
drive[0] = colon[-1];
if (pagefileOnOsVolume && ToLower(drive[0]) != systemDrive)
continue;
}
else if (!pagefileOnOsVolume || ToLower(drive[0]) == systemDrive)
{
const wchar_t* maxSizeStr = wcsrchr(line, ' ');
if (!maxSizeStr || !StringBuffer<32>(maxSizeStr + 1).Parse(maxSizeMb))
{
logger.Warning(TC("Unrecognized page file information format (please report): %s"), line);
continue;
}
if (maxSizeMb) // Custom set page file size
{
maxPageSize += maxSizeMb * 1024 * 1024;
continue;
}
}
else
{
logger.Warning(TC("Page file is set on drive %c: but registry key value PagefileOnOsVolume is set to 1. Fix registry"), drive[0]);
}
// Max possible system-managed page file
maxSizeMb = Max(u64(memStatus.ullTotalPhys) * 3, 4ull * 1024 * 1024 * 1024);
// Check if disk is limiting factor of system-managed page file
// Page file can be max 1/8 of volume size and ofc not more than free space
ULARGE_INTEGER totalNumberOfBytes;
ULARGE_INTEGER totalNumberOfFreeBytes;
if (!GetDiskFreeSpaceExW(drive.data, NULL, &totalNumberOfBytes, &totalNumberOfFreeBytes))
return logger.Error(TC("GetDiskFreeSpaceExW failed to get information about %s (%s)"), drive.data, LastErrorToText().data);
u64 maxDiskPageFileSize = Min(totalNumberOfBytes.QuadPart / 8, totalNumberOfFreeBytes.QuadPart);
maxPageSize += Min(maxDiskPageFileSize, maxSizeMb);
}
}
return maxPageSize;
}
#endif
void GetMachineId(StringBufferBase& out)
{
bool success = false;
tchar str[256];
#if PLATFORM_WINDOWS
DWORD strBytes = sizeof(str);
success = RegGetValueW(HKEY_LOCAL_MACHINE, TC("SOFTWARE\\Microsoft\\Cryptography"), TC("MachineGuid"), RRF_RT_REG_SZ, NULL, str, &strBytes) == ERROR_SUCCESS;
if (success)
out.Append(str);
#elif PLATFORM_LINUX
if (FILE* id = fopen("/etc/machine-id", "r"))
{
success = fgets(str, sizeof_array(str), id) != nullptr;
fclose(id);
if (success)
out.Append(str);
}
#else
(void)str;
success = GetComputerNameW(out); // TODO: Revisit below
//if (FILE* id = popen("ioreg -rd1 -c IOPlatformExpertDevice | awk -F\\\" '/IOPlatformUUID/ { print $4 }'", "r"))
//{
// success = fgets(str, sizeof(str), id) != nullptr;
// fclose(id);
// if (success)
// out.Append(str, strcspn(str, "\n"));
//}
#endif
if (!success)
out.Append(TCV("NoMachineId"));
}
bool GetMemoryInfo(Logger& logger, u64& outAvailable, u64& outTotal, u64* outMaxPageFileSize)
{
if (outMaxPageFileSize)
*outMaxPageFileSize = 0;
#if PLATFORM_WINDOWS
MEMORYSTATUSEX memStatus = { sizeof(memStatus) };
if (!GlobalMemoryStatusEx(&memStatus))
{
outAvailable = 0;
outTotal = 0;
return logger.Error(TC("Failed to get global memory status (%s)"), LastErrorToText().data);
}
// Page file can grow and we want to use the absolute max size to figure out when we need to wait to start new processes
static u64 maxPageSize = GetMaxPageFileSize(logger, memStatus);
if (outMaxPageFileSize)
*outMaxPageFileSize = maxPageSize;
u64 currentPageSize = memStatus.ullTotalPageFile - memStatus.ullTotalPhys;
if (currentPageSize < maxPageSize)
{
outTotal = memStatus.ullTotalPhys + maxPageSize;
outAvailable = memStatus.ullAvailPageFile + (maxPageSize - currentPageSize);
}
else
{
outTotal = memStatus.ullTotalPageFile;
outAvailable = memStatus.ullAvailPageFile;
}
#else
u64 memKb;
GetPhysicallyInstalledSystemMemory(memKb);
outTotal = memKb*1024*1024;
outAvailable = outTotal;
#endif
return true;
}
void GetSystemInfo(Logger& logger, StringBufferBase& out)
{
u32 cpuCount = GetLogicalProcessorCount();
u32 cpuGroupCount = GetProcessorGroupCount();
StringBuffer<128> cpuStr(TC("CPU"));
if (IsRunningArm())
cpuStr.Append(TCV("[Arm]"));
cpuStr.Append(':');
if (cpuGroupCount != 1)
cpuStr.AppendValue(cpuGroupCount).Append('x');
cpuStr.AppendValue(cpuCount/cpuGroupCount);
u64 totalMemoryInKilobytes = 0;
#if PLATFORM_WINDOWS
GetPhysicallyInstalledSystemMemory(&totalMemoryInKilobytes);
{
u32 maxMHz = 0;
DWORD valueSize = 4;
const tchar* key = TC("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, key, TC("~MHz"), RRF_RT_REG_DWORD, NULL, &maxMHz, &valueSize);
if (res != ERROR_SUCCESS)
{
// This will not always be the same and since we use the system info as part of key for client uniqueness it is annoying to get multiple sessions for same instance
Vector<PROCESSOR_POWER_INFORMATION> procInfos;
procInfos.resize(cpuCount);
if (CallNtPowerInformation(ProcessorInformation, NULL, 0, procInfos.data(), cpuCount*sizeof(PROCESSOR_POWER_INFORMATION)) == STATUS_SUCCESS)
maxMHz = procInfos[0].MaxMhz;
}
cpuStr.Appendf(TC(" @ %.1fGHz"), float(maxMHz) / 1000.0f);
}
#else
u64 throwAway;
GetMemoryInfo(logger, throwAway, totalMemoryInKilobytes);
totalMemoryInKilobytes /= 1024;
double processorMhz = 0.0;
#if PLATFORM_LINUX
if (FILE *fp = fopen("/proc/cpuinfo", "r"))
{
char line[256];
while (processorMhz == 0.0 && fgets(line, sizeof(line), fp))
if (strncmp(line, "cpu MHz", 7) == 0)
sscanf(line, "cpu MHz : %lf", &processorMhz);
fclose(fp);
cpuStr.Appendf(" @ %.1fGHz", processorMhz/1000.0);
}
#else
char brand[128];
size_t size = sizeof(brand);
if (sysctlbyname("machdep.cpu.brand_string", brand, &size, NULL, 0) == 0)
cpuStr.Clear().Append(brand).Appendf(" CPU:%u", cpuCount);
#endif
#endif
out.Appendf(TC("%s Mem:%ugb"), cpuStr.data, u32(totalMemoryInKilobytes/(1024*1024)));
#if PLATFORM_WINDOWS
if (!IsRunningWine())
{
DWORD value = 0;
DWORD valueSize = 4;
const tchar* fsKey = TC("SYSTEM\\CurrentControlSet\\Control\\FileSystem");
LSTATUS res = RegGetValueW(HKEY_LOCAL_MACHINE, fsKey, TC("NtfsDisableLastAccessUpdate"), RRF_RT_REG_DWORD, NULL, &value, &valueSize);
if (res != ERROR_SUCCESS)
{
logger.Detail(TC("Failed to retreive ntfs registry key (%i)"), res);
}
else
{
u32 lastAccessSettingsValue = value & 0xf;
if (lastAccessSettingsValue == 0 || lastAccessSettingsValue == 2)
out.Append(TCV(" NtfsLastAccessEnabled"));
}
value = 0;
res = RegGetValueW(HKEY_LOCAL_MACHINE, fsKey, TC("NtfsDisable8dot3NameCreation"), RRF_RT_REG_DWORD, NULL, &value, &valueSize);
if (res == ERROR_SUCCESS)
if (value == 0)
out.Append(TCV(" NtfsShortNamesEnabled"));
}
else
{
//StringBuffer<> testDir;
//testDir.Append(m_rootDir).Append(TCV("UbaTestShortNames"));
//::RemoveDirectory(testDir.data);
//wchar_t shortName[1024];
//if (::CreateDirectoryW(testDir.data, NULL))
// if (GetShortPathName(testDir.data, shortName, 1024) != 0 && !Contains(shortName, TC("UbaTestShortNames")))
// out.Append(TCV(" NtfsShortNamesEnabled"));
}
#endif
}
bool GetCpuTime(u64& outTotalTime, u64& outIdleTime)
{
outTotalTime = 0;
outIdleTime = 0;
#if PLATFORM_WINDOWS
GROUP_AFFINITY originalAffinity {};
GROUP_AFFINITY newAffinity{};
static u16 groupCount = GetActiveProcessorGroupCount();
if (groupCount <= 1)
{
u64 idleTime, kernelTime, userTime;
if (!GetSystemTimes((FILETIME*)&idleTime, (FILETIME*)&kernelTime, (FILETIME*)&userTime))
return false;
outIdleTime += idleTime;
outTotalTime += kernelTime + userTime;
}
else
{
for (u16 group=0; group!=groupCount; ++group)
{
newAffinity.Mask = ~0ull;
newAffinity.Group = group;
if (!SetThreadGroupAffinity(GetCurrentThread(), &newAffinity, group == 0 ? &originalAffinity : NULL))
return false;
u64 idleTime, kernelTime, userTime;
if (!GetSystemTimes((FILETIME*)&idleTime, (FILETIME*)&kernelTime, (FILETIME*)&userTime))
return false;
outIdleTime += idleTime;
outTotalTime += kernelTime + userTime;
}
if (!SetThreadGroupAffinity(GetCurrentThread(), &originalAffinity, nullptr))
return false;
}
#elif PLATFORM_LINUX
int fd = open("/proc/stat", O_RDONLY | O_CLOEXEC);
if (fd == -1)
return false;
char buffer[512];
int size = read(fd, buffer, sizeof_array(buffer) - 1);
close(fd);
if (size == -1)
return false;
buffer[size] = 0;
char* endl = strchr(buffer, '\n');
if (!endl)
return false;
u64 values[16] = { 0 };
u32 valueCount = 0;
*endl = 0;
char* parsePos = buffer;
// cpu
parsePos = strchr(parsePos, ' ');
if (!parsePos)
return false;
while (true)
{
// remove spaces
while (*parsePos && (*parsePos < '0' || *parsePos > '9'))
++parsePos;
if (!*parsePos)
break;
char* numberStart = parsePos;
while (*parsePos && *parsePos >= '0' && *parsePos <= '9')
++parsePos;
bool isLast = *parsePos == 0;
*parsePos = 0;
++parsePos;
values[valueCount++] = strtoull(numberStart, nullptr, 10);
if (isLast)
break;
}
// user: normal processes executing in user mode
// nice: niced processes executing in user mode
// system: processes executing in kernel mode
// idle: twiddling thumbs
// iowait: waiting for I/O to complete
// irq: servicing interrupts
// softirq: servicing softirqs
// steal
if (valueCount <= 6)
return false;
u64 work = values[0] + values[1] + values[2];
outIdleTime = values[3] + values[4] + values[5] + values[6] + values[7];
outTotalTime = work + outIdleTime;
#else // PLATFORM_MAC
mach_msg_type_number_t CpuMsgCount = 0;
processor_flavor_t CpuInfoType = PROCESSOR_CPU_LOAD_INFO;;
natural_t CpuCount = 0;
processor_cpu_load_info_t CpuData;
host_t host = mach_host_self();
u64 work = 0;
int res = host_processor_info(host, CpuInfoType, &CpuCount, (processor_info_array_t *)&CpuData, &CpuMsgCount);
if(res != KERN_SUCCESS)
return false;//m_logger.Error(TC("Kernel error: %s"), mach_error_string(res));
for(int i = 0; i < (int)CpuCount; i++)
{
work += CpuData[i].cpu_ticks[CPU_STATE_SYSTEM];
work += CpuData[i].cpu_ticks[CPU_STATE_USER];
work += CpuData[i].cpu_ticks[CPU_STATE_NICE];
outIdleTime += CpuData[i].cpu_ticks[CPU_STATE_IDLE];
}
outTotalTime = work + outIdleTime;
#endif
return true;
}
}