stats: Add support for hierarchical stats

This change makes the stat system aware of the hierarchical nature of
stats. The aim is to achieve the following goals:

  * Make the SimObject hierarchy explicit in the stat system (i.e.,
    get rid of name() + ".foo"). This makes stat naming less fragile
    and makes it possible to implement hierarchical formats like
    XML/HDF5/JSON in a clean way.

  * Make it more convenient to split stats into a separate
    struct/class that can be bound to a SimObject. This makes the
    namespace cleaner and makes stat accesses a bit more obvious.

  * Make it possible to build groups of stats in C++ that can be used
    in subcomponents in a SimObject (similar to what we do for
    checkpoint sections). This makes it easier to structure large
    components.

  * Enable partial stat dumps. Some of our internal users have been
    asking for this since a full stat dump can be large.

  * Enable better stat access from Python.

This changeset implements solves the first three points by introducing
a class (Stats::Group) that owns statistics belonging to the same
object. SimObjects inherit from Stats::Group since they typically have
statistics.

New-style statistics need to be associated with a parent group at
instantiation time. Instantiation typically sets the name and the
description, other parameters need to be set by overriding
Group::regStats() just like with legacy stats. Simple objects with
scalar stats can typically avoid implementing regStats() altogether
since the stat name and description are both specified in the
constructor.

For convenience reasons, statistics groups can be merged into other
groups. This means that a SimObject can create a stat struct that
inherits from Stats::Group and merge it into the parent group
(SimObject). This can make the code cleaner since statistics tracking
gets grouped into a single object.

Stat visitors have a new API to expose the group structure. The
Output::beginGroup(name) method is called at the beginning of a group
and the Output::endGroup() method is called when all stats, and
sub-groups, have been visited. Flat formats (e.g., the text format)
typically need to maintain a stack to track the full path to a stat.

Legacy, flat, statistics are still supported after applying this
change. These stats don't belong to any group and stat visitors will
not see a Output::beginGroup(name) call before their corresponding
Output::visit() methods are called.

Change-Id: I9025d61dfadeabcc8ecf30813ab2060def455648
Signed-off-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/19368
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br>
diff --git a/src/base/SConscript b/src/base/SConscript
index 9449a3d..96f7b5b 100644
--- a/src/base/SConscript
+++ b/src/base/SConscript
@@ -81,6 +81,7 @@
 Source('loader/raw_object.cc')
 Source('loader/symtab.cc')
 
+Source('stats/group.cc')
 Source('stats/text.cc')
 
 GTest('addr_range.test', 'addr_range.test.cc')
@@ -101,6 +102,7 @@
 DebugFlag('GDBSend', "Messages sent to the remote application")
 DebugFlag('GDBWrite', "Writes to the remote address space")
 DebugFlag('SQL', "SQL queries sent to the server")
+DebugFlag('Stats', "Statistics management")
 DebugFlag('StatEvents', "Statistics event tracking")
 
 CompoundFlag('GDBAll',
diff --git a/src/base/statistics.cc b/src/base/statistics.cc
index 123351b..a186f97 100644
--- a/src/base/statistics.cc
+++ b/src/base/statistics.cc
@@ -1,4 +1,16 @@
 /*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
  * Copyright (c) 2003-2005 The Regents of The University of Michigan
  * All rights reserved.
  *
@@ -67,10 +79,18 @@
 }
 
 void
-InfoAccess::setInfo(Info *info)
+InfoAccess::setInfo(Group *parent, Info *info)
 {
-    if (statsMap().find(this) != statsMap().end())
-        panic("shouldn't register stat twice!");
+    panic_if(statsMap().find(this) != statsMap().end() ||
+             _info != nullptr,
+             "shouldn't register stat twice!");
+
+    // New-style stats are reachable through the hierarchy and
+    // shouldn't be added to the global lists.
+    if (parent) {
+        _info = info;
+        return;
+    }
 
     statsList().push_back(info);
 
@@ -97,17 +117,29 @@
 Info *
 InfoAccess::info()
 {
-    MapType::const_iterator i = statsMap().find(this);
-    assert(i != statsMap().end());
-    return (*i).second;
+    if (_info) {
+        // New-style stats
+        return _info;
+    } else {
+        // Legacy stats
+        MapType::const_iterator i = statsMap().find(this);
+        assert(i != statsMap().end());
+        return (*i).second;
+    }
 }
 
 const Info *
 InfoAccess::info() const
 {
-    MapType::const_iterator i = statsMap().find(this);
-    assert(i != statsMap().end());
-    return (*i).second;
+    if (_info) {
+        // New-style stats
+        return _info;
+    } else {
+        // Legacy stats
+        MapType::const_iterator i = statsMap().find(this);
+        assert(i != statsMap().end());
+        return (*i).second;
+    }
 }
 
 StorageParams::~StorageParams()
@@ -224,7 +256,8 @@
 #endif
         panic("Not all stats have been initialized.\n"
               "You may need to add <ParentClass>::regStats() to a"
-              " new SimObject's regStats() function.");
+              " new SimObject's regStats() function. Name: %s",
+              name);
         return false;
     }
 
@@ -376,19 +409,23 @@
         cvec[i] += hs->cvec[i];
 }
 
-Formula::Formula()
+Formula::Formula(Group *parent, const char *name, const char *desc)
+    : DataWrapVec<Formula, FormulaInfoProxy>(parent, name, desc)
+
 {
 }
 
-Formula::Formula(Temp r)
+
+
+Formula::Formula(Group *parent, const char *name, const char *desc,
+                 const Temp &r)
+    : DataWrapVec<Formula, FormulaInfoProxy>(parent, name, desc)
 {
-    root = r.getNodePtr();
-    setInit();
-    assert(size());
+    *this = r;
 }
 
 const Formula &
-Formula::operator=(Temp r)
+Formula::operator=(const Temp &r)
 {
     assert(!root && "Can't change formulas");
     root = r.getNodePtr();
@@ -421,6 +458,7 @@
     return *this;
 }
 
+
 void
 Formula::result(VResult &vec) const
 {
diff --git a/src/base/statistics.hh b/src/base/statistics.hh
index 6ddf7ff..f4fa123 100644
--- a/src/base/statistics.hh
+++ b/src/base/statistics.hh
@@ -1,4 +1,16 @@
 /*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
  * Copyright (c) 2003-2005 The Regents of The University of Michigan
  * Copyright (c) 2017, Centre National de la Recherche Scientifique
  * All rights reserved.
@@ -63,6 +75,7 @@
 #include <string>
 #include <vector>
 
+#include "base/stats/group.hh"
 #include "base/stats/info.hh"
 #include "base/stats/output.hh"
 #include "base/stats/types.hh"
@@ -172,9 +185,12 @@
 
 class InfoAccess
 {
+  private:
+    Info *_info;
+
   protected:
     /** Set up an info class for this statistic */
-    void setInfo(Info *info);
+    void setInfo(Group *parent, Info *info);
     /** Save Storage class parameters if any */
     void setParams(const StorageParams *params);
     /** Save Storage class parameters if any */
@@ -186,6 +202,9 @@
     const Info *info() const;
 
   public:
+    InfoAccess()
+        : _info(nullptr) {};
+
     /**
      * Reset the stat to the default state.
      */
@@ -228,21 +247,27 @@
         return safe_cast<const Info *>(InfoAccess::info());
     }
 
-  protected:
-    /**
-     * Copy constructor, copies are not allowed.
-     */
-    DataWrap(const DataWrap &stat) = delete;
-
-    /**
-     * Can't copy stats.
-     */
-    void operator=(const DataWrap &) {}
-
   public:
-    DataWrap()
+    DataWrap() = delete;
+    DataWrap(const DataWrap &) = delete;
+    DataWrap &operator=(const DataWrap &) = delete;
+
+
+    DataWrap(Group *parent, const char *name, const char *desc)
     {
-        this->setInfo(new Info(self()));
+        auto info = new Info(self());
+        this->setInfo(parent, info);
+
+        if (parent)
+            parent->addStat(info);
+
+        if (name) {
+            info->setName(name);
+            info->flags.set(display);
+        }
+
+        if (desc)
+            info->desc = desc;
     }
 
     /**
@@ -335,13 +360,9 @@
   public:
     typedef InfoProxyType<Derived> Info;
 
-    DataWrapVec()
-    {}
-
-    DataWrapVec(const DataWrapVec &ref)
-    {}
-
-    void operator=(const DataWrapVec &)
+    DataWrapVec(Group *parent = nullptr, const char *name = nullptr,
+                const char *desc = nullptr)
+        : DataWrap<Derived, InfoProxyType>(parent, name, desc)
     {}
 
     // The following functions are specific to vectors.  If you use them
@@ -420,6 +441,11 @@
   public:
     typedef InfoProxyType<Derived> Info;
 
+    DataWrapVec2d(Group *parent, const char *name, const char *desc)
+        : DataWrapVec<Derived, InfoProxyType>(parent, name, desc)
+    {
+    }
+
     /**
      * @warning This makes the assumption that if you're gonna subnames a 2d
      * vector, you're subnaming across all y
@@ -677,7 +703,9 @@
     Counter value() const { return data()->value(); }
 
   public:
-    ScalarBase()
+    ScalarBase(Group *parent = nullptr, const char *name = nullptr,
+               const char *desc = nullptr)
+        : DataWrap<Derived, ScalarInfoProxy>(parent, name, desc)
     {
         this->doInit();
     }
@@ -807,7 +835,12 @@
     ProxyInfo *proxy;
 
   public:
-    ValueBase() : proxy(NULL) { }
+    ValueBase(Group *parent, const char *name, const char *desc)
+        : DataWrap<Derived, ScalarInfoProxy>(parent, name, desc),
+          proxy(NULL)
+    {
+    }
+
     ~ValueBase() { if (proxy) delete proxy; }
 
     template <class T>
@@ -1095,8 +1128,9 @@
     }
 
   public:
-    VectorBase()
-        : storage(nullptr), _size(0)
+    VectorBase(Group *parent, const char *name, const char *desc)
+        : DataWrapVec<Derived, VectorInfoProxy>(parent, name, desc),
+          storage(nullptr), _size(0)
     {}
 
     ~VectorBase()
@@ -1235,8 +1269,9 @@
     const Storage *data(off_type index) const { return &storage[index]; }
 
   public:
-    Vector2dBase()
-        : x(0), y(0), _size(0), storage(nullptr)
+    Vector2dBase(Group *parent, const char *name, const char *desc)
+        : DataWrapVec2d<Derived, Vector2dInfoProxy>(parent, name, desc),
+          x(0), y(0), _size(0), storage(nullptr)
     {}
 
     ~Vector2dBase()
@@ -1851,7 +1886,10 @@
     }
 
   public:
-    DistBase() { }
+    DistBase(Group *parent, const char *name, const char *desc)
+        : DataWrap<Derived, DistInfoProxy>(parent, name, desc)
+    {
+    }
 
     /**
      * Add a value to the distribtion n times. Calls sample on the storage
@@ -1945,8 +1983,9 @@
     }
 
   public:
-    VectorDistBase()
-        : storage(NULL)
+    VectorDistBase(Group *parent, const char *name, const char *desc)
+        : DataWrapVec<Derived, VectorDistInfoProxy>(parent, name, desc),
+          storage(NULL)
     {}
 
     ~VectorDistBase()
@@ -2472,6 +2511,12 @@
 {
   public:
     using ScalarBase<Scalar, StatStor>::operator=;
+
+    Scalar(Group *parent = nullptr, const char *name = nullptr,
+           const char *desc = nullptr)
+        : ScalarBase<Scalar, StatStor>(parent, name, desc)
+    {
+    }
 };
 
 /**
@@ -2482,10 +2527,22 @@
 {
   public:
     using ScalarBase<Average, AvgStor>::operator=;
+
+    Average(Group *parent = nullptr, const char *name = nullptr,
+            const char *desc = nullptr)
+        : ScalarBase<Average, AvgStor>(parent, name, desc)
+    {
+    }
 };
 
 class Value : public ValueBase<Value>
 {
+  public:
+    Value(Group *parent = nullptr, const char *name = nullptr,
+          const char *desc = nullptr)
+        : ValueBase<Value>(parent, name, desc)
+    {
+    }
 };
 
 /**
@@ -2494,6 +2551,12 @@
  */
 class Vector : public VectorBase<Vector, StatStor>
 {
+  public:
+    Vector(Group *parent = nullptr, const char *name = nullptr,
+           const char *desc = nullptr)
+        : VectorBase<Vector, StatStor>(parent, name, desc)
+    {
+    }
 };
 
 /**
@@ -2502,6 +2565,12 @@
  */
 class AverageVector : public VectorBase<AverageVector, AvgStor>
 {
+  public:
+    AverageVector(Group *parent = nullptr, const char *name = nullptr,
+                  const char *desc = nullptr)
+        : VectorBase<AverageVector, AvgStor>(parent, name, desc)
+    {
+    }
 };
 
 /**
@@ -2510,6 +2579,12 @@
  */
 class Vector2d : public Vector2dBase<Vector2d, StatStor>
 {
+  public:
+    Vector2d(Group *parent = nullptr, const char *name = nullptr,
+             const char *desc = nullptr)
+        : Vector2dBase<Vector2d, StatStor>(parent, name, desc)
+    {
+    }
 };
 
 /**
@@ -2519,6 +2594,12 @@
 class Distribution : public DistBase<Distribution, DistStor>
 {
   public:
+    Distribution(Group *parent = nullptr, const char *name = nullptr,
+                 const char *desc = nullptr)
+        : DistBase<Distribution, DistStor>(parent, name, desc)
+    {
+    }
+
     /**
      * Set the parameters of this distribution. @sa DistStor::Params
      * @param min The minimum value of the distribution.
@@ -2550,6 +2631,12 @@
 class Histogram : public DistBase<Histogram, HistStor>
 {
   public:
+    Histogram(Group *parent = nullptr, const char *name = nullptr,
+              const char *desc = nullptr)
+        : DistBase<Histogram, HistStor>(parent, name, desc)
+    {
+    }
+
     /**
      * Set the parameters of this histogram. @sa HistStor::Params
      * @param size The number of buckets in the histogram
@@ -2576,7 +2663,9 @@
     /**
      * Construct and initialize this distribution.
      */
-    StandardDeviation()
+    StandardDeviation(Group *parent = nullptr, const char *name = nullptr,
+                      const char *desc = nullptr)
+        : DistBase<StandardDeviation, SampleStor>(parent, name, desc)
     {
         SampleStor::Params *params = new SampleStor::Params;
         this->doInit();
@@ -2594,7 +2683,9 @@
     /**
      * Construct and initialize this distribution.
      */
-    AverageDeviation()
+    AverageDeviation(Group *parent = nullptr, const char *name = nullptr,
+                     const char *desc = nullptr)
+        : DistBase<AverageDeviation, AvgSampleStor>(parent, name, desc)
     {
         AvgSampleStor::Params *params = new AvgSampleStor::Params;
         this->doInit();
@@ -2609,6 +2700,12 @@
 class VectorDistribution : public VectorDistBase<VectorDistribution, DistStor>
 {
   public:
+    VectorDistribution(Group *parent = nullptr, const char *name = nullptr,
+                       const char *desc = nullptr)
+        : VectorDistBase<VectorDistribution, DistStor>(parent, name, desc)
+    {
+    }
+
     /**
      * Initialize storage and parameters for this distribution.
      * @param size The size of the vector (the number of distributions).
@@ -2639,6 +2736,13 @@
     : public VectorDistBase<VectorStandardDeviation, SampleStor>
 {
   public:
+    VectorStandardDeviation(Group *parent = nullptr, const char *name = nullptr,
+                            const char *desc = nullptr)
+        : VectorDistBase<VectorStandardDeviation, SampleStor>(parent, name,
+                                                              desc)
+    {
+    }
+
     /**
      * Initialize storage for this distribution.
      * @param size The size of the vector.
@@ -2662,6 +2766,13 @@
     : public VectorDistBase<VectorAverageDeviation, AvgSampleStor>
 {
   public:
+    VectorAverageDeviation(Group *parent = nullptr, const char *name = nullptr,
+                           const char *desc = nullptr)
+        : VectorDistBase<VectorAverageDeviation, AvgSampleStor>(parent, name,
+                                                                desc)
+    {
+    }
+
     /**
      * Initialize storage for this distribution.
      * @param size The size of the vector.
@@ -2753,7 +2864,10 @@
     }
 
   public:
-    SparseHistBase() { }
+    SparseHistBase(Group *parent, const char *name, const char *desc)
+        : DataWrap<Derived, SparseHistInfoProxy>(parent, name, desc)
+    {
+    }
 
     /**
      * Add a value to the distribtion n times. Calls sample on the storage
@@ -2870,6 +2984,12 @@
 class SparseHistogram : public SparseHistBase<SparseHistogram, SparseHistStor>
 {
   public:
+    SparseHistogram(Group *parent = nullptr, const char *name = nullptr,
+                    const char *desc = nullptr)
+        : SparseHistBase<SparseHistogram, SparseHistStor>(parent, name, desc)
+    {
+    }
+
     /**
      * Set the parameters of this histogram. @sa HistStor::Params
      * @param size The number of buckets in the histogram
@@ -2902,21 +3022,25 @@
     /**
      * Create and initialize thie formula, and register it with the database.
      */
-    Formula();
+    Formula(Group *parent = nullptr, const char *name = nullptr,
+            const char *desc = nullptr);
 
-    /**
-     * Create a formula with the given root node, register it with the
-     * database.
-     * @param r The root of the expression tree.
-     */
-    Formula(Temp r);
+    Formula(Group *parent, const char *name, const char *desc,
+            const Temp &r);
 
     /**
      * Set an unitialized Formula to the given root.
      * @param r The root of the expression tree.
      * @return a reference to this formula.
      */
-    const Formula &operator=(Temp r);
+    const Formula &operator=(const Temp &r);
+
+    template<typename T>
+    const Formula &operator=(const T &v)
+    {
+        *this = Temp(v);
+        return *this;
+    }
 
     /**
      * Add the given tree to the existing one.
diff --git a/src/base/stats/group.cc b/src/base/stats/group.cc
new file mode 100644
index 0000000..2bfc89d
--- /dev/null
+++ b/src/base/stats/group.cc
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Andreas Sandberg
+ */
+
+#include "base/stats/group.hh"
+
+#include <cassert>
+
+#include "base/stats/info.hh"
+#include "base/trace.hh"
+#include "debug/Stats.hh"
+#include "sim/sim_object.hh"
+
+namespace Stats {
+
+Group::Group(Group *parent, const char *name)
+    : mergedParent(name ? nullptr : parent)
+{
+    if (parent && name) {
+        parent->addStatGroup(name, this);
+    } else if (parent && !name) {
+        parent->mergeStatGroup(this);
+    }
+}
+
+Group::~Group()
+{
+}
+
+void
+Group::regStats()
+{
+    for (auto &g : mergedStatGroups)
+        g->regStats();
+
+    for (auto &g : statGroups) {
+        if (DTRACE(Stats)) {
+            const SimObject *so = dynamic_cast<const SimObject *>(this);
+            DPRINTF(Stats, "%s: regStats in group %s\n",
+                    so ? so->name() : "?",
+                    g.first);
+        }
+        g.second->regStats();
+    }
+}
+
+void
+Group::resetStats()
+{
+    for (auto &s : stats)
+        s->reset();
+
+    for (auto &g : mergedStatGroups)
+        g->resetStats();
+
+    for (auto &g : statGroups)
+        g.second->resetStats();
+}
+
+void
+Group::addStat(Stats::Info *info)
+{
+    stats.push_back(info);
+    if (mergedParent)
+        mergedParent->addStat(info);
+}
+
+void
+Group::addStatGroup(const char *name, Group *block)
+{
+    assert(statGroups.find(name) == statGroups.end());
+
+    statGroups[name] = block;
+}
+
+void
+Group::mergeStatGroup(Group *block)
+{
+    mergedStatGroups.push_back(block);
+}
+
+const std::map<std::string, Group *> &
+Group::getStatGroups() const
+{
+    return statGroups;
+}
+
+const std::vector<Info *> &
+Group::getStats() const
+{
+    return stats;
+}
+
+} // namespace Stats
diff --git a/src/base/stats/group.hh b/src/base/stats/group.hh
new file mode 100644
index 0000000..f65e464
--- /dev/null
+++ b/src/base/stats/group.hh
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Authors: Andreas Sandberg
+ */
+
+#ifndef __BASE_STATS_GROUP_HH__
+#define __BASE_STATS_GROUP_HH__
+
+#include <map>
+#include <vector>
+#include <string>
+
+/**
+ * Convenience macro to add a stat to a statistics group.
+ *
+ * This macro is used to add a stat to a Stats::Group in the
+ * initilization list in the Group's constructor. The macro
+ * automatically assigns the stat to the current group and gives it
+ * the same name as in the class. For example:
+ *
+ * \code
+ * struct MyStats : public Stats::Group
+ * {
+ *     Stats::Scalar scalar0;
+ *     Stats::Scalar scalar1;
+ *
+ *     Group()
+ *         : ADD_STAT(scalar0, "Description of scalar0"),
+ *           scalar1(this, "scalar1", "Description of scalar1")
+ *     {
+ *     }
+ * };
+ * \endcode
+ */
+#define ADD_STAT(n, ...) n(this, # n, __VA_ARGS__)
+
+namespace Stats {
+
+class Info;
+
+/**
+ * Statistics container.
+ *
+ * A stat group is a hierarchical structure that contain statistics
+ * and other groups. Groups are used by the stat system to reflect
+ * gem5's SimObject hierarchy and to expose internal hierarchy within
+ * an object. They can also be used to conveniently group stats into
+ * their own class/struct and then be merged into the parent group
+ * (typically a SimObject).
+ */
+class Group
+{
+  public:
+    Group() = delete;
+    Group(const Group &) = delete;
+    Group &operator=(const Group &) = delete;
+
+    /**
+     * Construct a new statistics group.
+     *
+     * The constructor takes two parameters, a parent and a name. The
+     * parent group should typically be specified. However, there are
+     * special cases where the parent group may be null. One such
+     * special case is SimObjects where the Python code performs late
+     * binding of the group parent.
+     *
+     * If the name parameter is NULL, the group gets merged into the
+     * parent group instead of creating a sub-group. Stats belonging
+     * to a merged group behave as if they have been added directly to
+     * the parent group.
+     *
+     * @param parent Parent group to associate this object to.
+     * @param name Name of this group, can be NULL to merge this group
+     * with the parent group.
+     */
+    Group(Group *parent, const char *name = nullptr);
+
+    virtual ~Group();
+
+    /**
+     * Callback to set stat parameters.
+     *
+     * This callback is typically used for complex stats (e.g.,
+     * distributions) that need parameters in addition to a name and a
+     * description. Stat names and descriptions should typically be
+     * set from the constructor usingo from the constructor using the
+     * ADD_STAT macro.
+     */
+    virtual void regStats();
+
+    /**
+     * Callback to reset stats.
+     */
+    virtual void resetStats();
+
+    /**
+     * Register a stat with this group. This method is normally called
+     * automatically when a stat is instantiated.
+     */
+    void addStat(Stats::Info *info);
+
+    /**
+     * Get all child groups associated with this object.
+     */
+    const std::map<std::string, Group *> &getStatGroups() const;
+
+    /**
+     * Get all stats associated with this object.
+     */
+    const std::vector<Info *> &getStats() const;
+
+     /**
+     * Add a stat block as a child of this block
+     *
+     * This method may only be called from a Group constructor or from
+     * regStats. It's typically only called explicitly from Python
+     * when setting up the SimObject hierarchy.
+     */
+    void addStatGroup(const char *name, Group *block);
+
+  private:
+    /**
+     * Merge the contents (stats & children) of a block to this block.
+     *
+     * This is called on a parent group by the child when it is being
+     * merged into the parent.
+     */
+    void mergeStatGroup(Group *block);
+
+  private:
+    /** Parent pointer if merged into parent */
+    Group *const mergedParent;
+
+    std::map<std::string, Group *> statGroups;
+    std::vector<Group *> mergedStatGroups;
+    std::vector<Info *> stats;
+};
+
+} // namespace Stats
+
+#endif // __BASE_STATS_GROUP_HH__
diff --git a/src/base/stats/output.hh b/src/base/stats/output.hh
index 9cd33a5..fe3860d 100644
--- a/src/base/stats/output.hh
+++ b/src/base/stats/output.hh
@@ -1,4 +1,16 @@
 /*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
  * Copyright (c) 2004-2005 The Regents of The University of Michigan
  * All rights reserved.
  *
@@ -48,10 +60,14 @@
 struct Output
 {
     virtual ~Output() {}
+
     virtual void begin() = 0;
     virtual void end() = 0;
     virtual bool valid() const = 0;
 
+    virtual void beginGroup(const char *name) = 0;
+    virtual void endGroup() = 0;
+
     virtual void visit(const ScalarInfo &info) = 0;
     virtual void visit(const VectorInfo &info) = 0;
     virtual void visit(const DistInfo &info) = 0;
diff --git a/src/base/stats/text.cc b/src/base/stats/text.cc
index 10e94a7..da68188 100644
--- a/src/base/stats/text.cc
+++ b/src/base/stats/text.cc
@@ -1,4 +1,16 @@
 /*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
  * Copyright (c) 2004-2005 The Regents of The University of Michigan
  * All rights reserved.
  *
@@ -153,6 +165,32 @@
     stream->flush();
 }
 
+std::string
+Text::statName(const std::string &name) const
+{
+    if (path.empty())
+        return name;
+    else
+        return csprintf("%s.%s", path.top(), name);
+}
+
+void
+Text::beginGroup(const char *name)
+{
+    if (path.empty()) {
+        path.push(name);
+    } else {
+        path.push(csprintf("%s.%s", path.top(), name));
+    }
+}
+
+void
+Text::endGroup()
+{
+    assert(!path.empty());
+    path.pop();
+}
+
 bool
 Text::noOutput(const Info &info)
 {
@@ -368,7 +406,7 @@
 void
 DistPrint::init(const Text *text, const Info &info)
 {
-    name = info.name;
+    name = text->statName(info.name);
     separatorString = info.separatorString;
     desc = info.desc;
     flags = info.flags;
@@ -511,7 +549,7 @@
 
     ScalarPrint print;
     print.value = info.result();
-    print.name = info.name;
+    print.name = statName(info.name);
     print.desc = info.desc;
     print.flags = info.flags;
     print.descriptions = descriptions;
@@ -531,7 +569,7 @@
     size_type size = info.size();
     VectorPrint print;
 
-    print.name = info.name;
+    print.name = statName(info.name);
     print.separatorString = info.separatorString;
     print.desc = info.desc;
     print.flags = info.flags;
@@ -606,8 +644,9 @@
             total += yvec[j];
         }
 
-        print.name = info.name + "_" +
-            (havesub ? info.subnames[i] : std::to_string(i));
+        print.name = statName(
+            info.name + "_" +
+            (havesub ? info.subnames[i] : std::to_string(i)));
         print.desc = info.desc;
         print.vec = yvec;
         print.total = total;
@@ -619,7 +658,7 @@
     total_subname.push_back("total");
 
     if (info.flags.isSet(::Stats::total) && (info.x > 1)) {
-        print.name = info.name;
+        print.name = statName(info.name);
         print.subnames = total_subname;
         print.desc = info.desc;
         print.vec = VResult(1, info.total());
@@ -687,7 +726,7 @@
 void
 SparseHistPrint::init(const Text *text, const Info &info)
 {
-    name = info.name;
+    name = text->statName(info.name);
     separatorString = info.separatorString;
     desc = info.desc;
     flags = info.flags;
diff --git a/src/base/stats/text.hh b/src/base/stats/text.hh
index 8bb290a..c8fba5a 100644
--- a/src/base/stats/text.hh
+++ b/src/base/stats/text.hh
@@ -1,4 +1,16 @@
 /*
+ * Copyright (c) 2019 Arm Limited
+ * All rights reserved.
+ *
+ * The license below extends only to copyright in the software and shall
+ * not be construed as granting a license to any other intellectual
+ * property including but not limited to intellectual property relating
+ * to a hardware implementation of the functionality of the software
+ * licensed hereunder.  You may use the software subject to the license
+ * terms below provided that you ensure that this notice is replicated
+ * unmodified and in its entirety in all distributions of the software,
+ * modified or unmodified, in source code or in binary form.
+ *
  * Copyright (c) 2004-2005 The Regents of The University of Michigan
  * All rights reserved.
  *
@@ -32,6 +44,7 @@
 #define __BASE_STATS_TEXT_HH__
 
 #include <iosfwd>
+#include <stack>
 #include <string>
 
 #include "base/stats/output.hh"
@@ -46,6 +59,9 @@
     bool mystream;
     std::ostream *stream;
 
+    // Object/group path
+    std::stack<std::string> path;
+
   protected:
     bool noOutput(const Info &info);
 
@@ -60,20 +76,25 @@
 
     void open(std::ostream &stream);
     void open(const std::string &file);
+    std::string statName(const std::string &name) const;
 
     // Implement Visit
-    virtual void visit(const ScalarInfo &info);
-    virtual void visit(const VectorInfo &info);
-    virtual void visit(const DistInfo &info);
-    virtual void visit(const VectorDistInfo &info);
-    virtual void visit(const Vector2dInfo &info);
-    virtual void visit(const FormulaInfo &info);
-    virtual void visit(const SparseHistInfo &info);
+    void visit(const ScalarInfo &info) override;
+    void visit(const VectorInfo &info) override;
+    void visit(const DistInfo &info) override;
+    void visit(const VectorDistInfo &info) override;
+    void visit(const Vector2dInfo &info) override;
+    void visit(const FormulaInfo &info) override;
+    void visit(const SparseHistInfo &info) override;
+
+    // Group handling
+    void beginGroup(const char *name) override;
+    void endGroup() override;
 
     // Implement Output
-    virtual bool valid() const;
-    virtual void begin();
-    virtual void end();
+    bool valid() const override;
+    void begin() override;
+    void end() override;
 };
 
 std::string ValueToString(Result value, int precision);
diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py
index 317ae6c..3d2f123 100644
--- a/src/python/m5/SimObject.py
+++ b/src/python/m5/SimObject.py
@@ -1086,7 +1086,7 @@
     abstract = True
 
     cxx_header = "sim/sim_object.hh"
-    cxx_extra_bases = [ "Drainable", "Serializable" ]
+    cxx_extra_bases = [ "Drainable", "Serializable", "Stats::Group" ]
     eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
 
     cxx_exports = [
@@ -1094,8 +1094,6 @@
         PyBindMethod("initState"),
         PyBindMethod("memInvalidate"),
         PyBindMethod("memWriteback"),
-        PyBindMethod("regStats"),
-        PyBindMethod("resetStats"),
         PyBindMethod("regProbePoints"),
         PyBindMethod("regProbeListeners"),
         PyBindMethod("startup"),
diff --git a/src/python/m5/simulate.py b/src/python/m5/simulate.py
index f8da1fb..1369fb5 100644
--- a/src/python/m5/simulate.py
+++ b/src/python/m5/simulate.py
@@ -124,7 +124,8 @@
     for obj in root.descendants(): obj.init()
 
     # Do a third pass to initialize statistics
-    for obj in root.descendants(): obj.regStats()
+    stats._bindStatHierarchy(root)
+    root.regStats()
 
     # Do a fourth pass to initialize probe points
     for obj in root.descendants(): obj.regProbePoints()
diff --git a/src/python/m5/stats/__init__.py b/src/python/m5/stats/__init__.py
index 019c7eb..d9174d3 100644
--- a/src/python/m5/stats/__init__.py
+++ b/src/python/m5/stats/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 ARM Limited
+# Copyright (c) 2017, 2019 Arm Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
@@ -158,6 +158,29 @@
     _m5.stats.initSimStats()
     _m5.stats.registerPythonStatsHandlers()
 
+def _visit_groups(root, visitor):
+    for group in root.getStatGroups().values():
+        visitor(group)
+        _visit_groups(group, visitor)
+
+def _visit_stats(root, visitor):
+    def for_each_stat(g):
+        for stat in g.getStats():
+            visitor(g, stat)
+    _visit_groups(root, for_each_stat)
+
+def _bindStatHierarchy(root):
+    def _bind_obj(name, obj):
+        if m5.SimObject.isSimObjectVector(obj):
+            for idx, obj in enumerate(obj):
+                _bind_obj("{}{}".format(name, idx), obj)
+        else:
+            root.addStatGroup(name, obj.getCCObject())
+            _bindStatHierarchy(obj)
+
+    for name, obj in root._children.items():
+        _bind_obj(name, obj)
+
 names = []
 stats_dict = {}
 stats_list = []
@@ -166,10 +189,7 @@
     enabled, all statistics must be created and initialized and once
     the package is enabled, no more statistics can be created.'''
 
-    global stats_list
-    stats_list = list(_m5.stats.statsList())
-
-    for stat in stats_list:
+    def check_stat(group, stat):
         if not stat.check() or not stat.baseCheck():
             fatal("statistic '%s' (%d) was not properly initialized " \
                   "by a regStats() function\n", stat.name, stat.id)
@@ -177,21 +197,57 @@
         if not (stat.flags & flags.display):
             stat.name = "__Stat%06d" % stat.id
 
+
+    # Legacy stat
+    global stats_list
+    stats_list = list(_m5.stats.statsList())
+
+    for stat in stats_list:
+        check_stat(None, stat)
+
     stats_list.sort(key=lambda s: s.name.split('.'))
     for stat in stats_list:
         stats_dict[stat.name] = stat
         stat.enable()
 
+
+    # New stats
+    _visit_stats(Root.getInstance(), check_stat)
+    _visit_stats(Root.getInstance(), lambda g, s: s.enable())
+
     _m5.stats.enable();
 
 def prepare():
     '''Prepare all stats for data access.  This must be done before
     dumping and serialization.'''
 
+    # Legacy stats
     for stat in stats_list:
         stat.prepare()
 
+    # New stats
+    _visit_stats(Root.getInstance(), lambda g, s: s.prepare())
+
 lastDump = 0
+
+def _dump_to_visitor(visitor):
+    # Legacy stats
+    for stat in stats_list:
+        stat.visit(visitor)
+
+    # New stats
+    def dump_group(group):
+        for stat in group.getStats():
+            stat.visit(visitor)
+
+        for n, g in group.getStatGroups().items():
+            visitor.beginGroup(n)
+            dump_group(g)
+            visitor.endGroup()
+
+    dump_group(Root.getInstance())
+
+
 def dump():
     '''Dump all statistics data to the registered outputs'''
 
@@ -210,8 +266,7 @@
     for output in outputList:
         if output.valid():
             output.begin()
-            for stat in stats_list:
-                stat.visit(output)
+            _dump_to_visitor(output)
             output.end()
 
 def reset():
@@ -220,9 +275,9 @@
     # call reset stats on all SimObjects
     root = Root.getInstance()
     if root:
-        for obj in root.descendants(): obj.resetStats()
+        root.resetStats()
 
-    # call any other registered stats reset callbacks
+    # call any other registered legacy stats reset callbacks
     for stat in stats_list:
         stat.reset()
 
diff --git a/src/python/pybind11/stats.cc b/src/python/pybind11/stats.cc
index 39d9ce8..1302c7c 100644
--- a/src/python/pybind11/stats.cc
+++ b/src/python/pybind11/stats.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 ARM Limited
+ * Copyright (c) 2017, 2019 ARM Limited
  * All rights reserved
  *
  * The license below extends only to copyright in the software and shall
@@ -93,9 +93,12 @@
         .def("begin", &Stats::Output::begin)
         .def("end", &Stats::Output::end)
         .def("valid", &Stats::Output::valid)
+        .def("beginGroup", &Stats::Output::beginGroup)
+        .def("endGroup", &Stats::Output::endGroup)
         ;
 
-    py::class_<Stats::Info>(m, "Info")
+    py::class_<Stats::Info, std::unique_ptr<Stats::Info, py::nodelete>>(
+        m, "Info")
         .def_readwrite("name", &Stats::Info::name)
         .def_readonly("desc", &Stats::Info::desc)
         .def_readonly("id", &Stats::Info::id)
@@ -110,4 +113,13 @@
         .def("zero", &Stats::Info::zero)
         .def("visit", &Stats::Info::visit)
         ;
+
+    py::class_<Stats::Group, std::unique_ptr<Stats::Group, py::nodelete>>(
+        m, "Group")
+        .def("regStats", &Stats::Group::regStats)
+        .def("resetStats", &Stats::Group::resetStats)
+        .def("getStats", &Stats::Group::getStats)
+        .def("getStatGroups", &Stats::Group::getStatGroups)
+        .def("addStatGroup", &Stats::Group::addStatGroup)
+        ;
 }
diff --git a/src/sim/sim_object.cc b/src/sim/sim_object.cc
index 7b794a0..eb6e15a 100644
--- a/src/sim/sim_object.cc
+++ b/src/sim/sim_object.cc
@@ -56,7 +56,9 @@
 // SimObject constructor: used to maintain static simObjectList
 //
 SimObject::SimObject(const Params *p)
-    : EventManager(getEventQueue(p->eventq_index)), _params(p)
+    : EventManager(getEventQueue(p->eventq_index)),
+      Stats::Group(nullptr),
+      _params(p)
 {
 #ifdef DEBUG
     doDebugBreak = false;
@@ -98,19 +100,6 @@
 {
 }
 
-//
-// no default statistics, so nothing to do in base implementation
-//
-void
-SimObject::regStats()
-{
-}
-
-void
-SimObject::resetStats()
-{
-}
-
 /**
  * No probe points by default, so do nothing in base.
  */
diff --git a/src/sim/sim_object.hh b/src/sim/sim_object.hh
index 5c9bf00..c938ba5 100644
--- a/src/sim/sim_object.hh
+++ b/src/sim/sim_object.hh
@@ -52,6 +52,7 @@
 #include <string>
 #include <vector>
 
+#include "base/stats/group.hh"
 #include "params/SimObject.hh"
 #include "sim/drain.hh"
 #include "sim/eventq.hh"
@@ -92,7 +93,8 @@
  * SimObject.py). This has the effect of calling the method on the
  * parent node <i>before</i> its children.
  */
-class SimObject : public EventManager, public Serializable, public Drainable
+class SimObject : public EventManager, public Serializable, public Drainable,
+                  public Stats::Group
 {
   private:
     typedef std::vector<SimObject *> SimObjectList;
@@ -146,16 +148,6 @@
     virtual void initState();
 
     /**
-     * Register statistics for this object.
-     */
-    virtual void regStats();
-
-    /**
-     * Reset statistics associated with this object.
-     */
-    virtual void resetStats();
-
-    /**
      * Register probe points for this object.
      */
     virtual void regProbePoints();