Created
June 11, 2024 18:08
-
-
Save ned14/fcbf8444d48837d14c6d4718f6532f40 to your computer and use it in GitHub Desktop.
Prints summary memory usage info for a process on Linux
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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