Implement heap tab
authorAndrey Kvochko <a.kvochko@samsung.com>
Wed, 20 Sep 2017 18:50:20 +0000 (21:50 +0300)
committerAndrey Kvochko/SRR-Compiler Lab/./삼성전자 <a.kvochko@samsung.com>
Fri, 27 Oct 2017 10:46:13 +0000 (13:46 +0300)
18 files changed:
profiler/profiler/src/profiler.cpp
profiler/profiler/src/stackentry.h
src/analyze/accumulatedtracedata.cpp
src/analyze/accumulatedtracedata.h
src/analyze/gui/CMakeLists.txt
src/analyze/gui/mainwindow.cpp
src/analyze/gui/mainwindow.ui
src/analyze/gui/objecttreemodel.cpp [new file with mode: 0644]
src/analyze/gui/objecttreemodel.h [new file with mode: 0644]
src/analyze/gui/objecttreeproxy.cpp [new file with mode: 0644]
src/analyze/gui/objecttreeproxy.h [new file with mode: 0644]
src/analyze/gui/parser.cpp
src/analyze/gui/parser.h
src/interpret/heaptrack_interpret.cpp
src/track/libheaptrack.cpp
src/track/objectgraph.h [new file with mode: 0644]
src/util/indices.h
src/util/pointermap.h

index 9907633..11e2779 100644 (file)
@@ -271,6 +271,43 @@ static HRESULT GetClassNameFromClassId(ICorProfilerInfo *info, ClassID classId,
   return S_OK;
 }
 
+static HRESULT GetClassSizeFromClassId(ICorProfilerInfo2 *info, ClassID classId, ULONG *pulClassSize) {
+  ModuleID moduleId;
+  mdTypeDef mdClass;
+  ClassID parentClassId;
+
+  HRESULT hr = info->GetClassIDInfo2(classId, &moduleId, &mdClass, &parentClassId, 0, nullptr, nullptr);
+  if (parentClassId) {
+    ULONG parentClassSize = 0;
+    hr = GetClassSizeFromClassId(info, parentClassId, &parentClassSize);
+    *pulClassSize += parentClassSize;
+  }
+
+  if (hr != S_OK)
+    return hr;
+
+  IMetaDataImport * pIMetaDataImport;
+  hr = info->GetModuleMetaData(moduleId, CorOpenFlags::ofRead, IID_IMetaDataImport, 
+                                                              (LPUNKNOWN *)&pIMetaDataImport);
+  if (hr != S_OK)
+    return hr; 
+
+  DWORD packSize;
+  ULONG cMax = 128;
+  COR_FIELD_OFFSET rFieldOffset[cMax];
+  ULONG cFieldOffset;
+
+  ULONG classSize = 0;
+  hr = pIMetaDataImport->GetClassLayout(mdClass, &packSize, rFieldOffset, cMax, &cFieldOffset, &classSize);
+  *pulClassSize += classSize;
+  
+  if (hr != S_OK)
+    return hr;
+
+  pIMetaDataImport->Release();
+  return S_OK;
+}
+
 void encodeWChar(WCHAR *orig, char *encoded) {
   int i = 0;
   while (orig[i] != 0) {
@@ -310,6 +347,7 @@ void OnFunctionEnter(FunctionIDOrClientID functionID,
   encodeWChar(szMethodName, methodName);
 
   PushShadowStack(functionID.functionID, className, methodName);
+  info->Release();
 }
 
 void OnFunctionLeave(FunctionIDOrClientID functionID,
@@ -325,7 +363,7 @@ HRESULT STDMETHODCALLTYPE Profiler::Initialize(IUnknown *pICorProfilerInfoUnk) {
     info->SetEventMask(
         COR_PRF_MONITOR_ENTERLEAVE | COR_PRF_ENABLE_FUNCTION_ARGS |
         COR_PRF_ENABLE_FUNCTION_RETVAL | COR_PRF_ENABLE_FRAME_INFO |
-        COR_PRF_ENABLE_STACK_SNAPSHOT |
+        COR_PRF_ENABLE_STACK_SNAPSHOT | COR_PRF_MONITOR_CLASS_LOADS |
         COR_PRF_ENABLE_OBJECT_ALLOCATED | COR_PRF_MONITOR_OBJECT_ALLOCATED | COR_PRF_MONITOR_GC);
     info->SetEnterLeaveFunctionHooks3WithInfo(OnFunctionEnter, OnFunctionLeave,
                                               NULL);
@@ -411,6 +449,20 @@ HRESULT STDMETHODCALLTYPE Profiler::ClassLoadStarted(ClassID classId) {
 
 HRESULT STDMETHODCALLTYPE
     Profiler::ClassLoadFinished(ClassID classId, HRESULT hrStatus) {
+
+  if (hrStatus != S_OK)
+    return S_OK;
+  ICorProfilerInfo2 *info;
+  HRESULT hr = g_pICorProfilerInfoUnknown->QueryInterface(IID_ICorProfilerInfo2,
+                                                          (void **)&info);
+  WCHAR wszClassName[MAX_NAME_LENGTH + 1];
+  GetClassNameFromClassId(info, classId, wszClassName);
+  char className[MAX_NAME_LENGTH + 1];
+  encodeWChar(wszClassName, className);
+  uint32_t classSize = 0;
+  GetClassSizeFromClassId(info, classId, &classSize);
+  heaptrack_loadclass(reinterpret_cast<void*>(classId), classSize, className);
+  info->Release();
   return S_OK;
 }
 
@@ -579,6 +631,8 @@ HRESULT STDMETHODCALLTYPE
   if (hr == S_OK)
     PopShadowStack();
 
+  info->Release();
+
   return S_OK;
 }
 
@@ -591,11 +645,48 @@ HRESULT STDMETHODCALLTYPE Profiler::ObjectsAllocatedByClass(ULONG cClassCount,
 HRESULT STDMETHODCALLTYPE
     Profiler::ObjectReferences(ObjectID objectId, ClassID classId,
                                ULONG cObjectRefs, ObjectID objectRefIds[]) {
+  HRESULT hr;
+  ICorProfilerInfo *info;
+  hr = g_pICorProfilerInfoUnknown->QueryInterface(IID_ICorProfilerInfo,
+                                                            (void **)&info);
+  if (hr != S_OK)
+    return hr;
+
+  for (int i = 0; i < cObjectRefs; ++i) {    
+    ClassID subjClass;
+    hr = info->GetClassFromObject(objectRefIds[i], &subjClass);
+
+    // We still want the best estimate, even though something went wrong.
+    if (hr != S_OK)
+      continue;
+
+    heaptrack_add_object_dep((void*)objectId, (void*)classId, (void*)objectRefIds[i], (void*)subjClass);
+  }
+  info->Release();
   return S_OK;
 }
 
 HRESULT STDMETHODCALLTYPE
     Profiler::RootReferences(ULONG cRootRefs, ObjectID rootRefIds[]) {
+  HRESULT hr;
+  ICorProfilerInfo *info;
+  hr = g_pICorProfilerInfoUnknown->QueryInterface(IID_ICorProfilerInfo,
+                                                            (void **)&info);
+
+  if (hr != S_OK)
+    return hr;
+
+  for (int i = 0; i < cRootRefs; ++i) {    
+    ClassID rootClass;
+    hr = info->GetClassFromObject(rootRefIds[i], &rootClass);
+
+    // We still want the best estimate, even though something went wrong.
+    if (hr != S_OK)
+      continue;
+
+    heaptrack_gcroot((void *) rootRefIds[i], (void*)rootClass);
+  }
+  info->Release();
   return S_OK;
 }
 
@@ -723,7 +814,7 @@ HRESULT STDMETHODCALLTYPE
 
       heaptrack_gcmarksurvived((void *) ranges[i].rangeStart, (unsigned long) ranges[i].rangeLength, NULL);
     }
-
+  info->Release();
   return S_OK;
 }
 
index 9e457c0..48bba03 100644 (file)
@@ -19,5 +19,8 @@ extern "C" void heaptrack_objectallocate(void *objectId, unsigned long objectSiz
 extern "C" void heaptrack_startgc();
 extern "C" void heaptrack_gcmarksurvived(void *rangeStart, unsigned long rangeLength, void *rangeMovedTo);
 extern "C" void heaptrack_finishgc();
+extern "C" void heaptrack_add_object_dep(void *keyObjectId, void *keyClassId, void *valObjectId, void *valClassId);
+extern "C" void heaptrack_loadclass(void *classId,  unsigned long classSize, char *className);
+extern "C" void heaptrack_gcroot(void *objectId, void *rootClass);
 
 #endif // STACKENTRY_H
index 420ed89..28b3c46 100644 (file)
@@ -68,6 +68,8 @@ AccumulatedTraceData::AccumulatedTraceData()
     allocations.reserve(16384);
     stopIndices.reserve(4);
     opNewIpIndices.reserve(16);
+    classInfos.reserve(4096);
+    objectTreeNodes.reserve(16384);
 }
 
 const string& AccumulatedTraceData::stringify(const StringIndex stringId) const
@@ -778,6 +780,25 @@ bool AccumulatedTraceData::read(istream& in, const ParsePass pass)
         } else if (reader.mode() == 'I') { // system information
             reader >> systemInfo.pageSize;
             reader >> systemInfo.pages;
+        } else if (reader.mode() == 'e') { // object dependency
+            if (pass != FirstPass) {
+                continue;
+            }
+            ObjectTreeNode node;
+            reader >> node.gcNum;
+            reader >> node.numChildren;
+            reader >> node.objectPtr;
+            reader >> node.classIndex;
+            reader >> node.allocIndex;
+            objectTreeNodes.push_back(node);
+        } else if (reader.mode() == 'C') { // class info
+            if (pass != FirstPass) {
+                continue;
+            }
+            ClassInfo classInfo;
+            reader >> classInfo.classIndex;
+            reader >> classInfo.size;
+            classInfos.push_back(classInfo);
         } else {
             cerr << "failed to parse line: " << reader.line() << endl;
         }
index 399c71c..88a7949 100644 (file)
@@ -87,6 +87,21 @@ struct Allocation : public AllocationData
     TraceIndex traceIndex;
 };
 
+struct ClassInfo
+{
+    ClassIndex classIndex;
+    uint64_t size = 0;
+};
+
+struct ObjectTreeNode
+{
+    uint64_t gcNum;
+    uint64_t numChildren;
+    uint64_t objectPtr;
+    ClassIndex classIndex;
+    AllocationIndex allocIndex;
+};
+
 /**
  * Information for a single call to an allocation function.
  */
@@ -352,8 +367,9 @@ struct AccumulatedTraceData
     std::vector<TraceNode> traces;
     std::vector<std::string> strings;
     std::vector<IpIndex> opNewIpIndices;
-
     std::vector<AllocationInfo> allocationInfos;
+    std::vector<ClassInfo> classInfos;
+    std::vector<ObjectTreeNode> objectTreeNodes;
 
     AddressRangesMap addressRangeInfos;
 
index 4a32334..e734554 100644 (file)
@@ -21,7 +21,9 @@ set(SRCFILES
     gui.cpp
     mainwindow.cpp
     treemodel.cpp
+    objecttreemodel.cpp
     treeproxy.cpp
+    objecttreeproxy.cpp
     costdelegate.cpp
     parser.cpp
     flamegraph.cpp
index 16f1404..1b0aeec 100644 (file)
@@ -41,7 +41,9 @@
 #include "stacksmodel.h"
 #include "topproxy.h"
 #include "treemodel.h"
+#include "objecttreemodel.h"
 #include "treeproxy.h"
+#include "objecttreeproxy.h"
 
 #include "gui_config.h"
 
@@ -156,6 +158,18 @@ void setupTreeModel(TreeModel* model, QTreeView* view, CostDelegate* costDelegat
     addContextMenu(view, TreeModel::LocationRole);
 }
 
+void setupObjectTreeModel(ObjectTreeModel* model, QTreeView* view, QLineEdit* filterClass, QComboBox* filterGC) {
+    auto proxy = new ObjectTreeProxy(ObjectTreeModel::ClassNameColumn, ObjectTreeModel::GCNumColumn, model);
+    proxy->setSourceModel(model);
+    proxy->setSortRole(ObjectTreeModel::SortRole);
+
+    view->setModel(proxy);
+    view->hideColumn(ObjectTreeModel::GCNumColumn);
+    QObject::connect(filterClass, &QLineEdit::textChanged, proxy, &ObjectTreeProxy::setNameFilter);
+    QObject::connect(filterGC, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+                     proxy, &ObjectTreeProxy::setGCFilter);
+}
+
 void setupCallerCalle(CallerCalleeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
                       QLineEdit* filterFile, QLineEdit* filterModule)
 {
@@ -228,15 +242,17 @@ MainWindow::MainWindow(QWidget* parent)
     auto bottomUpModelFilterOutLeaves = new TreeModel(this);
     auto topDownModel = new TreeModel(this);
     auto callerCalleeModel = new CallerCalleeModel(this);
+    auto objectTreeModel = new ObjectTreeModel(this);
     connect(this, &MainWindow::clearData, bottomUpModelFilterOutLeaves, &TreeModel::clearData);
     connect(this, &MainWindow::clearData, topDownModel, &TreeModel::clearData);
     connect(this, &MainWindow::clearData, callerCalleeModel, &CallerCalleeModel::clearData);
     connect(this, &MainWindow::clearData, m_ui->flameGraphTab, &FlameGraph::clearData);
+    connect(this, &MainWindow::clearData, objectTreeModel, &ObjectTreeModel::clearData);
 
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), false);
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), false);
     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), false);
-
+    m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), false);
     connect(m_parser, &Parser::bottomUpDataAvailable, this, [=](const TreeData& data) {
         if (!m_diffMode) {
             m_ui->flameGraphTab->setBottomUpData(data);
@@ -247,6 +263,18 @@ MainWindow::MainWindow(QWidget* parent)
         m_ui->pages->setCurrentWidget(m_ui->resultsPage);
         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->bottomUpTab), true);
     });
+    connect(m_parser, &Parser::objectTreeBottomUpDataAvailable, this, [=](const ObjectTreeData& data) {
+        int maxGC = 0;
+        for (const ObjectRowData& row: data) {
+            if (maxGC < row.gcNum)
+                maxGC = row.gcNum;
+        }
+        for (int gc = 0; gc < maxGC; gc++) {
+            m_ui->filterGC->addItem(QString::number(gc+1));
+        }
+        objectTreeModel->resetData(data);
+        m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), true);
+    });
     connect(m_parser,
             (AccumulatedTraceData::isHideUnmanagedStackParts ?
              &Parser::bottomUpDataAvailable : &Parser::bottomUpFilterOutLeavesDataAvailable),
@@ -445,6 +473,8 @@ MainWindow::MainWindow(QWidget* parent)
     setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate, m_ui->callerCalleeFilterFunction,
                      m_ui->callerCalleeFilterFile, m_ui->callerCalleeFilterModule);
 
+    setupObjectTreeModel(objectTreeModel, m_ui->objectTreeResults, m_ui->filterClass, m_ui->filterGC);
+
     auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
         if (path.isEmpty()) {
             return allowEmpty;
@@ -596,7 +626,7 @@ void MainWindow::setupStacks()
         if (!current.isValid()) {
             stacksModel->clear();
         } else {
-            auto proxy = qobject_cast<const TreeProxy*>(current.model());
+            auto proxy = qobject_cast<const QSortFilterProxyModel*>(current.model());
             Q_ASSERT(proxy);
             auto leaf = proxy->mapToSource(current);
             stacksModel->fillFromIndex(leaf);
index e6489c1..53563af 100644 (file)
@@ -6,7 +6,7 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>1332</width>
+    <width>1473</width>
     <height>896</height>
    </rect>
   </property>
     </property>
     <item>
      <widget class="KMessageWidget" name="messages">
-      <property name="messageType">
-       <enum>KMessageWidget::Information</enum>
-      </property>
-      <property name="icon">
+      <property name="icon" stdset="0">
        <iconset theme="dialog-error">
         <normaloff>.</normaloff>.</iconset>
       </property>
           </property>
           <layout class="QFormLayout" name="formLayout">
            <item row="1" column="1">
-            <widget class="KUrlRequester" name="openFile">
+            <widget class="KUrlRequester" name="openFile" native="true">
              <property name="toolTip">
               <string>&lt;qt&gt;&lt;p&gt;This field specifies the primary heaptrack data file. These files are called &lt;tt&gt;heaptrack.$APP.$PID.gz&lt;/tt&gt;. You can produce such a file by profiling your application, e.g. via:&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;heaptrack &amp;lt;yourapplication&amp;gt; ...&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Or, alternatively, you can attach to a running process via&lt;/p&gt;
 &lt;pre&gt;&lt;code&gt;heaptrack --pid $(pidof &amp;lt;yourapplication&amp;gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/qt&gt;</string>
              </property>
-             <property name="filter">
+             <property name="filter" stdset="0">
               <string>heaptrack.*.*.gz</string>
              </property>
-             <property name="placeholderText">
+             <property name="placeholderText" stdset="0">
               <string>path/to/heaptrack.$APP.$PID.gz</string>
              </property>
-             <property name="fileDialogModality">
-              <enum>Qt::WindowModal</enum>
-             </property>
             </widget>
            </item>
            <item row="2" column="1">
-            <widget class="KUrlRequester" name="compareTo">
+            <widget class="KUrlRequester" name="compareTo" native="true">
              <property name="toolTip">
               <string>&lt;qt&gt;You can optionally specify a second heaptrack data file to compare to. If set, this file will be used as a base and its cost gets subtracted from the primary data costs.&lt;/qt&gt;</string>
              </property>
-             <property name="filter">
+             <property name="filter" stdset="0">
               <string>heaptrack.*.*.gz</string>
              </property>
-             <property name="placeholderText">
+             <property name="placeholderText" stdset="0">
               <string>path/to/heaptrack.$APP.$PID.gz</string>
              </property>
-             <property name="fileDialogModality">
-              <enum>Qt::WindowModal</enum>
-             </property>
             </widget>
            </item>
            <item row="1" column="0">
             </item>
            </layout>
           </widget>
+          <widget class="QWidget" name="heapTab">
+           <attribute name="title">
+            <string>Managed Heap</string>
+           </attribute>
+           <layout class="QVBoxLayout" name="verticalLayout_16">
+            <item>
+             <layout class="QHBoxLayout" name="horizontalLayout_3">
+              <item>
+               <widget class="QLabel" name="label">
+                <property name="text">
+                 <string>GC #</string>
+                </property>
+               </widget>
+              </item>
+              <item>
+               <widget class="QComboBox" name="filterGC"/>
+              </item>
+              <item>
+               <widget class="QLineEdit" name="filterClass">
+                <property name="placeholderText">
+                 <string>Search</string>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </item>
+            <item>
+             <widget class="QTreeView" name="objectTreeResults">
+              <property name="sortingEnabled">
+               <bool>true</bool>
+              </property>
+              <attribute name="headerDefaultSectionSize">
+               <number>100</number>
+              </attribute>
+             </widget>
+            </item>
+           </layout>
+          </widget>
           <widget class="FlameGraph" name="flameGraphTab">
            <attribute name="title">
             <string>Flame Graph</string>
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>1332</width>
-     <height>30</height>
+     <width>1473</width>
+     <height>22</height>
     </rect>
    </property>
    <widget class="QMenu" name="menu_File">
    <class>KMessageWidget</class>
    <extends>QFrame</extends>
    <header>kmessagewidget.h</header>
+   <container>1</container>
   </customwidget>
   <customwidget>
    <class>KUrlRequester</class>
diff --git a/src/analyze/gui/objecttreemodel.cpp b/src/analyze/gui/objecttreemodel.cpp
new file mode 100644 (file)
index 0000000..b3fd264
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "objecttreemodel.h"
+
+#include <QDebug>
+#include <QTextStream>
+
+#include <KFormat>
+#include <KLocalizedString>
+
+#include <cmath>
+
+namespace {
+
+int indexOf(const ObjectRowData* row, const ObjectTreeData& siblings)
+{
+    Q_ASSERT(siblings.data() <= row);
+    Q_ASSERT(siblings.data() + siblings.size() > row);
+    return row - siblings.data();
+}
+
+const ObjectRowData* rowAt(const ObjectTreeData& rows, int row)
+{
+    Q_ASSERT(rows.size() > row);
+    return rows.data() + row;
+}
+
+/// @return the parent row containing @p index
+const ObjectRowData* toParentRow(const QModelIndex& index)
+{
+    return static_cast<const ObjectRowData*>(index.internalPointer());
+}
+}
+
+ObjectTreeModel::ObjectTreeModel(QObject* parent)
+    : QAbstractItemModel(parent)
+{
+    qRegisterMetaType<ObjectTreeData>();
+}
+
+ObjectTreeModel::~ObjectTreeModel()
+{
+}
+
+QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if (orientation != Qt::Horizontal || section < 0 || section >= NUM_COLUMNS) {
+        return {};
+    }
+    if (role == Qt::InitialSortOrderRole) {
+        if (section == InstanceCountColumn) {
+            return Qt::DescendingOrder;
+        }
+    }
+    if (role == Qt::DisplayRole) {
+        switch (static_cast<Columns>(section)) {
+        case ClassNameColumn:
+            return i18n("Class Name");
+        case InstanceCountColumn:
+            return i18n("Instances");
+        case ShallowSizeColumn:
+            return i18n("Shallow Size");
+        case ReferencedSizeColumn:
+            return i18n("Referenced Size");
+        case GCNumColumn:
+            return i18n("GC #");
+        case NUM_COLUMNS:
+            break;
+        }
+    } else if (role == Qt::ToolTipRole) {
+        switch (static_cast<Columns>(section)) {
+        case ClassNameColumn:
+            return i18n("<qt>The name of the class.</qt>");
+        case InstanceCountColumn:
+            return i18n("<qt>Total number of instances in this reference chain.</qt>");
+        case GCNumColumn:
+            return i18n("<qt>GC number after which the snapshot has been taken.</qt>");
+        case ShallowSizeColumn:
+            return i18n("<qt>Total size of objects of this type in a given reference chain.</qt>");
+        case ReferencedSizeColumn:
+            return i18n("<qt>Total size of objects of this type referenced by other objects in a given reference chain.</qt>");
+        case NUM_COLUMNS:
+            break;
+        }
+    }
+    return {};
+}
+
+QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const
+{
+    if (index.row() < 0 || index.column() < 0 || index.column() > NUM_COLUMNS) {
+        return {};
+    }
+
+    const auto row = toRow(index);
+
+    if (role == Qt::DisplayRole || role == SortRole) {
+        switch (static_cast<Columns>(index.column())) {
+        case ClassNameColumn:
+            if (row->className.empty())
+                return i18n("<gcroot>");
+            return i18n(row->className.c_str());
+        case InstanceCountColumn:
+            return static_cast<qint64>(row->allocations);
+        case ShallowSizeColumn:
+            return static_cast<qint64>(row->allocated);
+        case ReferencedSizeColumn:
+            return static_cast<qint64>(row->referenced);
+        case GCNumColumn:
+            return static_cast<quint64>(row->gcNum);
+        case NUM_COLUMNS:
+            break;
+        }
+    } else if (role == Qt::ToolTipRole) {
+        QString tooltip;
+        QTextStream stream(&tooltip);
+        stream << "<qt><pre style='font-family:monospace;'>";
+        stream << "</pre></qt>";
+        return tooltip;
+    }
+    return {};
+}
+
+QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const
+{
+    if (row < 0 || column < 0 || column >= NUM_COLUMNS || row >= rowCount(parent)) {
+        return QModelIndex();
+    }
+    return createIndex(row, column, const_cast<void*>(reinterpret_cast<const void*>(toRow(parent))));
+}
+
+QModelIndex ObjectTreeModel::parent(const QModelIndex& child) const
+{
+    if (!child.isValid()) {
+        return {};
+    }
+    const auto parent = toParentRow(child);
+    if (!parent) {
+        return {};
+    }
+    return createIndex(rowOf(parent), 0, const_cast<void*>(reinterpret_cast<const void*>(parent->parent)));
+}
+
+int ObjectTreeModel::rowCount(const QModelIndex& parent) const
+{
+    if (!parent.isValid()) {
+        return m_data.size();
+    } else if (parent.column() != 0) {
+        return 0;
+    }
+    auto row = toRow(parent);
+    Q_ASSERT(row);
+    return row->children.size();
+}
+
+int ObjectTreeModel::columnCount(const QModelIndex& /*parent*/) const
+{
+    return NUM_COLUMNS;
+}
+
+void ObjectTreeModel::resetData(const ObjectTreeData& data)
+{
+    beginResetModel();
+    m_data = data;
+    endResetModel();
+}
+
+void ObjectTreeModel::clearData()
+{
+    beginResetModel();
+    m_data = {};
+    endResetModel();
+}
+
+const ObjectRowData* ObjectTreeModel::toRow(const QModelIndex& index) const
+{
+    if (!index.isValid()) {
+        return nullptr;
+    }
+    if (const auto parent = toParentRow(index)) {
+        return rowAt(parent->children, index.row());
+    } else {
+        return rowAt(m_data, index.row());
+    }
+}
+
+int ObjectTreeModel::rowOf(const ObjectRowData* row) const
+{
+    if (auto parent = row->parent) {
+        return indexOf(row, parent->children);
+    } else {
+        return indexOf(row, m_data);
+    }
+}
diff --git a/src/analyze/gui/objecttreemodel.h b/src/analyze/gui/objecttreemodel.h
new file mode 100644 (file)
index 0000000..97229c1
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef OBJECTTREEMODEL_H
+#define OBJECTTREEMODEL_H
+
+#include <QAbstractItemModel>
+#include <QVector>
+
+#include <KFormat>
+#include <string.h>
+
+struct ObjectRowData
+{
+    uint64_t classIndex;
+    std::string className;
+    quint32 gcNum;
+    uint64_t allocations;
+    uint64_t allocated;
+    uint64_t referenced;
+    const ObjectRowData* parent;
+    QVector<ObjectRowData> children;
+    bool operator<(const ObjectRowData& rhs) const
+    {
+        return className < rhs.className;
+    }
+};
+
+using ObjectTreeData = QVector<ObjectRowData>;
+Q_DECLARE_METATYPE(ObjectTreeData)
+
+class ObjectTreeModel : public QAbstractItemModel
+{
+    Q_OBJECT
+public:
+    ObjectTreeModel(QObject* parent);
+    virtual ~ObjectTreeModel();
+
+    enum Columns
+    {
+        InstanceCountColumn,
+        ShallowSizeColumn,
+        ReferencedSizeColumn,
+        GCNumColumn,
+        ClassNameColumn,
+        NUM_COLUMNS
+    };
+
+    enum Roles
+    {
+        SortRole = Qt::UserRole,
+        MaxCostRole,
+        LocationRole
+    };
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
+    QModelIndex parent(const QModelIndex& child) const override;
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+
+public slots:
+    void resetData(const ObjectTreeData& data);
+    void clearData();
+
+private:
+    /// @return the row resembled by @p index
+    const ObjectRowData* toRow(const QModelIndex& index) const;
+    /// @return the row number of @p row in its parent
+    int rowOf(const ObjectRowData* row) const;
+
+    ObjectTreeData m_data;
+};
+
+#endif // OBJECTTREEMODEL_H
diff --git a/src/analyze/gui/objecttreeproxy.cpp b/src/analyze/gui/objecttreeproxy.cpp
new file mode 100644 (file)
index 0000000..53e32e0
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "objecttreeproxy.h"
+
+ObjectTreeProxy::ObjectTreeProxy(int nameColumn, int gcColumn, QObject* parent)
+    : KRecursiveFilterProxyModel(parent)
+    , m_nameColumn(nameColumn)
+    , m_gcColumn(gcColumn)
+{
+}
+
+ObjectTreeProxy::~ObjectTreeProxy() = default;
+
+void ObjectTreeProxy::setNameFilter(const QString& nameFilter)
+{
+    m_nameFilter = nameFilter;
+    invalidate();
+}
+
+void ObjectTreeProxy::setGCFilter(int gcFilter)
+{
+    m_gcFilter = gcFilter;
+    invalidate();
+}
+
+bool ObjectTreeProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+    // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
+    // when the subtrees are checked by QSFPM.
+    if (acceptRow(sourceRow, sourceParent)) {
+        return true;
+    }
+
+    QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
+    Q_ASSERT(source_index.isValid());
+    bool accepted = false;
+
+    QModelIndex parent = sourceParent;
+    while (parent.isValid()) {
+        if (acceptRow(parent.row(), parent.parent()))
+            return true;
+        parent = parent.parent();
+    }
+
+    const int numChildren = sourceModel()->rowCount(source_index);
+    for (int row = 0, rows = numChildren; row < rows; ++row) {
+        if (filterAcceptsRow(row, source_index)) {
+            accepted = true;
+            break;
+        }
+    }
+
+    return accepted;
+}
+
+bool ObjectTreeProxy::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
+{
+    auto source = sourceModel();
+    if (!source) {
+        return false;
+    }
+
+    const auto& name = source->index(sourceRow, m_nameColumn, sourceParent).data().toString();
+    if (name.contains(QString::fromStdString("EMPTY_MESSAGE"), Qt::CaseInsensitive))
+        return false;
+
+    if (!m_nameFilter.isEmpty()) {
+        if (!name.contains(m_nameFilter, Qt::CaseInsensitive)) {
+            return false;
+        }
+    }
+    if (m_gcFilter != -1) {
+        const int gc = source->index(sourceRow, m_gcColumn, sourceParent).data().toInt() - 1;
+        if (gc != m_gcFilter) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/src/analyze/gui/objecttreeproxy.h b/src/analyze/gui/objecttreeproxy.h
new file mode 100644 (file)
index 0000000..843fa74
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef OBJECTTREEPROXY_H
+#define OBJECTTREEPROXY_H
+
+#include <KRecursiveFilterProxyModel>
+
+class ObjectTreeProxy final : public KRecursiveFilterProxyModel
+{
+    Q_OBJECT
+public:
+    explicit ObjectTreeProxy(int nameColumn, int gcColumn, QObject* parent = nullptr);
+    virtual ~ObjectTreeProxy();
+
+public slots:
+    void setNameFilter(const QString& nameFilter);
+    void setGCFilter(int gcFilter);
+
+private:
+    bool acceptRow(int source_row, const QModelIndex& source_parent) const override;
+    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+    int m_nameColumn;
+    int m_gcColumn;
+
+    QString m_nameFilter;
+    int m_gcFilter;
+};
+
+#endif // OBJECTTREEPROXY_H
index c8993c5..107ec10 100644 (file)
@@ -33,6 +33,130 @@ using namespace std;
 
 namespace {
 
+struct ObjectNode {
+    ObjectNode()
+    : m_children(), m_objectPtr(0), m_classIndex(), m_objectSize(0), gcNum(0)
+    {
+    }
+
+    std::vector<ObjectNode> m_children;
+    uint64_t m_objectPtr;
+    ClassIndex m_classIndex;
+    uint64_t m_objectSize;
+    uint32_t gcNum;
+};
+
+
+struct TypeTree {
+    TypeTree()
+    : m_classIndex(),
+      m_parents(),
+      m_uniqueObjects(),
+      m_totalSize(0),
+      m_referencedSize(0),
+      m_gcNum(0)
+    {
+    }
+
+    TypeTree(const TypeTree& rhs)
+    : m_classIndex(rhs.m_classIndex),
+      m_parents(),
+      m_uniqueObjects(rhs.m_uniqueObjects),
+      m_totalSize(rhs.m_totalSize),
+      m_referencedSize(rhs.m_referencedSize),
+      m_gcNum(rhs.m_gcNum)
+    {
+        for (auto& rParent: rhs.m_parents) {
+            auto parent = std::unique_ptr<TypeTree>(new TypeTree(*rParent));
+            m_parents.push_back(std::move(parent));
+        }
+    }
+
+    ~TypeTree() {
+    }
+
+    static std::unique_ptr<TypeTree> create(ObjectNode& node) {
+        auto TT = std::unique_ptr<TypeTree>(new TypeTree());
+        TT->m_classIndex = node.m_classIndex;
+        TT->m_gcNum = node.gcNum;
+        TT->m_uniqueObjects.insert(node.m_objectPtr);
+        TT->m_totalSize = node.m_objectSize;
+        TT->m_referencedSize = node.m_objectSize;
+        return TT;
+    }
+
+    static std::vector<std::unique_ptr<TypeTree>> createBottomUp(ObjectNode& node) {
+        std::vector<std::unique_ptr<TypeTree>> result;
+        if (node.m_classIndex.index == 0 && node.m_children.size() == 0)
+            return result;
+
+        if (node.m_children.size() == 0) {
+            result.push_back(std::move(create(node)));
+            return result;
+        }
+
+        for (ObjectNode& child: node.m_children) {
+            auto leaves = TypeTree::createBottomUp(child);
+            for (auto& leaf: leaves) {
+                auto parent = create(node);
+                parent->m_referencedSize = leaf->m_referencedSize;
+                TypeTree* childIt = leaf.get();
+                while (childIt->m_parents.size() > 0)
+                    childIt = childIt->m_parents[0].get();
+                childIt->m_parents.push_back(std::move(parent));
+                result.push_back(std::move(leaf));
+                auto parentCopy = create(node);
+                result.push_back(std::move(parentCopy));
+            }
+        }
+
+        return result;
+    }
+
+    void mergeSubtrees() {
+        std::unordered_map<uint64_t,
+                std::vector<TypeTree*>> classCounters;
+
+        for (auto& parent: m_parents) {
+            classCounters[parent->m_classIndex.index].push_back(parent.get());
+        }
+
+        std::vector<std::unique_ptr<TypeTree>> newParents;
+
+        for (auto it = classCounters.begin(),
+             end = classCounters.end(); it != end; ++it) {
+            auto combinedParent = std::unique_ptr<TypeTree>(new TypeTree());
+            combinedParent->m_classIndex.index = it->first;
+            auto& subtrees = it->second;
+            combinedParent->m_gcNum = m_gcNum;
+            for (auto& parent: subtrees) {
+                for (auto &parentsParent: parent->m_parents)
+                    combinedParent->m_parents.push_back(std::move(parentsParent));
+                combinedParent->m_referencedSize += parent->m_referencedSize;
+                for (auto& uo: parent->m_uniqueObjects) {
+                    auto pib = combinedParent->m_uniqueObjects.insert(uo);
+                    if (pib.second)
+                        combinedParent->m_totalSize += parent->m_totalSize;
+                }
+
+            }
+            combinedParent->mergeSubtrees();
+
+            newParents.push_back(std::move(combinedParent));
+        }
+
+        m_parents.erase(m_parents.begin(), m_parents.end());
+        m_parents = std::move(newParents);
+    }
+
+    ClassIndex m_classIndex;
+    std::vector<std::unique_ptr<TypeTree>> m_parents;
+    std::unordered_set<uint32_t> m_uniqueObjects;
+    uint64_t m_totalSize;
+    uint64_t m_referencedSize;
+    int m_gcNum;
+};
+
 // TODO: use QString directly
 struct StringCache
 {
@@ -585,6 +709,72 @@ HistogramData buildSizeHistogram(ParserData& data)
 }
 }
 
+void setObjectParents(QVector<ObjectRowData>& children, const ObjectRowData* parent)
+{
+    for (auto& row : children) {
+        row.parent = parent;
+        setObjectParents(row.children, &row);
+    }
+}
+
+ObjectRowData objectRowDataFromTypeTree(ParserData& data, TypeTree* tree) {
+    ObjectRowData rowData;
+    rowData.gcNum = tree->m_gcNum;
+    rowData.classIndex = tree->m_classIndex.index;
+    rowData.className = data.stringify(tree->m_classIndex);
+    rowData.allocations = tree->m_uniqueObjects.size();
+    rowData.allocated = tree->m_totalSize;
+    rowData.referenced = tree->m_referencedSize;
+    for (auto& parent: tree->m_parents)
+        rowData.children.push_back(objectRowDataFromTypeTree(data, parent.get()));
+    return rowData;
+}
+
+ObjectNode buildObjectGraph(ParserData& data, size_t &nodeIndex) {
+    ObjectNode node;
+    ObjectTreeNode &dataNode = data.objectTreeNodes[nodeIndex];
+    node.m_classIndex = dataNode.classIndex;
+    node.m_objectPtr = dataNode.objectPtr;
+    node.gcNum = dataNode.gcNum;
+    if (dataNode.allocIndex.index != 0)
+        node.m_objectSize = data.allocationInfos[dataNode.allocIndex.index].size;
+    else
+        node.m_objectSize = 0;
+    nodeIndex++;
+    for (size_t i = 0; i < dataNode.numChildren; ++i) {
+        if (node.gcNum != data.objectTreeNodes[nodeIndex].gcNum)
+            break;
+        node.m_children.push_back(buildObjectGraph(data, nodeIndex));
+    }
+    return node;
+}
+
+ObjectTreeData buildObjectTree(ParserData& data)
+{
+
+    ObjectTreeData ret;
+    size_t nodeIndex = 0;
+    std::vector<ObjectNode> nodes;
+    while (nodeIndex < data.objectTreeNodes.size()) {
+        nodes.push_back(buildObjectGraph(data, nodeIndex));
+    }
+
+    for (auto& node: nodes) {
+        TypeTree root;
+        root.m_gcNum = node.gcNum;
+        root.m_parents = TypeTree::createBottomUp(node);
+        root.mergeSubtrees();
+
+        for (auto& parent: root.m_parents) {
+            ret.push_back(objectRowDataFromTypeTree(data, parent.get()));
+        }
+
+    }
+    setObjectParents(ret, nullptr);
+
+    return ret;
+}
+
 Parser::Parser(QObject* parent)
     : QObject(parent)
 {
@@ -658,6 +848,11 @@ void Parser::parse(const QString& path, const QString& diffBase)
             emit bottomUpFilterOutLeavesDataAvailable(mergedAllocations);
         }
 
+        if (AllocationData::display == AllocationData::DisplayId::managed) {
+            const auto objectTreeBottomUpData = buildObjectTree(*data);
+            emit objectTreeBottomUpDataAvailable(objectTreeBottomUpData);
+        }
+
         // also calculate the size histogram
         emit progressMessageAvailable(i18n("building size histogram..."));
         const auto sizeHistogram = buildSizeHistogram(*data);
index fb9af9f..7503b55 100644 (file)
@@ -25,6 +25,7 @@
 #include "chartmodel.h"
 #include "histogrammodel.h"
 #include "treemodel.h"
+#include "objecttreemodel.h"
 
 class Parser : public QObject
 {
@@ -49,6 +50,8 @@ signals:
     void allocatedChartDataAvailable(const ChartData& data);
     void temporaryChartDataAvailable(const ChartData& data);
     void sizeHistogramDataAvailable(const HistogramData& data);
+    void objectTreeTopDownDataAvailable(const ObjectTreeData& data);
+    void objectTreeBottomUpDataAvailable(const ObjectTreeData& data);
     void finished();
     void failedToOpen(const QString& path);
 };
index 31abd86..5142f0d 100644 (file)
@@ -187,6 +187,7 @@ struct AccumulatedTraceData
         m_backtraceStates.reserve(64);
         m_internedData.reserve(4096);
         m_encounteredIps.reserve(32768);
+        m_encounteredClasses.reserve(4096);
     }
 
     ~AccumulatedTraceData()
@@ -321,6 +322,23 @@ struct AccumulatedTraceData
         return ipId;
     }
 
+    size_t addClass(const uintptr_t classPointer, const size_t classSize) {
+        if (!classPointer) {
+            return 0;
+        }
+
+        auto it = m_encounteredClasses.find(classPointer);
+        if (it != m_encounteredClasses.end()) {
+            return it->second;
+        }
+
+        size_t classIndex = intern(m_managedNames[classPointer]);
+        m_encounteredClasses.insert(it, make_pair(classPointer, classIndex));
+
+        fprintf(stdout, "C %zx %zx\n", classIndex, classSize);
+        return classIndex;
+    }
+
     bool fileIsReadable(const string& path) const
     {
         return access(path.c_str(), R_OK) == 0;
@@ -406,6 +424,7 @@ private:
     unordered_map<uintptr_t, string> m_managedNames;
     unordered_map<string, size_t> m_internedData;
     unordered_map<uintptr_t, size_t> m_encounteredIps;
+    unordered_map<uintptr_t, size_t> m_encounteredClasses;
 };
 }
 
@@ -540,6 +559,7 @@ int main(int /*argc*/, char** /*argv*/)
                 for (auto managedPtr : managedPointersSet) {
                     auto allocation = ptrToIndex.takePointer(managedPtr);
 
+
                     if (!allocation.second) {
                         cerr << "wrong trace format (unknown managed pointer) 0x" << std::hex << managedPtr << std::dec << endl;
                         continue;
@@ -663,21 +683,46 @@ int main(int /*argc*/, char** /*argv*/)
             --leakedAllocations;
         } else if (reader.mode() == 'n') {
             uint64_t ip;
-            string methodName;
-            if (!(reader >> ip) || !(reader >> methodName)) {
+            string name;
+            if (!(reader >> ip) || !(reader >> name)) {
                 cerr << "failed to parse line: " << reader.line() << endl;
             }
 
-            if (managed_name_ids.find(methodName) == managed_name_ids.end()) {
-                managed_name_ids.insert(std::make_pair(methodName, 1));
+            if (managed_name_ids.find(name) == managed_name_ids.end()) {
+                managed_name_ids.insert(std::make_pair(name, 1));
             } else {
-                int id = ++managed_name_ids[methodName];
+                int id = ++managed_name_ids[name];
 
-                methodName.append("~");
-                methodName.append(std::to_string(id));
+                name.append("~");
+                name.append(std::to_string(id));
             }
 
-            data.addManagedNameForIP(ip, methodName);
+            data.addManagedNameForIP(ip, name);
+        } else if (reader.mode() == 'e') {
+            size_t gcCounter = 0;
+            size_t numChildren = 0;
+            uintptr_t objectPointer = 0;
+            uintptr_t classPointer = 0;
+
+            if (!(reader >> gcCounter) || !(reader >> numChildren) || !(reader >> objectPointer) || !(reader >> classPointer)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            // ensure class is encountered
+            const auto classId = data.addClass(classPointer, 0);
+            const auto objectId = ptrToIndex.peekPointer(objectPointer);
+            if (!objectId.second)
+                cerr << "unknown object id (" << objectPointer << ") here: " << reader.line() << endl;
+            // trace point, map current output index to parent index
+            fprintf(stdout, "e %zx %zx %zx %zx %zx\n", gcCounter, numChildren, objectPointer, classId, objectId.first.index);
+        } else if (reader.mode() == 'C') {
+            uintptr_t classPointer = 0;
+            size_t classSize = 0;
+            if (!(reader >> classPointer) || !(reader >> classSize)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            data.addClass(classPointer, classSize);
         } else {
             fputs(reader.line().c_str(), stdout);
             fputc('\n', stdout);
index 35a3914..02f04c2 100644 (file)
 #include <string>
 #include <thread>
 #include <unordered_set>
+#include <unordered_map>
 
 #include <boost/algorithm/string/replace.hpp>
 
 #include "tracetree.h"
+#include "objectgraph.h"
 #include "util/config.h"
 #include "util/libunwind_config.h"
 
@@ -62,6 +64,7 @@
 using namespace std;
 
 unordered_set<Trace::ip_t> TraceTree::knownNames;
+std::unordered_map<void*, ObjectNode> ObjectGraph::m_graph;
 
 thread_local bool RecursionGuard::isActive = false;
 
@@ -509,7 +512,10 @@ public:
         if (fprintf(s_data->out, "G 1\n") < 0) {
             writeError();
             return;
-       }
+       }
+
+        ObjectGraph graph;
+        graph.clear();
     }
 
     void handleGCSurvivedRange(void *rangeStart, unsigned long rangeLength, void *rangeMovedTo)
@@ -528,6 +534,8 @@ public:
 
     void handleFinishGC()
     {
+        static int gc_counter = 0;
+        gc_counter++;
         if (!s_data || !s_data->out) {
             return;
         }
@@ -535,7 +543,20 @@ public:
         if (fprintf(s_data->out, "G 0\n") < 0) {
             writeError();
             return;
-       }
+           }
+
+        ObjectGraph graph;
+        graph.print(gc_counter, s_data->out);
+    }
+
+    void handleLoadClass(void *classId, unsigned long classSize, char *className) {
+        std::string formattedName;
+        formattedName.append("[");
+        formattedName.append(className);
+        formattedName.append("]");
+        fprintf(s_data->out, "n %" PRIxPTR " %s\n", reinterpret_cast<uintptr_t>(classId), formattedName.c_str());
+        fprintf(s_data->out, "C %" PRIxPTR " %lx\n",  reinterpret_cast<uintptr_t>(classId), classSize);
+        TraceTree::knownNames.insert(classId);
     }
 
 private:
@@ -987,6 +1008,25 @@ void heaptrack_finishgc() {
     heaptrack.handleFinishGC();
 }
 
+void heaptrack_add_object_dep(void *keyObjectId, void *keyClassId, void *valObjectId, void *valClassId) {
+    ObjectGraph graph;
+    graph.index(keyObjectId, keyClassId, valObjectId, valClassId);
+}
+
+void heaptrack_gcroot(void *objectId, void *classId) {
+    ObjectGraph graph;
+    graph.addRoot(objectId, classId);
+}
+
+__attribute__((noinline))
+void heaptrack_loadclass(void *classId, unsigned long classSize, char *className) {
+    assert(!RecursionGuard::isActive);
+    RecursionGuard guard;
+
+    HeapTrack heaptrack(guard);
+    heaptrack.handleLoadClass(classId, classSize, className);
+}
+
 void heaptrack_invalidate_module_cache()
 {
     RecursionGuard guard;
diff --git a/src/track/objectgraph.h b/src/track/objectgraph.h
new file mode 100644 (file)
index 0000000..4ddabd0
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef OBJECTGRAPH_H
+#define OBJECTGRAPH_H
+
+#include <vector>
+#include <unordered_map>
+
+class ObjectNode {
+public:
+    ObjectNode()
+    : children(), objectId(nullptr), classId(nullptr), visited(false)
+    {
+    }
+
+    std::vector<ObjectNode*> children;
+    void *objectId;
+    void *classId;
+    bool visited;
+
+    void print(size_t gcCounter, FILE *out) {
+        fprintf(out, "e %zx %zx %zx %zx\n", gcCounter, children.size(), reinterpret_cast<uintptr_t>(objectId),  reinterpret_cast<uintptr_t>(classId));
+        for (ObjectNode* child: children) {
+            child->print(gcCounter, out);
+        }
+    }
+};
+
+class ObjectGraph {
+public:
+    ObjectGraph() {}
+
+    void addRoot(void* rootObjectID, void *rootClassID) {
+        index(nullptr, nullptr, rootObjectID, rootClassID);
+    }
+
+    void index(void *keyObjectID, void *keyClassID, void *valObjectID, void *valClassID) {
+        auto keyIt = m_graph.find(keyObjectID);
+        if (keyIt == m_graph.end()) {
+            ObjectNode node;
+            node.objectId = keyObjectID;
+            node.classId = keyClassID;
+            keyIt = m_graph.insert(std::make_pair(keyObjectID, node)).first;
+        }
+
+        auto valIt = m_graph.find(valObjectID);
+        if (valIt == m_graph.end()) {
+            ObjectNode node;
+            node.objectId = valObjectID;
+            node.classId = valClassID;
+            valIt = m_graph.insert(std::make_pair(valObjectID, node)).first;
+        }
+
+        keyIt->second.children.push_back(&(valIt->second));
+    }
+
+    void eliminateLoops(ObjectNode *node) {
+        node->visited = true;
+        node->children.erase(remove_if(node->children.begin(), node->children.end(), [](const ObjectNode* child) -> bool {
+            return child->visited;
+        }), node->children.end());
+
+        for (size_t i = 0; i < node->children.size(); ++i) {
+            eliminateLoops(node->children[i]);
+        }
+    }
+
+    void print(size_t gcCounter, FILE* out) {
+        auto it = m_graph.find(nullptr);
+        if (it == m_graph.end()) {
+            return;
+        }
+
+        ObjectNode *node = &(it->second);
+        eliminateLoops(node);
+        node->print(gcCounter, out);
+    }
+
+    void clear() {
+        m_graph.clear();
+    }
+
+    static std::unordered_map<void*, ObjectNode> m_graph;
+};
+
+#endif // OBJECTGRAPH_H
index 99e1724..a79a007 100644 (file)
@@ -89,6 +89,9 @@ struct ModuleIndex : public StringIndex
 struct FunctionIndex : public StringIndex
 {
 };
+struct ClassIndex : public StringIndex
+{
+};
 struct FileIndex : public StringIndex
 {
 };
index b994eec..2ca6605 100644 (file)
@@ -165,6 +165,24 @@ public:
         return {index, true};
     }
 
+    std::pair<AllocationIndex, bool> peekPointer(const uint64_t ptr)
+    {
+        const SplitPointer pointer(ptr);
+
+        auto mapIt = map.find(pointer.big);
+        if (mapIt == map.end()) {
+            return {{}, false};
+        }
+        auto& indices = mapIt->second;
+        auto pageIt = std::lower_bound(indices.smallPtrParts.begin(), indices.smallPtrParts.end(), pointer.small);
+        if (pageIt == indices.smallPtrParts.end() || *pageIt != pointer.small) {
+            return {{}, false};
+        }
+        auto allocationIt = indices.allocationIndices.begin() + distance(indices.smallPtrParts.begin(), pageIt);
+        auto index = *allocationIt;
+        return {index, true};
+    }
+
 private:
     struct Indices
     {