Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Fix available memory extraction on Linux #26764

Merged
merged 4 commits into from
Sep 24, 2019
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 70 additions & 12 deletions src/pal/src/misc/sysinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Revision History:
#include <sched.h>
#include <errno.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/types.h>
#if HAVE_SYSCTL
#include <sys/sysctl.h>
Expand Down Expand Up @@ -239,6 +240,58 @@ GetSystemInfo(
PERF_EXIT(GetSystemInfo);
}

// Get memory size multiplier based on the passed in units (k = kilo, m = mega, g = giga)
static uint64_t GetMemorySizeMultiplier(char units)
{
switch(units)
{
case 'g':
case 'G': return 1024 * 1024 * 1024;
case 'm':
case 'M': return 1024 * 1024;
case 'k':
case 'K': return 1024;
}

// No units multiplier
return 1;
}

#ifndef __APPLE__

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to use platform-specific #ifdefs here? This should be built on FreeBSD as well, where it's going to fail during runtime (and when it's time to add support to other OSes, it's easier to have the preprocessor #error on an #else).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that procfs exists on other Unix systems too. And here we just try once and then fall back to using sysconf, so we don't fail.

// Try to read the MemAvailable entry from /proc/meminfo.
// Return true if the /proc/meminfo existed, the entry was present and we were able to parse it.
static bool ReadMemAvailable(uint64_t* memAvailable)
{
bool foundMemAvailable = false;
FILE* memInfoFile = fopen("/proc/meminfo", "r");
if (memInfoFile != NULL)
{
char *line = nullptr;
size_t lineLen = 0;

while (getline(&line, &lineLen, memInfoFile) != -1)
{
char units = '\0';
uint64_t available;
int fieldsParsed = sscanf(line, "MemAvailable: %" SCNu64 " %cB", &available, &units);

if (fieldsParsed >= 1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be >=2 since we need to parse values for 2 fields?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, since the units fields can be missing. So we will either get just the "available" or "available" and "units".

{
uint64_t multiplier = GetMemorySizeMultiplier(units);
*memAvailable = available * multiplier;
foundMemAvailable = true;
break;
}
}

free(line);
fclose(memInfoFile);
jkotas marked this conversation as resolved.
Show resolved Hide resolved
}

return foundMemAvailable;
}
#endif // __APPLE__

/*++
Function:
GlobalMemoryStatusEx
Expand Down Expand Up @@ -365,7 +418,22 @@ GlobalMemoryStatusEx(
if (lpBuffer->ullTotalPhys > 0)
{
#ifndef __APPLE__
lpBuffer->ullAvailPhys = sysconf(SYSCONF_PAGES) * sysconf(_SC_PAGE_SIZE);
static volatile bool tryReadMemInfo = true;

if (tryReadMemInfo)
{
// Ensure that we don't try to read the /proc/meminfo in successive calls to the GlobalMemoryStatusEx
// if we have failed to access the file or the file didn't contain the MemAvailable value.
tryReadMemInfo = ReadMemAvailable(&lpBuffer->ullAvailPhys);
}

if (!tryReadMemInfo)
{
// The /proc/meminfo doesn't exist or it doesn't contain the MemAvailable row or the format of the row is invalid
// Fall back to getting the available pages using sysconf.
lpBuffer->ullAvailPhys = sysconf(SYSCONF_PAGES) * sysconf(_SC_PAGE_SIZE);
}

INT64 used_memory = lpBuffer->ullTotalPhys - lpBuffer->ullAvailPhys;
lpBuffer->dwMemoryLoad = (DWORD)((used_memory * 100) / lpBuffer->ullTotalPhys);
#else
Expand Down Expand Up @@ -445,17 +513,7 @@ ReadMemoryValueFromFile(const char* filename, uint64_t* val)
if (errno != 0)
goto done;

multiplier = 1;
switch(*endptr)
{
case 'g':
case 'G': multiplier = 1024;
case 'm':
case 'M': multiplier = multiplier*1024;
case 'k':
case 'K': multiplier = multiplier*1024;
}

multiplier = GetMemorySizeMultiplier(*endptr);
*val = num * multiplier;
result = true;
if (*val/multiplier != num)
Expand Down