new file mode 100644
@@ -0,0 +1,148 @@
+#
+# Copyright OpenEmbedded Contributors
+#
+# SPDX-License-Identifier: MIT
+#
+
+# This class captures the host's dmesg output at the start and completion of a build.
+# It captures messages with log level "notice (5)" and above. Messages that contain
+# keywords from "keywords_include_messages" list are captured regardless of the log level.
+# It excludes messages that contain keywords from "keywords_exclude_messages" list regardless
+# of the log level.
+# It stores these outputs in a variable and upon build completion, computes a diff between
+# the two logs to detect any changes that occurred during the build process. It prints the diff
+# using bb.warn().
+# This class is intended to expose failures (e.g. segmentation faults) that
+# are hidden or ignored by the host build system. However, it may also capture
+# irrelevant or unimportant messages.
+# The user needs to have privileges to run dmesg otherwise the operation is not allowed
+# and a warning is generated.
+# To enable dmesg logging, add the following to your local.conf:
+# INHERIT += "host_dmesg_logger"
+
+def append_log(msg, d):
+ """
+ Appends a log message to variable '_dmesg_log'
+ """
+ current_log = d.getVar('_dmesg_log') or ''
+ current_log += msg + '\n'
+ d.setVar('_dmesg_log', current_log)
+
+def capture_dmesg():
+ """
+ Returns the current output of dmesg command
+ """
+ import subprocess
+ import re
+
+ log_level_map = {
+ "emerg": 0,
+ "alert": 1,
+ "crit": 2,
+ "err": 3,
+ "warning": 4,
+ "notice": 5,
+ "info": 6,
+ "debug": 7
+ }
+
+ # keywords of messages which will be exclude in diff regardless of log level
+ keywords_include_messages = [
+ "over core_pipe_limit",
+ ]
+
+ # keywords of messages which will be exclude in diff regardless of log level
+ keywords_exclude_messages = [
+ "keywords_to_exclude",
+ ]
+
+ try:
+ result = subprocess.run(['dmesg', '-T', '-x'], capture_output=True, text=True, check=False)
+ if result.returncode != 0:
+ return f"Error running dmesg: {result.stderr.strip()}"
+ filtered_lines = []
+ for line in result.stdout.splitlines():
+ line_lower = line.lower()
+ match = re.search(r":(emerg|alert|crit|err|warning|notice|info|debug)", line_lower)
+ if match:
+ level_name = match.group(1).lower()
+ else:
+ level_name = None
+
+ # include messages which belong in the required level (notice -5)
+ if level_name is not None and level_name in log_level_map and log_level_map[level_name] <= 5:
+ log_level_ok = True
+ else:
+ log_level_ok = False
+
+ # include messages that contain keywords of "keywords_include_messages" list
+ include_message = False
+ for keyword in keywords_include_messages:
+ if keyword.lower() in line_lower:
+ include_message = True
+ break
+
+ # exclude messages that contain keywords of "keywords_exclude_messages" list
+ exclude_message = False
+ for keyword in keywords_exclude_messages:
+ if keyword.lower() in line_lower:
+ exclude_message = True
+ break
+
+ if (log_level_ok or include_message) and not exclude_message:
+ filtered_lines.append(line)
+
+ return "\n".join(filtered_lines) if filtered_lines else "No relevant dmesg messages."
+ except Exception as e:
+ return f"Exception running dmesg: {str(e)}"
+
+addhandler hostdmesglogger_eventhandler
+python hostdmesglogger_eventhandler() {
+ import difflib
+
+ start_marker = "=== BuildStarted dmesg ==="
+ end_marker = "=== BuildCompleted dmesg ==="
+ diff_marker = "=== dmesg diff ==="
+
+ # execute dmesg when BuildStarted event is fired
+ if isinstance(e, bb.event.BuildStarted):
+ dmesg_output = capture_dmesg()
+ if dmesg_output.startswith("Error running dmesg:") or dmesg_output.startswith("Exception running dmesg:"):
+ bb.warn(dmesg_output)
+ else:
+ append_log(start_marker, d)
+ append_log(dmesg_output, d)
+
+ # execute dmesg when BuildCompleted event is fired
+ if isinstance(e, bb.event.BuildCompleted):
+ dmesg_output = capture_dmesg()
+ if dmesg_output.startswith("Error running dmesg:") or dmesg_output.startswith("Exception running dmesg:"):
+ bb.warn(dmesg_output)
+ else:
+ append_log(end_marker, d)
+ append_log(dmesg_output, d)
+
+ content = d.getVar('_dmesg_log') or ''
+ if start_marker in content and end_marker in content:
+ start_dmesg = content.split(start_marker)[1].split(end_marker)[0]
+ end_dmesg = content.split(end_marker)[1]
+
+ start_lines = start_dmesg.strip().splitlines()
+ end_lines = end_dmesg.strip().splitlines()
+
+ # generating diff between BuildStarted and BuildCompleted dmesg outputs
+ diff = list(difflib.unified_diff(
+ start_lines, end_lines,
+ fromfile = 'dmesg_start',
+ tofile = 'dmesg_end',
+ lineterm = ''
+ ))
+
+ append_log(diff_marker, d)
+ if diff:
+ for line in diff:
+ bb.warn(line)
+ else:
+ bb.warn("Could not find both dmesg sections for diff.")
+}
+hostdmesglogger_eventhandler[eventmask] = "bb.event.BuildStarted bb.event.BuildCompleted"
This class is used to capture the host dmesg output when BuildStarted and BuildCompleted. Filters are applied to these messages.Then computes a diff and prints the result. Fixes [YOCTO #15557] CC: Yoann Congal <yoann.congal@smile.fr> CC: Randy MacLeod <randy.macleod@windriver.com> CC: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Christos Gavros <gavrosc@yahoo.com> --- v2->v3 * remove message 'No differences in dmesg output' * show message's log level * capture messages from notice level and above * include messages which contain keywords from list keywords_include_messages * exclude messages which contain keywords from list keywords_exclude_messages * the description is extended --- meta/classes-global/host_dmesg_logger.bbclass | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 meta/classes-global/host_dmesg_logger.bbclass