We now track the RSS by parsing /proc/self/statm on every timestamp.
The total system memory is taken from sysconf.
The RSS may potentially miss its true peaks, which can be handled in
the future via getrusage(). Still, this is a nice and useful addition
I think.
endif()
set(HEAPTRACK_VERSION_MAJOR 1)
-set(HEAPTRACK_VERSION_MINOR 0)
+set(HEAPTRACK_VERSION_MINOR 1)
set(HEAPTRACK_VERSION_PATCH 0)
set(HEAPTRACK_LIB_VERSION 1.0.0)
set(HEAPTRACK_LIB_SOVERSION 1)
peak = 0;
peakTime = 0;
leaked = 0;
+ systemInfo = {};
+ peakRSS = 0;
allocations.clear();
uint fileVersion = 0;
}
handleTimeStamp(timeStamp, newStamp);
timeStamp = newStamp;
+ } else if (reader.mode() == 'R') { // RSS timestamp
+ uint64_t rss = 0;
+ reader >> rss;
+ if (rss > peakRSS) {
+ peakRSS = rss;
+ }
} else if (reader.mode() == 'X') {
handleDebuggee(reader.line().c_str() + 2);
} else if (reader.mode() == 'A') {
<< " and is thus not compatible with this build of heaptrack version " << hex << HEAPTRACK_VERSION << '.' << endl;
return false;
}
+ } else if (reader.mode() == 'I') { // system information
+ reader >> systemInfo.pageSize;
+ reader >> systemInfo.pages;
} else {
cerr << "failed to parse line: " << reader.line() << endl;
}
uint64_t leaked = 0;
uint64_t totalTime = 0;
uint64_t peakTime = 0;
+ uint64_t peakRSS = 0;
+
+ struct SystemInfo {
+ uint64_t pages = 0;
+ uint64_t pageSize = 0;
+ };
+ SystemInfo systemInfo;
// our indices are sequentially increasing thus a new allocation can only ever
// occur with an index larger than any other we encountered so far
<< i18n("<dt><b>debuggee</b>:</dt><dd style='font-family:monospace;'>%1</dd>", data.debuggee)
// xgettext:no-c-format
<< i18n("<dt><b>total runtime</b>:</dt><dd>%1s</dd>", totalTimeS)
+ << i18n("<dt><b>total system memory</b>:</dt><dd>%1s</dd>", format.formatByteSize(data.totalSystemMemory))
<< "</dl></qt>";
}
{
<< i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, %3/s)</dd>",
data.temporary, round(float(data.temporary) * 100.f * 100.f / data.allocations) / 100.f,
quint64(data.temporary / totalTimeS))
+ << i18n("<dt><b>bytes allocated in total</b> (ignoring deallocations):</dt><dd>%1 (%2/s)</dd>",
+ format.formatByteSize(data.allocated, 2), format.formatByteSize(data.allocated / totalTimeS))
<< "</dl></qt>";
}
{
QTextStream stream(&textRight);
stream << "<qt><dl>"
<< i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 after %2s</dd>", format.formatByteSize(data.peak), peakTimeS)
+ << i18n("<dt><b>peak RSS</b> (including heaptrack overhead):</dt><dd>%1</dd>", format.formatByteSize(data.peakRSS))
<< i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>", format.formatByteSize(data.leaked))
- << i18n("<dt><b>bytes allocated in total</b> (ignoring deallocations):</dt><dd>%1 (%2/s)</dd>",
- format.formatByteSize(data.allocated, 2), format.formatByteSize(data.allocated / totalTimeS))
<< "</dl></qt>";
}
data->leaked,
data->totalAllocations,
data->totalTemporary,
- data->totalAllocated
+ data->totalAllocated,
+ data->peakRSS * data->systemInfo.pageSize,
+ data->systemInfo.pages * data->systemInfo.pageSize
});
emit progressMessageAvailable(i18n("merging allocations..."));
uint64_t allocations;
uint64_t temporary;
uint64_t allocated;
+ uint64_t peakRSS;
+ uint64_t totalSystemMemory;
};
Q_DECLARE_METATYPE(SummaryData);
<< "temporary memory allocations: " << data.totalTemporary
<< " (" << size_t(data.totalTemporary / totalTimeS) << "/s)\n"
<< "peak heap memory consumption: " << formatBytes(data.peak) << '\n'
+ << "peak RSS (including heaptrack overhead): " << formatBytes(data.peakRSS * data.systemInfo.pageSize) << '\n'
<< "total memory leaked: " << formatBytes(data.leaked) << '\n';
if (!printHistogram.empty()) {
fputc('\n', out);
}
+void writeSystemInfo(FILE* out)
+{
+ fprintf(out, "I %lx %lx\n",
+ sysconf(_SC_PAGESIZE),
+ sysconf(_SC_PHYS_PAGES));
+}
+
FILE* createFile(const char* fileName)
{
string outputFileName;
writeVersion(out);
writeExe(out);
writeCommandLine(out);
+ writeSystemInfo(out);
s_data = new LockedData(out, stopCallback);
debugLog<MinimalOutput>("%s", "shutdown()");
writeTimestamp();
+ writeRSS();
// NOTE: we leak heaptrack data on exit, intentionally
// This way, we can be sure to get all static deallocations.
}
}
+ void writeRSS()
+ {
+ if (!s_data || !s_data->out || !s_data->procStatm) {
+ return;
+ }
+
+ // read RSS in pages from statm, then rewind for next read
+ size_t rss = 0;
+ fscanf(s_data->procStatm, "%*x %zx", &rss);
+ rewind(s_data->procStatm);
+ // TODO: compare to rusage.ru_maxrss (getrusage) to find "real" peak?
+ // TODO: use custom allocators with known page sizes to prevent tainting
+ // the RSS numbers with heaptrack-internal data
+
+ if (fprintf(s_data->out, "R %zx\n", rss) < 0) {
+ writeError();
+ return;
+ }
+ }
+
void handleMalloc(void* ptr, size_t size, const Trace &trace)
{
if (!s_data || !s_data->out) {
, stopCallback(stopCallback)
{
debugLog<MinimalOutput>("%s", "constructing LockedData");
+ procStatm = fopen("/proc/self/statm", "r");
+ if (!procStatm) {
+ fprintf(stderr, "WARNING: Failed to open /proc/self/statm for reading.\n");
+ }
timerThread = thread([&] () {
RecursionGuard::isActive = true;
debugLog<MinimalOutput>("%s", "timer thread started");
HeapTrack heaptrack([&] { return !stopTimerThread.load(); });
if (!stopTimerThread) {
heaptrack.writeTimestamp();
+ heaptrack.writeRSS();
}
}
});
fclose(out);
}
+ if (procStatm) {
+ fclose(procStatm);
+ }
+
if (stopCallback && !s_atexit) {
stopCallback();
}
*/
FILE* out = nullptr;
+ /// /proc/self/statm file stream to read RSS value from
+ FILE* procStatm = nullptr;
+
/**
* Calls to dlopen/dlclose mark the cache as dirty.
* When this happened, all modules and their section addresses