diff --git a/src/python/libcgroup.pyx.m4 b/src/python/libcgroup.pyx.m4 index cc1bf03b..471df3d1 100644 --- a/src/python/libcgroup.pyx.m4 +++ b/src/python/libcgroup.pyx.m4 @@ -1162,6 +1162,59 @@ class LibcgroupList(LibcgroupTree): cgpid.pidstats['Command'], cgpid.pidstats[self.metric], cgpid.cgroup)) +class LibcgroupPsiList(LibcgroupTree): + def __init__(self, name, controller='cpu', depth=None, psi_field='some-avg10', + threshold=None, limit=None): + super().__init__(name, version=Version.CGROUP_V2, controller=controller, + depth=depth) + + self.controller = controller + self.psi_field = psi_field + self.cglist = list() + self.threshold = threshold + self.limit = limit + + def walk_action(self, cg): + cg.get_psi(self.controller) + + if not self.threshold: + self.cglist.append(cg) + elif cg.psi[self.psi_field] >= self.threshold: + self.cglist.append(cg) + + def sort(self): + self.cglist = sorted(self.cglist, reverse=True, + key=lambda cg: cg.psi[self.psi_field]) + + def _show_float(self): + print('{0: >10} {1: >3} {2: <16}'.`format'(self.psi_field, 'PSI', 'CGROUP')) + + for i, cg in enumerate(self.cglist): + if self.limit and i >= self.limit: + break + + print(' {0: 6.2f} {1: <16}'.`format'(cg.psi[self.psi_field], + cg.path[`len'(self.start_path):])) + + def _show_int(self): + print('{0: >10} {1: >3} {2: <16}'.`format'(self.psi_field, 'PSI', 'CGROUP')) + + for i, cg in enumerate(self.cglist): + if self.limit and i >= self.limit: + break + + print(' {0: 11d} {1: <16}'.`format'(cg.psi[self.psi_field], + cg.path[`len'(self.start_path):])) + + def show(self, sort=True): + if sort: + self.sort() + + if 'total' in self.psi_field: + self._show_int() + else: + self._show_float() + class LibcgroupPid(object): def __init__(self, pid, command=None): self.pid = pid diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 6a3b5dc2..344dfbfa 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -71,6 +71,7 @@ cgsnapshot_CFLAGS = $(CODE_COVERAGE_CFLAGS) $(EXTRA_CFLAGS) if ENABLE_PYTHON EXTRA_DIST = \ + cgpsilist.py \ cgpsitree.py endif diff --git a/src/tools/cgpsilist.py b/src/tools/cgpsilist.py new file mode 100755 index 00000000..78bf9e35 --- /dev/null +++ b/src/tools/cgpsilist.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-only +# +# Display a list of cgroups and their specified PSI metrics +# +# Copyright (c) 2021-2024 Oracle and/or its affiliates. +# Author: Tom Hromatka +# + +from libcgroup import LibcgroupPsiList +import argparse +import os + +def parse_args(): + parser = argparse.ArgumentParser('Libcgroup PSI List') + parser.add_argument('-C', '--cgroup', type=str, required=False, default=None, + help='Relative path to the cgroup of interest, e.g. machine.slice/foo.scope') + parser.add_argument('-c', '--controller', required=True, + help='PSI controller data to display. cpu, io, or memory') + parser.add_argument('-f', '--field', required=False, default='some-avg10', + help='Which PSI field to display, e.g. some-avg10, full-avg60, ...') + parser.add_argument('-d', '--depth', type=int, required=False, default=None, + help='Depth to recurse into the cgroup path. 0 == only this cgroup, 1 == this cgroup and its children, ...') + parser.add_argument('-t', '--threshold', type=float, required=False, default=1.0, + help='Only list cgroups whose PSI exceeds this percentage') + parser.add_argument('-l', '--limit', type=int, required=False, default=None, + help='Only display the first N cgroups. If not provided, all cgroups that match are displayed') + + args = parser.parse_args() + + return args + +def main(args): + cglist = LibcgroupPsiList(args.cgroup, controller=args.controller, depth=args.depth, + psi_field=args.field, threshold=args.threshold, limit=args.limit) + + cglist.walk() + cglist.show() + +if __name__ == '__main__': + args = parse_args() + main(args)