Include total system memory and peak RSS usage in output.
authorMilian Wolff <mail@milianw.de>
Thu, 25 Feb 2016 19:44:39 +0000 (20:44 +0100)
committerMilian Wolff <mail@milianw.de>
Thu, 25 Feb 2016 19:44:39 +0000 (20:44 +0100)
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.

CMakeLists.txt
accumulatedtracedata.cpp
accumulatedtracedata.h
gui/mainwindow.cpp
gui/parser.cpp
gui/parser.h
heaptrack_print.cpp
libheaptrack.cpp

index ce0c3d4..7ae7ac0 100644 (file)
@@ -8,7 +8,7 @@ if(NOT CMAKE_BUILD_TYPE)
 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)
index bf98a25..fcf81e6 100644 (file)
@@ -157,6 +157,8 @@ bool AccumulatedTraceData::read(istream& in)
     peak = 0;
     peakTime = 0;
     leaked = 0;
+    systemInfo = {};
+    peakRSS = 0;
     allocations.clear();
     uint fileVersion = 0;
 
@@ -317,6 +319,12 @@ bool AccumulatedTraceData::read(istream& in)
             }
             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') {
@@ -330,6 +338,9 @@ bool AccumulatedTraceData::read(istream& in)
                      << " 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;
         }
index 5b55650..7f2b816 100644 (file)
@@ -131,6 +131,13 @@ struct AccumulatedTraceData
     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
index 0cdd8d0..530c411 100644 (file)
@@ -160,6 +160,7 @@ MainWindow::MainWindow(QWidget* parent)
                            << 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>";
                 }
                 {
@@ -170,15 +171,16 @@ MainWindow::MainWindow(QWidget* parent)
                            << 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>";
                 }
 
index 1a932b6..4a1adc7 100644 (file)
@@ -488,7 +488,9 @@ void Parser::parse(const QString& path)
             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..."));
index 0036371..33f33ad 100644 (file)
@@ -36,6 +36,8 @@ struct SummaryData
     uint64_t allocations;
     uint64_t temporary;
     uint64_t allocated;
+    uint64_t peakRSS;
+    uint64_t totalSystemMemory;
 };
 Q_DECLARE_METATYPE(SummaryData);
 
index b942ba8..c559ae5 100644 (file)
@@ -668,6 +668,7 @@ int main(int argc, char** argv)
          << "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()) {
index 0a46ee1..076d9b7 100644 (file)
@@ -144,6 +144,13 @@ void writeCommandLine(FILE* out)
     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;
@@ -251,6 +258,7 @@ public:
         writeVersion(out);
         writeExe(out);
         writeCommandLine(out);
+        writeSystemInfo(out);
 
         s_data = new LockedData(out, stopCallback);
 
@@ -272,6 +280,7 @@ public:
         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.
@@ -307,6 +316,26 @@ public:
         }
     }
 
+    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) {
@@ -442,6 +471,10 @@ private:
             , 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");
@@ -452,6 +485,7 @@ private:
                     HeapTrack heaptrack([&] { return !stopTimerThread.load(); });
                     if (!stopTimerThread) {
                         heaptrack.writeTimestamp();
+                        heaptrack.writeRSS();
                     }
                 }
             });
@@ -471,6 +505,10 @@ private:
                 fclose(out);
             }
 
+            if (procStatm) {
+                fclose(procStatm);
+            }
+
             if (stopCallback && !s_atexit) {
                 stopCallback();
             }
@@ -484,6 +522,9 @@ private:
          */
         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