diff mbox series

[bitbake-devel] knotty: Integrate PSI reporting into the UI

Message ID 20251105144420.359338-1-JPEWhacker@gmail.com
State New
Headers show
Series [bitbake-devel] knotty: Integrate PSI reporting into the UI | expand

Commit Message

Joshua Watt Nov. 5, 2025, 2:44 p.m. UTC
Reworks the way that pressure stall information (PSI) is reported in the
UI. This change does a number of things to enable PSI to still be
reported, but less intrusive than a NOTE message each time it changes:
 1) The primary reporting of the PSI to the UI is now done through an
    event.
 2) The knotty UI handles the event and reports which PSI montiors are
    preventing tasks from starting with a line in the status
    footer.
 3) The old logging messages for PSI is moved to it's own logging domain
    (BitBaker.RunQueue.PSI), at verbose level. This allows filtering out
    PSI related events in structured logging configurations
 4) The default config for knotty will report the full PSI messages
    (e.g. the old logging message) to the BB_LOGCONFIG file.

This should providing relevant information in the user in the UI, but
also not flooding the console with PSI messages.

Signed-off-by: Joshua Watt <JPEWhacker@gmail.com>
---
 lib/bb/runqueue.py    | 10 +++++++++-
 lib/bb/ui/knotty.py   | 33 +++++++++++++++++++++++++++++++--
 lib/bb/ui/uihelper.py |  7 +++++++
 3 files changed, 47 insertions(+), 3 deletions(-)

Comments

Martin Jansa Nov. 5, 2025, 6:01 p.m. UTC | #1
On Wed, Nov 5, 2025 at 3:44 PM Joshua Watt via lists.openembedded.org
<JPEWhacker=gmail.com@lists.openembedded.org> wrote:
>
> Reworks the way that pressure stall information (PSI) is reported in the
> UI. This change does a number of things to enable PSI to still be
> reported, but less intrusive than a NOTE message each time it changes:
>  1) The primary reporting of the PSI to the UI is now done through an
>     event.
>  2) The knotty UI handles the event and reports which PSI montiors are
>     preventing tasks from starting with a line in the status
>     footer.

Small typo above, otherwise LGTM
diff mbox series

Patch

diff --git a/lib/bb/runqueue.py b/lib/bb/runqueue.py
index 63d4edd89..a880a0d54 100644
--- a/lib/bb/runqueue.py
+++ b/lib/bb/runqueue.py
@@ -31,6 +31,7 @@  import time
 bblogger = logging.getLogger("BitBake")
 logger = logging.getLogger("BitBake.RunQueue")
 hashequiv_logger = logging.getLogger("BitBake.RunQueue.HashEquiv")
+psi_logger = logging.getLogger("BitBake.RunQueue.PSI")
 
 __find_sha256__ = re.compile( r'(?i)(?<![a-z0-9])[a-f0-9]{64}(?![a-z0-9])' )
 
@@ -221,7 +222,8 @@  class RunQueueScheduler(object):
             pressure_state = (exceeds_cpu_pressure, exceeds_io_pressure, exceeds_memory_pressure)
             pressure_values = (round(cpu_pressure,1), self.rq.max_cpu_pressure, round(io_pressure,1), self.rq.max_io_pressure, round(memory_pressure,1), self.rq.max_memory_pressure)
             if hasattr(self, "pressure_state") and pressure_state != self.pressure_state:
-                bb.note("Pressure status changed to CPU: %s, IO: %s, Mem: %s (CPU: %s/%s, IO: %s/%s, Mem: %s/%s) - using %s/%s bitbake threads" % (pressure_state + pressure_values + (len(self.rq.runq_running.difference(self.rq.runq_complete)), self.rq.number_tasks)))
+                psi_logger.verbose("Pressure status changed to CPU: %s, IO: %s, Mem: %s (CPU: %s/%s, IO: %s/%s, Mem: %s/%s) - using %s/%s bitbake threads" % (pressure_state + pressure_values + (len(self.rq.runq_running.difference(self.rq.runq_complete)), self.rq.number_tasks)))
+                bb.event.fire(PSIEvent(pressure_state, pressure_values), self.rq.cfgData)
             self.pressure_state = pressure_state
             return (exceeds_cpu_pressure or exceeds_io_pressure or exceeds_memory_pressure)
         elif self.rq.max_loadfactor:
@@ -3307,6 +3309,12 @@  class taskUniHashUpdate(bb.event.Event):
         self.unihash = unihash
         bb.event.Event.__init__(self)
 
+class PSIEvent(bb.event.Event):
+    def __init__(self, pressure_state, pressure_values):
+        super().__init__()
+        self.pressure_state = pressure_state
+        self.pressure_values = pressure_values
+
 class runQueuePipe():
     """
     Abstraction for a pipe between a worker thread and the server
diff --git a/lib/bb/ui/knotty.py b/lib/bb/ui/knotty.py
index 00258c80f..8477e4a9c 100644
--- a/lib/bb/ui/knotty.py
+++ b/lib/bb/ui/knotty.py
@@ -115,6 +115,20 @@  def pluralise(singular, plural, qty):
     else:
         return plural % qty
 
+def get_pressure_message(pressure_state, pressure_values):
+    cpu_pressure, io_pressure, mem_pressure = pressure_state
+    pressure_strs = []
+    if cpu_pressure:
+        pressure_strs.append("CPU pressure (>%sus)" % pressure_values[1])
+    if io_pressure:
+        pressure_strs.append("I/O pressure (>%sus)" % pressure_values[3])
+    if mem_pressure:
+        pressure_strs.append("MEM pressure (>%sus)" % pressure_values[5])
+
+    if not pressure_strs:
+        pressure_strs.append("No pressure")
+
+    return "%s is limiting task startup" % ", ".join(pressure_strs)
 
 class InteractConsoleLogFilter(logging.Filter):
     def __init__(self, tf):
@@ -122,7 +136,7 @@  class InteractConsoleLogFilter(logging.Filter):
         super().__init__()
 
     def filter(self, record):
-        if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ")):
+        if record.levelno == bb.msg.BBLogFormatter.NOTE and (record.msg.startswith("Running") or record.msg.startswith("recipe ") or "limiting task startup" in record.msg):
             return False
         self.tf.clearFooter()
         return True
@@ -320,6 +334,11 @@  class TerminalFilter(object):
                 content += msg + "\n"
                 print(msg, file=self._footer_buf)
 
+                if any(self.helper.pressure_state):
+                    msg = get_pressure_message(self.helper.pressure_state, self.helper.pressure_values)
+                    content += msg + "\n"
+                    print(msg, file=self._footer_buf)
+
             if self.quiet:
                 msg = "Running tasks (%s, %s)" % (scene_tasks, cur_tasks)
             elif not len(activetasks):
@@ -433,7 +452,8 @@  _evt_list = [ "bb.runqueue.runQueueExitWait", "bb.event.LogExecTTY", "logging.Lo
               "bb.event.MultipleProviders", "bb.event.NoProvider", "bb.runqueue.sceneQueueTaskStarted",
               "bb.runqueue.runQueueTaskStarted", "bb.runqueue.runQueueTaskFailed", "bb.runqueue.sceneQueueTaskFailed",
               "bb.event.BuildBase", "bb.build.TaskStarted", "bb.build.TaskSucceeded", "bb.build.TaskFailedSilent",
-              "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished"]
+              "bb.build.TaskProgress", "bb.event.ProcessStarted", "bb.event.ProcessProgress", "bb.event.ProcessFinished",
+              "bb.runqueue.PSIEvent"]
 
 def drain_events_errorhandling(eventHandler):
     # We don't have logging setup, we do need to show any events we see before exiting
@@ -587,6 +607,10 @@  def main(server, eventHandler, params, tf = TerminalFilter):
                     "BitBake.RunQueue.HashEquiv": {
                         "level": "VERBOSE",
                         "handlers": ["BitBake.verbconsolelog"],
+                    },
+                    "BitBake.RunQueue.PSI": {
+                        "level": "VERBOSE",
+                        "handlers": ["BitBake.verbconsolelog"],
                     }
                 }
             })
@@ -904,6 +928,11 @@  def main(server, eventHandler, params, tf = TerminalFilter):
                     parseprogress.finish()
                 parseprogress = None
                 continue
+            if isinstance(event, bb.runqueue.PSIEvent):
+                if params.options.quiet > 1:
+                    continue
+                logger.info(get_pressure_message(event.pressure_state, event.pressure_values))
+                continue
 
             # ignore
             if isinstance(event, (bb.event.BuildBase,
diff --git a/lib/bb/ui/uihelper.py b/lib/bb/ui/uihelper.py
index a22363247..3e077f044 100644
--- a/lib/bb/ui/uihelper.py
+++ b/lib/bb/ui/uihelper.py
@@ -6,6 +6,7 @@ 
 #
 
 import bb.build
+import bb.runqueue
 import time
 
 class BBUIHelper:
@@ -17,6 +18,8 @@  class BBUIHelper:
         self.pidmap = {}
         self.tasknumber_current = 0
         self.tasknumber_total = 0
+        self.pressure_state = (False, False, False)
+        self.pressure_values = None
 
     def eventHandler(self, event):
         # PIDs are a bad idea as they can be reused before we process all UI events.
@@ -57,6 +60,10 @@  class BBUIHelper:
                 self.running_tasks[self.pidmap[event.pid]]['progress'] = event.progress
                 self.running_tasks[self.pidmap[event.pid]]['rate'] = event.rate
                 self.needUpdate = True
+        elif isinstance(event, bb.runqueue.PSIEvent):
+            self.pressure_state = event.pressure_state
+            self.pressure_values = event.pressure_values
+            self.needUpdate = True
         else:
             return False
         return True