[PATCH 3/5] tuna: Eliminate global ps and irqs variables

John Kacur <[email protected]> Thu, 11 Jun 2026 16:38:30 -0400
Newsgroups org.kernel.vger.linux-rt-users
Message-ID <[email protected]>
Remove the remaining global variables (ps and irqs) and pass them as
function parameters instead. This completes the refactoring to eliminate
side effects and global state from the codebase.

The ps and irqs globals were used as a cache/singleton pattern where they
would be lazily initialized on first use and reused across function calls.
However, most commands only call these functions once, so the caching
benefit was minimal while the global state made the code harder to
understand and test.

Changes:
- Removed global variable declarations for ps and irqs (lines 78-82)
- Removed "FIXME: ETOOMANYGLOBALS" comment - issue is now resolved
- Updated thread_help() to accept ps as parameter
- Removed global ps declaration from main(), made it local variable
- Added irqs parameter to ps_show_thread() signature
- Added irqs parameter to ps_show() signature
- Updated do_ps() to create irqs locally and pass to ps_show()
- Updated show_irqs() to create irqs locally instead of using global
- Updated what_is command to create ps if needed and pass to thread_help()
- Updated all call sites to pass parameters correctly

Benefits:
- No global variables - all state is now local or explicitly passed
- Functions are pure with clear dependencies visible in signatures
- Code is more testable and easier to understand
- Explicit control flow with no hidden state

All 6 existing unit tests pass.

Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: John Kacur <[email protected]>
---
 tuna-cmd.py | 37 +++++++++++++------------------------
 1 file changed, 13 insertions(+), 24 deletions(-)

diff --git a/tuna-cmd.py b/tuna-cmd.py
index 4da6c6ea6ce5..d83e57732360 100755
--- a/tuna-cmd.py
+++ b/tuna-cmd.py
@@ -75,11 +75,6 @@ try:
 except ImportError:
     have_inet_diag = False
 
-# FIXME: ETOOMANYGLOBALS, we need a class!
-
-ps = None
-irqs = None
-
 class HelpMessageParser(argparse.ArgumentParser):
     def error(self, message):
         sys.stderr.write(f'error: {message}\n')
@@ -250,11 +245,7 @@ def gen_parser():
     return parser
 
 
-def thread_help(tid):
-    global ps
-    if not ps:
-        ps = procfs.pidstats()
-
+def thread_help(tid, ps):
     if tid not in ps:
         print(f"tuna: thread {tid} doesn't exist!")
         return
@@ -329,8 +320,7 @@ def format_affinity(affinity):
     return ",".join(str(hex(a)) for a in procfs.hexbitmask(affinity, utils.get_nr_cpus()))
 
 def ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info, sock_inodes,
-                   sock_inode_re, cgroups, columns=None, compact=True):
-    global irqs
+                   sock_inode_re, cgroups, irqs, columns=None, compact=True):
     try:
         affinity = format_affinity(os.sched_getaffinity(pid))
     except OSError as e:
@@ -345,8 +335,6 @@ def ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info, sock_inodes,
     users = ""
     if tuna.is_irq_thread(cmd):
         try:
-            if not irqs:
-                irqs = procfs.interrupts()
             users = irqs[tuna.irq_thread_number(cmd)]["users"]
             for u in users:
                 if u in utils.get_nics():
@@ -388,12 +376,12 @@ def ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info, sock_inodes,
         for tid in list(ps[pid]["threads"].keys()):
             ps_show_thread(tid, False, ps[pid]["threads"],
                            has_ctxt_switch_info,
-                           sock_inodes, sock_inode_re, cgroups, columns, compact)
+                           sock_inodes, sock_inode_re, cgroups, irqs, columns, compact)
 
 
 def ps_show(ps, affect_children, thread_list, cpu_list,
             irq_list_numbers, show_uthreads, show_kthreads,
-            has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups, compact, match_requested):
+            has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups, compact, match_requested, irqs):
 
     ps_list = []
     for pid in list(ps.keys()):
@@ -443,7 +431,7 @@ def ps_show(ps, affect_children, thread_list, cpu_list,
 
     for pid in ps_list:
         ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info,
-                       sock_inodes, sock_inode_re, cgroups, columns, compact)
+                       sock_inodes, sock_inode_re, cgroups, irqs, columns, compact)
 
 
 def load_socktype(socktype, inodes):
@@ -469,6 +457,8 @@ def do_ps(thread_list, cpu_list, irq_list, show_uthreads, show_kthreads,
     if affect_children:
         ps.reload_threads()
 
+    irqs = procfs.interrupts()
+
     sock_inodes = None
     sock_inode_re = None
     if show_sockets:
@@ -481,7 +471,7 @@ def do_ps(thread_list, cpu_list, irq_list, show_uthreads, show_kthreads,
             ps_show_header(has_ctxt_switch_info, cgroups)
         ps_show(ps, affect_children, thread_list,
                 cpu_list, irq_list, show_uthreads, show_kthreads,
-                has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups, compact, match_requested)
+                has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups, compact, match_requested, irqs)
     except IOError:
         # 'tuna -P | head' for instance
         pass
@@ -505,9 +495,7 @@ def find_drivers_by_users(users):
 
 
 def show_irqs(irq_list, cpu_list, match_requested):
-    global irqs
-    if not irqs:
-        irqs = procfs.interrupts()
+    irqs = procfs.interrupts()
 
     if sys.stdout.isatty():
         print("%4s %-16s %8s" % ("#", _("users"), _("affinity"),))
@@ -655,8 +643,6 @@ def nohz_full_to_cpu():
 
 
 def main():
-    global ps
-
     i18n_init()
     parser = gen_parser()
     # Set all necessary defaults for gui subparser if no arguments provided
@@ -681,6 +667,7 @@ def main():
 
     # Convert string arguments to lists after parsing
     match_requested = False
+    ps = None
 
     # Convert thread_list from string to list
     if 'thread_list' in vars(args) and args.thread_list:
@@ -772,8 +759,10 @@ def main():
         save(args.cpu_list, args.thread_list, args.filename)
 
     elif args.command in ['W', 'what_is']:
+        if not ps:
+            ps = procfs.pidstats()
         for tid in args.thread_list:
-            thread_help(tid)
+            thread_help(tid, ps)
 
     elif args.command in ['g', 'gui']:
         # Don't try to start the gui if no display is available
-- 
2.54.0