From d19cd9ed717758f7f8ac205e9427ad70c865115a Mon Sep 17 00:00:00 2001 From: Tom Hromatka Date: Wed, 24 Apr 2024 20:28:35 -0600 Subject: [PATCH] python: tools: Add tool for displaying a list of realtime data Add a tool for displaying the realtime allocations for a cgroup hierarchy. ADD EXAMPLES HERE Signed-off-by: Tom Hromatka --- src/python/libcgroup.pyx.m4 | 75 +++++++++++++++++++++++++++++++++++++ src/tools/Makefile.am | 1 + src/tools/cgrealtimelist.py | 35 +++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100755 src/tools/cgrealtimelist.py diff --git a/src/python/libcgroup.pyx.m4 b/src/python/libcgroup.pyx.m4 index 4af57076..4dca26b4 100644 --- a/src/python/libcgroup.pyx.m4 +++ b/src/python/libcgroup.pyx.m4 @@ -1236,6 +1236,81 @@ class LibcgroupPsiList(LibcgroupTree): else: self._show_float() +class LibcgroupRealtimeList(LibcgroupTree): + def __init__(self, name, depth=None, limit=None): + super().__init__(name, version=Version.CGROUP_V1, controller='cpu', + depth=depth) + + self.rootcg.get_realtime() + self.cglist = list() + self.limit = limit + + def walk_action(self, cg): + cg.get_realtime() + + if cg.settings['cpu.rt_runtime_us']: + self.cglist.append(cg) + + def sort(self): + self.cglist = sorted(self.cglist, reverse=True, + key=lambda cg: cg.realtime_pct) + + def show(self, sort=True, verbose=True): + total_pct = 0.0 + + if sort: + self.sort() + + print('{0: >7} {1: >7} {2: >7} {3: <20}'.`format'('RUNTIME', 'PERIOD', 'PERCENT', 'CGROUP')) + + for i, cg in enumerate(self.cglist): + # Only add the realtime of the direct children of the root cgroup. + # Grandchildren realtime allocations are accounted for in their + # parents' realtime allocations. + if cg.path[`len'(self.rootcg.path):].count('/') == 1: + total_pct += cg.realtime_pct + + if self.limit and i >= self.limit: + continue + + print('{0: >7} {1: >7} {2: >5.2f}% {3: <20}'.`format'( + cg.settings['cpu.rt_runtime_us'], + cg.settings['cpu.rt_period_us'], + cg.realtime_pct, + cg.path[`len'(self.start_path):])) + + if verbose: + print('\n{0:,} / {1:,} microseconds ({2: >5.2f}%) of the CPU ' + 'cycles have been allocated to realtime in the root ' + 'cgroup.'.`format'( + self.rootcg.settings['cpu.rt_runtime_us'], + self.rootcg.settings['cpu.rt_period_us'], + self.rootcg.realtime_pct)) + + if self.mount == self.start_path: + alloc_path = 'the root cgroup' + tmpcg = self.rootcg + else: + alloc_path = self.rootcg.path[`len'(self.mount):] + tmpcg = self.rootcg + + percent_consumed = 100 * total_pct / tmpcg.realtime_pct + print('\n{0:,} of the {1:,} realtime cycles ({2: >5.2f}%) for {3:} ' + 'have been assigned to children cgroups'.`format'( + int(percent_consumed * tmpcg.settings['cpu.rt_runtime_us'] / 100), + tmpcg.settings['cpu.rt_runtime_us'], + percent_consumed, + alloc_path)) + + print('\n{0:,} (cpu.rt_runtime_us) / {1:,} (cpu.rt_period_us) ' + 'microseconds can still be assigned to a child of {2:}'.`format'( + max(int((tmpcg.realtime_pct - total_pct) * 10000), 0), + 1000000, + alloc_path)) + + print('\nNote that the remaining cpu.rt_runtime_us is estimated ' + 'and could be off by 1 or 2 in either direction.\n') + 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 b9a09e1c..4c971c78 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -73,6 +73,7 @@ if ENABLE_PYTHON EXTRA_DIST = \ cgpsilist.py \ cgpsitree.py \ + cgrealtimelist.py \ cgrealtimetree.py endif diff --git a/src/tools/cgrealtimelist.py b/src/tools/cgrealtimelist.py new file mode 100755 index 00000000..0589d8d4 --- /dev/null +++ b/src/tools/cgrealtimelist.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-only +# +# Display a list of cgroups and their realtime usage +# +# Copyright (c) 2021-2024 Oracle and/or its affiliates. +# Author: Tom Hromatka +# + +from libcgroup import LibcgroupRealtimeList +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('-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('-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 = LibcgroupRealtimeList(args.cgroup, depth=args.depth, limit=args.limit) + + cglist.walk() + cglist.show() + +if __name__ == '__main__': + args = parse_args() + main(args)