Skip to content

Instantly share code, notes, and snippets.

@ned14
Created June 11, 2024 18:08
Show Gist options
  • Save ned14/fcbf8444d48837d14c6d4718f6532f40 to your computer and use it in GitHub Desktop.
Save ned14/fcbf8444d48837d14c6d4718f6532f40 to your computer and use it in GitHub Desktop.
Prints summary memory usage info for a process on Linux
#include <cstdint>
#include <cstdio>
#include <string_view>
#include <system_error>
#include <vector>
#ifndef __linux__
#error "This is linux specific"
#endif
#include <fcntl.h>
#include <unistd.h>
/* The following is a quick and nasty extraction from
https://github.com/ned14/llfio/blob/develop/include/llfio/v2.0/detail/impl/posix/utils.ipp
*/
struct process_memory_usage
{
//! The total physical memory in this system.
uint64_t system_physical_memory_total{0};
//! The physical memory in this system not containing dirty pages i.e. is currently used for file system caching, or unused.
uint64_t system_physical_memory_available{0};
//! The maximum amount of memory which can be committed by all processes. This is typically physical RAM plus swap files. Note that swap files can be added
//! and removed over time.
uint64_t system_commit_charge_maximum{0};
//! The amount of commit charge remaining before the maximum. Subtract this from `system_commit_charge_maximum` to determine the amount of commit charge
//! consumed by all processes in the system.
uint64_t system_commit_charge_available{0};
//! The total virtual address space in use.
size_t total_address_space_in_use{0};
//! The total memory currently paged into the process. Always `<= total_address_space_in_use`. Also known as "working set", or "resident set size including
//! shared".
size_t total_address_space_paged_in{0};
//! The total anonymous memory committed. Also known as "commit charge".
size_t private_committed{0};
//! The total anonymous memory currently paged into the process. Always `<= private_committed`. Also known as "active anonymous pages".
size_t private_paged_in{0};
};
process_memory_usage process_memory_usage(int pid)
{
auto fill_buffer = [](std::vector<char> &buffer, const std::string &path)
{
for(;;)
{
int ih = ::open(path.c_str(), O_RDONLY);
if(ih == -1)
{
throw std::system_error(errno, std::system_category());
}
size_t totalbytesread = 0;
for(;;)
{
auto bytesread = ::read(ih, buffer.data() + totalbytesread, buffer.size() - totalbytesread);
if(bytesread < 0)
{
::close(ih);
throw std::system_error(errno, std::system_category());
}
if(bytesread == 0)
{
break;
}
totalbytesread += bytesread;
}
::close(ih);
if(totalbytesread < buffer.size())
{
buffer.resize(totalbytesread);
break;
}
buffer.resize(buffer.size() * 2);
}
};
auto parse = [](std::string_view item, std::string_view what)
{
auto idx = item.find(what);
if(std::string_view::npos == idx)
{
return (uint64_t) -1;
}
idx += what.size();
for(; item[idx] == ' '; idx++)
;
auto eidx = idx;
for(; item[eidx] != '\n'; eidx++)
;
std::string_view unit(item.substr(eidx - 2, 2));
uint64_t value = atoll(item.data() + idx);
if(unit == "kB")
{
value *= 1024ULL;
}
else if(unit == "mB")
{
value *= 1024ULL * 1024;
}
else if(unit == "gB")
{
value *= 1024ULL * 1024 * 1024;
}
else if(unit == "tB")
{
value *= 1024ULL * 1024 * 1024 * 1024;
}
else if(unit == "pB")
{
value *= 1024ULL * 1024 * 1024 * 1024 * 1024;
}
else if(unit == "eB")
{
value *= 1024ULL * 1024 * 1024 * 1024 * 1024 * 1024;
}
else if(unit == "zB")
{
value *= 1024ULL * 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
}
else if(unit == "yB")
{
value *= 1024ULL * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
}
else
{
throw std::system_error(std::make_error_code(std::errc::illegal_byte_sequence));
}
return value;
};
/* /proc/[pid]/status:
total_address_space_in_use = VmSize
total_address_space_paged_in = VmRSS
private_committed = ??? MISSING
private_paged_in = RssAnon
/proc/[pid]/smaps:
total_address_space_in_use = Sum of Size
total_address_space_paged_in = Sum of Rss
private_committed = (Sum of Anonymous - Sum of LazyFree) for all entries with VmFlags containing ac, and inode = 0?
private_paged_in = Sum of Rss for all entries with VmFlags containing ac, and inode = 0?
/proc/[pid]/maps:
hexstart-hexend rw-p offset devid:devid inode pathname
total_address_space_in_use = Sum of regions
total_address_space_paged_in = ??? MISSING
private_committed = Sum of Size for all entries with rw-p, and inode = 0?
private_paged_in = ??? MISSING
/proc/[pid]/statm:
%zu %zu %zu ... total_address_space_in_use total_address_space_paged_in file_shared_pages_paged_in
(values are in pages)
/proc/[pid]/smaps_rollup (Linux 3.16 onwards only):
total_address_space_in_use = ??? MISSING
total_address_space_paged_in = ??? MISSING
private_committed = Anonymous - LazyFree (but, can't distinguish reservations!)
private_paged_in = ??? MISSING
*/
struct process_memory_usage ret;
{
if(false) // this is the inaccurate but way way way faster branch
{
{
std::vector<char> buffer(256);
fill_buffer(buffer, "/proc/" + std::to_string(pid) + "/statm");
if(buffer.size() > 1)
{
size_t file_and_shared_pages_paged_in = 0;
sscanf(buffer.data(), "%zu %zu %zu", &ret.total_address_space_in_use, &ret.total_address_space_paged_in, &file_and_shared_pages_paged_in);
ret.private_paged_in = ret.total_address_space_paged_in - file_and_shared_pages_paged_in;
ret.total_address_space_in_use *= getpagesize();
ret.total_address_space_paged_in *= getpagesize();
ret.private_paged_in *= getpagesize();
// std::cout << std::string_view(buffer.data(), buffer.size()) << std::endl;
}
}
{
std::vector<char> smaps_rollup(256), maps(65536);
fill_buffer(smaps_rollup, "/proc/" + std::to_string(pid) + "/smaps_rollup");
fill_buffer(maps, "/proc/" + std::to_string(pid) + "/maps");
uint64_t lazyfree = 0;
{
std::string_view i(smaps_rollup.data(), smaps_rollup.size());
lazyfree = parse(i, "\nLazyFree:");
}
std::string_view i(maps.data(), maps.size());
size_t anonymous = 0;
for(size_t idx = 0;;)
{
idx = i.find("\n", idx);
if(idx == i.npos)
{
break;
}
idx++;
size_t start = 0, end = 0, inode = 1;
char read = 0, write = 0, executable = 0, private_ = 0;
sscanf(i.data() + idx, "%zx-%zx %c%c%c%c %*u %*u:%*u %zd", &start, &end, &read, &write, &executable, &private_, &inode);
if(inode == 0 && read == 'r' && write == 'w' && executable == '-' && private_ == 'p')
{
anonymous += end - start;
// std::cout << (end - start) << " " << i.substr(idx, 40) << std::endl;
}
}
if(lazyfree != (uint64_t) -1)
{
anonymous -= (size_t) lazyfree;
}
ret.private_committed = anonymous;
}
}
else
{
std::vector<char> buffer(1024 * 1024);
fill_buffer(buffer, "/proc/" + std::to_string(pid) + "/smaps");
const std::string_view totalview(buffer.data(), buffer.size());
// std::cerr << totalview << std::endl;
std::vector<std::string_view> anon_entries, non_anon_entries;
anon_entries.reserve(32);
non_anon_entries.reserve(32);
auto find_item = [&](size_t idx) -> std::string_view
{
auto x = totalview.rfind("\nSize:", idx);
if(x == std::string_view::npos)
{
return {};
}
x = totalview.rfind("\n", x - 1);
if(x == std::string_view::npos)
{
x = 0;
}
else
{
x++;
}
return totalview.substr(x, idx - x);
};
for(std::string_view item = find_item(totalview.size()); item != std::string_view(); item = find_item(item.data() - totalview.data()))
{
// std::cout << "***" << item << "***";
// hexaddr-hexaddr flags offset dev:id inode [path]
size_t inode = 1;
sscanf(item.data(), "%*x-%*x %*c%*c%*c%*c %*x %*c%*c:%*c%*c %zu", &inode);
auto vmflagsidx = item.rfind("\nVmFlags:");
if(vmflagsidx == std::string_view::npos)
{
throw std::system_error(std::make_error_code(std::errc::illegal_byte_sequence));
}
// Is there " ac" after vmflagsidx?
if(std::string_view::npos != item.find(" ac", vmflagsidx) && inode == 0)
{
// std::cerr << "Adding anon entry at offset " << itemtopidx << std::endl;
anon_entries.push_back(item);
}
else
{
// std::cerr << "Adding non-anon entry at offset " << itemtopidx << std::endl;
non_anon_entries.push_back(item);
}
}
// std::cerr << "Anon entries:";
for(auto &i : anon_entries)
{
auto &&size = parse(i, "\nSize:"); // amount committed
auto &&rss = parse(i, "\nRss:"); // amount paged in
auto &&anonymous = parse(i, "\nAnonymous:"); // amount actually dirtied
auto &&lazyfree = parse(i, "\nLazyFree:"); // amount "decommitted" on Linux to avoid a VMA split
if(size != (uint64_t) -1 && rss != (uint64_t) -1 && anonymous != (uint64_t) -1)
{
ret.total_address_space_in_use += size;
ret.total_address_space_paged_in += rss;
ret.private_committed += size;
if(lazyfree != (uint64_t) -1)
{
ret.total_address_space_paged_in -= lazyfree;
ret.private_committed -= lazyfree;
}
ret.private_paged_in += rss;
}
// std::cerr << i << "\nSize = " << size << " Rss = " << rss << std::endl;
}
// std::cerr << "\n\nNon-anon entries:";
for(auto &i : non_anon_entries)
{
auto &&size = parse(i, "\nSize:");
auto &&rss = parse(i, "\nRss:");
auto &&lazyfree = parse(i, "\nLazyFree:");
if(size != (uint64_t) -1 && rss != (uint64_t) -1)
{
ret.total_address_space_in_use += size;
ret.total_address_space_paged_in += rss;
if(lazyfree != (uint64_t) -1)
{
ret.total_address_space_in_use -= lazyfree;
}
}
// std::cerr << i << "\nSize = " << size << " Rss = " << rss << std::endl;
}
}
}
{
std::vector<char> buffer(1024);
fill_buffer(buffer, "/proc/meminfo");
if(buffer.size() > 1)
{
std::string_view i(buffer.data(), buffer.size());
ret.system_physical_memory_total = parse(i, "MemTotal:");
ret.system_physical_memory_available = parse(i, "\nMemAvailable:");
if((uint64_t) -1 == ret.system_physical_memory_available)
{
// MemAvailable is >= Linux 3.14, so let's approximate what it would be
auto &&memfree = parse(i, "\nMemFree:");
auto &&cached = parse(i, "\nCached:");
auto &&swapcached = parse(i, "\nSwapCached:");
ret.system_physical_memory_available = memfree + cached + swapcached;
}
ret.system_commit_charge_maximum = parse(i, "\nCommitLimit:");
ret.system_commit_charge_available = parse(i, "\nCommitted_AS:");
auto &&lazyfree = parse(i, "\nLazyFree:");
if(lazyfree == (uint64_t) -1)
{
lazyfree = 0;
}
ret.system_commit_charge_available = ret.system_commit_charge_maximum - ret.system_commit_charge_available + lazyfree;
}
}
return ret;
}
int main(int argc, char *argv[])
{
try
{
if(argc < 2)
{
fprintf(stderr, "FATAL: %s <pid>\n", argv[0]);
return 1;
}
const auto pid = atoi(argv[1]);
auto usage = process_memory_usage(pid);
auto print = [](uint64_t bytes)
{
if(bytes > 1024ULL * 1024u * 1024u)
{
return std::to_string(bytes / (1024.0 * 1024 * 1024)) + " Gb";
}
if(bytes > 1024ULL * 1024u)
{
return std::to_string(bytes / (1024.0 * 1024)) + " Mb";
}
if(bytes > 1024ULL)
{
return std::to_string(bytes / (1024.0)) + " Kb";
}
return std::to_string(bytes) + " bytes";
};
puts("For this system:");
printf("\n System physical memory total: %s", print(usage.system_physical_memory_total).c_str());
printf("\n System physical memory available: %s", print(usage.system_physical_memory_available).c_str());
printf("\n System commit charge total: %s", print(usage.system_commit_charge_maximum).c_str());
printf("\n System commit charge available: %s", print(usage.system_commit_charge_available).c_str());
printf("\n\nFor process with pid %d:", pid);
printf("\n Address space in use: %s", print(usage.total_address_space_in_use).c_str());
printf("\n Address space paged in: %s", print(usage.total_address_space_paged_in).c_str());
printf("\n Private anonymous memory: %s", print(usage.private_committed).c_str());
printf("\n Private anonymous memory paged in: %s", print(usage.private_paged_in).c_str());
puts("\n");
fflush(stdout);
return 0;
}
catch(const std::exception &e)
{
fprintf(stderr, "FATAL: Exception thrown: %s\n", e.what());
return 1;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment