python: Move find from group to AbstractStat

This expands the 'find' feature to be recursive and find all the
stats/groups of stats of that regex all the way down the SimStats tree.

Change-Id: Id888911a6189e0440d2537f9720aa594353e00c7
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/63692
Maintainer: Bobby Bruce <bbruce@ucdavis.edu>
Reviewed-by: Bobby Bruce <bbruce@ucdavis.edu>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/python/m5/ext/pystats/abstract_stat.py b/src/python/m5/ext/pystats/abstract_stat.py
index 511ee36..f2a75fc 100644
--- a/src/python/m5/ext/pystats/abstract_stat.py
+++ b/src/python/m5/ext/pystats/abstract_stat.py
@@ -26,6 +26,15 @@
 
 from .serializable_stat import SerializableStat
 
+import re
+from typing import (
+    Callable,
+    List,
+    Optional,
+    Pattern,
+    Union,
+)
+
 
 class AbstractStat(SerializableStat):
     """
@@ -34,4 +43,55 @@
     All PyStats are JsonSerializable.
     """
 
-    pass
+    def children(
+        self,
+        predicate: Optional[Callable[[str], bool]] = None,
+        recursive: bool = False,
+    ) -> List["AbstractStat"]:
+        """Iterate through all of the children, optionally with a predicate
+
+        ```
+        >>> system.children(lambda _name: 'cpu' in name)
+        [cpu0, cpu1, cpu2]
+        ```
+
+        :param: predicate(str) -> bool: Optional. Each child's name is passed
+                to this function. If it returns true, then the child is
+                yielded. Otherwise, the child is skipped.
+                If not provided then all children are returned.
+        """
+
+        to_return = []
+        for attr in self.__dict__:
+            obj = getattr(self, attr)
+            if isinstance(obj, AbstractStat):
+                if (predicate and predicate(attr)) or not predicate:
+                    to_return.append(obj)
+                if recursive:
+                    to_return = to_return + obj.children(
+                        predicate=predicate, recursive=True
+                    )
+
+        return to_return
+
+    def find(self, regex: Union[str, Pattern]) -> List["AbstractStat"]:
+        """Find all stats that match the name, recursively through all the
+        SimStats.
+
+
+        ```
+        >>> system.find('cpu[0-9]')
+        [cpu0, cpu1, cpu2]
+        ```
+        Note: The above will not match `cpu_other`.
+
+        :param: regex: The regular expression used to search. Can be a
+                precompiled regex or a string in regex format
+        """
+        if isinstance(regex, str):
+            pattern = re.compile(regex)
+        else:
+            pattern = regex
+        return self.children(
+            lambda _name: re.match(pattern, _name), recursive=True
+        )
diff --git a/src/python/m5/ext/pystats/group.py b/src/python/m5/ext/pystats/group.py
index 7fcd665..0b71663 100644
--- a/src/python/m5/ext/pystats/group.py
+++ b/src/python/m5/ext/pystats/group.py
@@ -24,15 +24,11 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import re
 from typing import (
-    Callable,
     Dict,
-    Iterator,
     List,
     Mapping,
     Optional,
-    Pattern,
     Union,
 )
 
@@ -68,87 +64,6 @@
         for key, value in kwargs.items():
             setattr(self, key, value)
 
-    def children(
-        self, predicate: Optional[Callable[[str], bool]] = None
-    ) -> Iterator[Union["Group", Statistic]]:
-        """Iterate through all of the children, optionally with a predicate
-
-        ```
-        >>> system.children(lambda _name: 'cpu' in name)
-        [cpu0, cpu1, cpu2]
-        ```
-
-        :param: predicate(str) -> bool: Optional. Each child's name is passed
-                to this function. If it returns true, then the child is
-                yielded. Otherwise, the child is skipped.
-                If not provided then all children are returned.
-        """
-        for attr in self.__dict__:
-            # Check the provided predicate. If not a match, skip this child
-            if predicate and not predicate(attr):
-                continue
-            obj = getattr(self, attr)
-            if isinstance(obj, Group) or isinstance(obj, Statistic):
-                yield obj
-
-    def find(self, name: str) -> Iterator[Union["Group", Statistic]]:
-        """Find all stats that match the name
-
-        This function searches all of the "children" in this group. It yields
-        the set of attributes (children) that have the `name` as a substring.
-        The order of the objects returned by the generator is arbitrary.
-
-        ```
-        >>> system.find('cpu')
-        [cpu0, cpu1, cpu2, cpu3, other_cpu, ...]
-        ```
-
-        This is useful for performing aggregates over substats. For instance:
-
-        ```
-        >>> total_instructions = sum([cpu.exec_context.thread_0.numInsts.value
-                                      for cpu in simstat.system.find('cpu')])
-        100000
-        ```
-
-        :param: name: The name to search for
-        """
-        yield from self.children(lambda _name: _name in name)
-
-    def find_re(
-        self, regex: Union[str, Pattern]
-    ) -> Iterator[Union["Group", Statistic]]:
-        """Find all stats that match the name
-
-        This function searches all of the "children" in this group. It yields
-        the set of attributes (children) that have the `name` mathing the
-        regex provided. The order of the objects returned by the generator is
-        arbitrary.
-
-        ```
-        >>> system.find_re('cpu[0-9]')
-        [cpu0, cpu1, cpu2]
-        ```
-        Note: The above will not match `cpu_other`.
-
-        :param: regex: The regular expression used to search. Can be a
-                precompiled regex or a string in regex format
-        """
-        if isinstance(regex, str):
-            pattern = re.compile(regex)
-        else:
-            pattern = regex
-        yield from self.children(lambda _name: bool(pattern.search(_name)))
-
-    def _repr_name(self) -> str:
-        return "Group"
-
-    def __repr__(self) -> str:
-        stats_list = []
-        for key in self.__dict__:
-            stats_list.append(key)
-        return f"{self._repr_name()}: {stats_list}"
-
 
 class Vector(Group):
     """