From d84a204282521b29076e4ce3cd675aa9291d6d36 Mon Sep 17 00:00:00 2001 From: Imran Khan Date: Thu, 1 Aug 2024 14:46:58 +1000 Subject: [PATCH 1/2] kernfs,memcg: Add helpers to get memcg info Add kernfs based helpers to extract memcg related information, like number of active and inactive memcgroups, page cache pages pinning memcgroups etc. Signed-off-by: Imran Khan --- drgn_tools/kernfs_memcg.py | 172 +++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 drgn_tools/kernfs_memcg.py diff --git a/drgn_tools/kernfs_memcg.py b/drgn_tools/kernfs_memcg.py new file mode 100644 index 00000000..9fb384db --- /dev/null +++ b/drgn_tools/kernfs_memcg.py @@ -0,0 +1,172 @@ +# Copyright (c) 2024, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +""" +Kernfs_memcg +-------------- + +The ``drgn.helpers.linux.kernfs_memcg`` module provides helpers for working with the +Linux memcg subsystem. +""" +from typing import Iterator + +from drgn import cast +from drgn import FaultError +from drgn import Object +from drgn import Program +from drgn.helpers.linux import cgroup_path +from drgn.helpers.linux import css_for_each_descendant_pre +from drgn.helpers.linux import dentry_path +from drgn.helpers.linux import find_slab_cache +from drgn.helpers.linux import for_each_page +from drgn.helpers.linux import kernfs_path +from drgn.helpers.linux import slab_cache_for_each_allocated_object + + +def for_each_kernfs_node(prog: Program) -> Iterator[Object]: + """ + Iterate over all kernfs_node objects in the system. + + :returns: Iterator of ``struct kernfs_node *`` objects. + """ + kernfs_node_cache = find_slab_cache(prog, "kernfs_node_cache") + for kn in slab_cache_for_each_allocated_object( + kernfs_node_cache, "struct kernfs_node" + ): + yield kn + + +def dump_memcgroup_hierarchy(prog: Program) -> None: + """ + Dump hierarchy of active mem cgroups. + """ + cgroup_subsys = prog["cgroup_subsys"][4] + css = cgroup_subsys.root.cgrp.self.address_of_() + print(f"dumping: {cgroup_subsys.name.string_().decode()} hierarchy") + for pos in css_for_each_descendant_pre(css): + print( + f"path: {cgroup_path(pos.cgroup).decode()} flags: 0x{pos.flags.value_():x}" + ) + + +def kernfs_node_of_cgroup(kn: Object) -> bool: + """ + Check if a kernfs_node object represents a cgroup object. + + :param kn: ``struct kernfs_node *`` + :returns: True if kernfs_node object represents a cgroup object, + False otherwise. + """ + if (kn.flags.value_() & 0xF) == 0x1: + try: + cgrp = Object(kn.prog_, "struct cgroup", address=kn.priv.value_()) + return cgrp.kn == kn + except FaultError: + return False + else: + return False + + +def kernfs_node_of_memcgroup(kn: Object) -> bool: + """ + Check if a kernfs_node object represents a mem cgroup object. + + :param kn: ``struct kernfs_node *`` + :returns: True if kernfs_node object represents a mem cgroup object, + False otherwise. + """ + if kernfs_node_of_cgroup(kn): + prog = kn.prog_ + cgrp = Object(prog, "struct cgroup", address=kn.priv.value_()) + return prog["cgroup_subsys"][4].root == cgrp.root + else: + return False + + +def dump_memcg_kernfs_nodes(prog) -> None: + """ + List all kernfs_node objects that represent a mem cgroup. + """ + count = 0 + for kn in for_each_kernfs_node(prog): + if kernfs_node_of_memcgroup(kn): + count = count + 1 + path = kernfs_path(kn).decode() + print("kernfs_node: ", hex(kn.value_()), " ", path) + + print("Total number of memcg kernfs_node objects: ", count) + + +def get_num_active_mem_cgroups(prog: Program) -> int: + """ + Get number of active mem cgroups. + """ + mem_cgroup_subsys = prog["cgroup_subsys"][4] + # add 1 to number of active memcgroups to account for root memcgroup + return mem_cgroup_subsys.root.cgrp.nr_descendants.value_() + 1 + + +def get_num_dying_mem_cgroups(prog: Program) -> int: + """ + Get number of inactive or dying mem cgroups. + """ + mem_cgroup_subsys = prog["cgroup_subsys"][4] + return mem_cgroup_subsys.root.cgrp.nr_dying_descendants.value_() + + +# By default we scan all pages, that have memcg ref +# but if max_pages is specified then we bail out +# after getting those many pages or scanning all +# pages , whichever happens first +def dump_page_cache_pages_pinning_cgroups(prog: Program, max_pages: int = 0): + """ + Dump all page-cache pages that have reference to a mem-cgroup. + The ouput also contains information such as the cgroup that is pinned, its flags + (to indicate current state of cgroup) and file cached by this page. + + :params: max_pages: specify how many pages to find. For default (0) all such pages + are listed. + + """ + PG_slab_mask = 1 << prog.constant("PG_slab") + mem_cgroup_root = prog["cgroup_subsys"][4].root + total_count = 0 + found_count = 0 + for page in for_each_page(prog): + total_count = total_count + 1 + try: + # Ignore slab pages + if page.flags & PG_slab_mask: + continue + # Ignore non page-cache pages + if not page.mapping: + continue + try: + mem_cgroup = page.mem_cgroup + except AttributeError: + mem_cgroup = page.memcg_data + + if not mem_cgroup.value_() or mem_cgroup.value_() & 3: + continue + cgroup_subsys_state = cast( + "struct cgroup_subsys_state *", mem_cgroup + ) + if cgroup_subsys_state.cgroup.root == mem_cgroup_root: + found_count = found_count + 1 + cgrp = cgroup_subsys_state.cgroup + address_space = page.mapping + inode = address_space.host + dentry_addr = inode.i_dentry.first.value_() - 0xB0 + if dentry_addr <= 0: + continue + dentry = Object(prog, "struct dentry", address=dentry_addr) + print( + f"page: 0x{page.value_():x} cgroup: {cgroup_path(cgrp).decode()} flags: {cgrp.self.flags.value_()} dpath: {dentry_path(dentry.address_of_()).decode()}\n" + ) + if max_pages and found_count == max_pages: + break + except FaultError: + continue + + print( + f"Scanned {total_count} pages, found {found_count} pages with memory cgroup refs." + ) From e841d78affa79da0941d628580f191d7e5047efe Mon Sep 17 00:00:00 2001 From: Imran Khan Date: Tue, 22 Oct 2024 21:41:21 +1100 Subject: [PATCH 2/2] kernfs,memcg: Add test case for memcg helpers. Signed-off-by: Imran Khan --- tests/test_kernfs_memcg.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_kernfs_memcg.py diff --git a/tests/test_kernfs_memcg.py b/tests/test_kernfs_memcg.py new file mode 100644 index 00000000..d4924f99 --- /dev/null +++ b/tests/test_kernfs_memcg.py @@ -0,0 +1,33 @@ +# Copyright (c) 2024, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +import drgn + +from drgn_tools import kernfs_memcg as kernfs_memcg + + +def test_dump_page_cache_pages_pinning_cgroups(prog: drgn.Program) -> None: + kernfs_memcg.dump_page_cache_pages_pinning_cgroups(prog, 10) + + +def test_dump_memcgroup_hierarchy(prog: drgn.Program) -> None: + kernfs_memcg.dump_memcgroup_hierarchy(prog) + + +def test_kernfs_node_of_memcgroup(prog: drgn.Program) -> None: + count = 0 + for kn in kernfs_memcg.for_each_kernfs_node(prog): + if kernfs_memcg.kernfs_node_of_memcgroup(kn): + count = count + 1 + if count >= 5: + print("Found 5 memcgroup, kernfs_node objects.") + break + + +def test_get_num_active_mem_cgroups(prog: drgn.Program) -> None: + count = kernfs_memcg.get_num_active_mem_cgroups(prog) + print(f"number of active memcgroups: {count}\n") + + +def test_get_num_dying_mem_cgroups(prog: drgn.Program) -> None: + count = kernfs_memcg.get_num_dying_mem_cgroups(prog) + print(f"number of dying memcgroups: {count}\n")