Igt-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Sobin Thomas <sobin.thomas@intel.com>
To: igt-dev@lists.freedesktop.org
Cc: zbigniew.kempczynski@intel.com, Sobin Thomas <sobin.thomas@intel.com>
Subject: [PATCH i-g-t 1/2] igt/runner: Refactor dmesg record buffering
Date: Mon,  5 Oct 2026 11:14:41 +0000	[thread overview]
Message-ID: <20261005111442.2755785-2-sobin.thomas@intel.com> (raw)
In-Reply-To: <20261005111442.2755785-1-sobin.thomas@intel.com>

Modify the runner result gen module to centralize dmesg record emission for
parent and dynamic-subtest buffers. Adding this prepares the parser
for duplicate rate limitting for the dmesg logs.

Signed-off-by: Sobin Thomas <sobin.thomas@intel.com>
---
 runner/resultgen.c    | 205 ++++++++++++++++++++++++++++++++----------
 runner/runner_tests.c |   3 +
 runner/settings.c     |  30 +++++++
 runner/settings.h     |   1 +
 4 files changed, 191 insertions(+), 48 deletions(-)

diff --git a/runner/resultgen.c b/runner/resultgen.c
index f5a91f542..5945bee29 100644
--- a/runner/resultgen.c
+++ b/runner/resultgen.c
@@ -1004,6 +1004,103 @@ static void add_empty_dmesgs_where_missing(struct json_t *tests,
 
 }
 
+/* Parent and dynamic-subtest output are fed from the same record stream. */
+struct dmesg_buffers {
+	char *text, *warnings;
+	size_t textlen, warningslen;
+};
+
+static void add_dmesg_buffers(struct json_t *obj, const struct dmesg_buffers *buffers)
+{
+	add_dmesg(obj, buffers->text, buffers->textlen,
+		  buffers->warnings, buffers->warningslen);
+}
+
+struct dmesg_repeat_state {
+	char *message, *last_formatted;
+	unsigned int flags;
+	size_t count;
+	bool warning, dynamic_warning;
+};
+
+static void emit_dmesg_record(struct dmesg_buffers *parent,
+			      struct dmesg_buffers *dynamic,
+			      const char *text, bool warning, bool dynamic_warning)
+{
+	append_line(&parent->text, &parent->textlen, text);
+	append_line(&dynamic->text, &dynamic->textlen, text);
+	if (warning) {
+		append_line(&parent->warnings, &parent->warningslen, text);
+		if (dynamic_warning)
+			append_line(&dynamic->warnings, &dynamic->warningslen, text);
+	}
+}
+
+static bool flush_dmesg_repeats(struct dmesg_repeat_state *state, int limit,
+				struct dmesg_buffers *parent,
+				struct dmesg_buffers *dynamic)
+{
+	if (limit && state->count > (size_t)limit) {
+		char *summary;
+		size_t len = strlen(state->last_formatted);
+		const char *newline = "";
+
+		if (len && state->last_formatted[len - 1] == '\n') {
+			state->last_formatted[len - 1] = '\0';
+			newline = "\n";
+		}
+		if (asprintf(&summary, "...\n%s <occurs %zu>%s",
+			     state->last_formatted, state->count - limit, newline) < 0)
+			return false;
+		emit_dmesg_record(parent, dynamic, summary,
+				  state->warning, state->dynamic_warning);
+		free(summary);
+	}
+	free(state->message);
+	free(state->last_formatted);
+	memset(state, 0, sizeof(*state));
+	return true;
+}
+
+static bool append_dmesg_record(struct dmesg_repeat_state *state, int limit,
+				 struct dmesg_buffers *parent,
+				 struct dmesg_buffers *dynamic,
+				 const char *message, const char *formatted,
+				 unsigned int flags, bool warning,
+				 bool dynamic_warning, bool passthrough)
+{
+	char *last;
+
+	if (state->message &&
+	    (passthrough || state->flags != flags || state->warning != warning ||
+	     state->dynamic_warning != dynamic_warning ||
+	     strcmp(state->message, message))) {
+		if (!flush_dmesg_repeats(state, limit, parent, dynamic))
+			return false;
+	}
+	if (!limit || passthrough) {
+		emit_dmesg_record(parent, dynamic, formatted, warning, dynamic_warning);
+		return true;
+	}
+	if (!state->message) {
+		state->message = strdup(message);
+		if (!state->message)
+			return false;
+		state->flags = flags;
+		state->warning = warning;
+		state->dynamic_warning = dynamic_warning;
+	}
+	last = strdup(formatted);
+	if (!last)
+		return false;
+	free(state->last_formatted);
+	state->last_formatted = last;
+	state->count++;
+	if (state->count <= (size_t)limit)
+		emit_dmesg_record(parent, dynamic, formatted, warning, dynamic_warning);
+	return true;
+}
+
 static bool fill_from_dmesg(int fd,
 			    struct settings *settings,
 			    char *binary,
@@ -1011,11 +1108,10 @@ static bool fill_from_dmesg(int fd,
 			    struct json_t *tests)
 {
 	char *line = NULL;
-	char *warnings = NULL, *dynamic_warnings = NULL;
-	char *dmesg = NULL, *dynamic_dmesg = NULL;
+	struct dmesg_buffers parent = {0}, dynamic = {0};
+	struct dmesg_repeat_state repeat = {0};
+	bool success = false;
 	size_t linelen = 0;
-	size_t warningslen = 0, dynamic_warnings_len = 0;
-	size_t dmesglen = 0, dynamic_dmesg_len = 0;
 	struct json_t *current_test = NULL;
 	struct json_t *current_dynamic_test = NULL;
 	FILE *f = fdopen(fd, "r");
@@ -1037,33 +1133,47 @@ static bool fill_from_dmesg(int fd,
 	re_ignore = NULL;
 	while (getline(&line, &linelen, f) > 0) {
 		char *formatted;
+		bool warning, control;
 		unsigned flags;
 		unsigned long long ts_usec;
 		char continuation;
 		char *message, *subtest, *dynamic_subtest, *ignore;
 
-		if (!parse_dmesg_line(line, &flags, &ts_usec, &continuation, &message))
+		if (!parse_dmesg_line(line, &flags, &ts_usec, &continuation, &message)) {
+			if (line[0] != ' ' &&
+			    !flush_dmesg_repeats(&repeat, settings->dmesg_rate_limit,
+						 &parent, &dynamic))
+				goto out;
 			continue;
+		}
+
+		control = strstr(message, STARTING_SUBTEST_DMESG) ||
+			strstr(message, STARTING_DYNAMIC_SUBTEST_DMESG) ||
+			strstr(message, IGT_ADD_IGNORED_REGEX_DMESG);
+		/* Flush into the old destinations before changing test/regex state. */
+		if (control && !flush_dmesg_repeats(&repeat, settings->dmesg_rate_limit,
+						   &parent, &dynamic))
+			goto out;
 
 		generate_formatted_dmesg_line(message, flags, ts_usec, &formatted);
 
 		if ((subtest = strstr(message, STARTING_SUBTEST_DMESG)) != NULL) {
 			if (current_test != NULL) {
 				/* Done with the previous subtest, file up */
-				add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
+				add_dmesg_buffers(current_test, &parent);
 
-				free(dmesg);
-				free(warnings);
-				dmesg = warnings = NULL;
-				dmesglen = warningslen = 0;
+				free(parent.text);
+				free(parent.warnings);
+				parent.text = parent.warnings = NULL;
+				parent.textlen = parent.warningslen = 0;
 
 				if (current_dynamic_test != NULL)
-					add_dmesg(current_dynamic_test, dynamic_dmesg, dynamic_dmesg_len, dynamic_warnings, dynamic_warnings_len);
+					add_dmesg_buffers(current_dynamic_test, &dynamic);
 
-				free(dynamic_dmesg);
-				free(dynamic_warnings);
-				dynamic_dmesg = dynamic_warnings = NULL;
-				dynamic_dmesg_len = dynamic_warnings_len = 0;
+				free(dynamic.text);
+				free(dynamic.warnings);
+				dynamic.text = dynamic.warnings = NULL;
+				dynamic.textlen = dynamic.warningslen = 0;
 				current_dynamic_test = NULL;
 			}
 
@@ -1077,12 +1187,12 @@ static bool fill_from_dmesg(int fd,
 		    (dynamic_subtest = strstr(message, STARTING_DYNAMIC_SUBTEST_DMESG)) != NULL) {
 			if (current_dynamic_test != NULL) {
 				/* Done with the previous dynamic subtest, file up */
-				add_dmesg(current_dynamic_test, dynamic_dmesg, dynamic_dmesg_len, dynamic_warnings, dynamic_warnings_len);
+				add_dmesg_buffers(current_dynamic_test, &dynamic);
 
-				free(dynamic_dmesg);
-				free(dynamic_warnings);
-				dynamic_dmesg = dynamic_warnings = NULL;
-				dynamic_dmesg_len = dynamic_warnings_len = 0;
+				free(dynamic.text);
+				free(dynamic.warnings);
+				dynamic.text = dynamic.warnings = NULL;
+				dynamic.textlen = dynamic.warningslen = 0;
 			}
 
 			dynamic_subtest += strlen(STARTING_DYNAMIC_SUBTEST_DMESG);
@@ -1094,33 +1204,27 @@ static bool fill_from_dmesg(int fd,
 		if ((ignore = strstr(message, IGT_ADD_IGNORED_REGEX_DMESG)) != NULL)
 			add_ignored_regex(&re_ignore, ignore + strlen(IGT_ADD_IGNORED_REGEX_DMESG));
 
-		if (settings->piglit_style_dmesg) {
-			if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' &&
-			    g_regex_match(re, message, 0, NULL) &&
-			    not_ignored(re_ignore, message)) {
-				append_line(&warnings, &warningslen, formatted);
-				if (current_test != NULL)
-					append_line(&dynamic_warnings, &dynamic_warnings_len, formatted);
-			}
-		} else {
-			if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' &&
-			    !g_regex_match(re, message, 0, NULL) &&
-			    not_ignored(re_ignore, message)) {
-				append_line(&warnings, &warningslen, formatted);
-				if (current_test != NULL)
-					append_line(&dynamic_warnings, &dynamic_warnings_len, formatted);
-			}
+		warning = (flags & 0x07) <= settings->dmesg_warn_level &&
+			continuation != 'c' &&
+			(!!g_regex_match(re, message, 0, NULL) == settings->piglit_style_dmesg) &&
+			not_ignored(re_ignore, message);
+		if (!append_dmesg_record(&repeat, settings->dmesg_rate_limit,
+					 &parent, &dynamic, message, formatted, flags,
+					 warning, current_test != NULL,
+					 control || continuation == 'c')) {
+			free(formatted);
+			goto out;
 		}
-		append_line(&dmesg, &dmesglen, formatted);
-		append_line(&dynamic_dmesg, &dynamic_dmesg_len, formatted);
 		free(formatted);
 	}
-	free(line);
+	if (ferror(f) || !flush_dmesg_repeats(&repeat, settings->dmesg_rate_limit,
+					    &parent, &dynamic))
+		goto out;
 
 	if (current_test != NULL) {
-		add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
+		add_dmesg_buffers(current_test, &parent);
 		if (current_dynamic_test != NULL) {
-			add_dmesg(current_dynamic_test, dynamic_dmesg, dynamic_dmesg_len, dynamic_warnings, dynamic_warnings_len);
+			add_dmesg_buffers(current_dynamic_test, &dynamic);
 		}
 	} else {
 		/*
@@ -1136,26 +1240,31 @@ static bool fill_from_dmesg(int fd,
 			 * there are would have skip as their result
 			 * anyway.
 			 */
-			add_dmesg(current_test, dmesg, dmesglen, NULL, 0);
+			add_dmesg(current_test, parent.text, parent.textlen, NULL, 0);
 		}
 
 		if (subtests->size == 0) {
 			generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name));
 			current_test = get_or_create_json_object(tests, piglit_name);
-			add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
+			add_dmesg_buffers(current_test, &parent);
 		}
 	}
 
 	add_empty_dmesgs_where_missing(tests, binary, subtests);
 
-	free(dmesg);
-	free(dynamic_dmesg);
-	free(warnings);
-	free(dynamic_warnings);
+	success = true;
+out:
+	free(line);
+	free(repeat.message);
+	free(repeat.last_formatted);
+	free(parent.text);
+	free(dynamic.text);
+	free(parent.warnings);
+	free(dynamic.warnings);
 	clean_regex(&re_ignore);
 	g_regex_unref(re);
 	fclose(f);
-	return true;
+	return success;
 }
 
 static struct json_t *tmp_tests;
diff --git a/runner/runner_tests.c b/runner/runner_tests.c
index 798fee8ef..fb39faec6 100644
--- a/runner/runner_tests.c
+++ b/runner/runner_tests.c
@@ -205,6 +205,7 @@ static void assert_settings_equal(struct settings *one, struct settings *two)
 	igt_assert_eqstr(one->code_coverage_script, two->code_coverage_script);
 	igt_assert_eq(one->piglit_style_dmesg, two->piglit_style_dmesg);
 	igt_assert_eq(one->dmesg_warn_level, two->dmesg_warn_level);
+	igt_assert_eq(one->dmesg_rate_limit, two->dmesg_rate_limit);
 	igt_assert_eq(one->prune_mode, two->prune_mode);
 
 	igt_assert_eq(igt_vec_length(&one->hook_strs), igt_vec_length(&two->hook_strs));
@@ -322,6 +323,7 @@ int igt_main()
 
 		igt_assert(!settings->piglit_style_dmesg);
 		igt_assert_eq(settings->dmesg_warn_level, 4);
+		igt_assert_eq(settings->dmesg_rate_limit, 0);
 	}
 
 	igt_subtest_group() {
@@ -997,6 +999,7 @@ int igt_main()
 					       "--overall-timeout", "360",
 					       "--use-watchdog",
 					       "--piglit-style-dmesg",
+					       "--rate-limit=3",
 					       "--prune-mode=keep-all",
 					       "--hook", "echo hello",
 					       "--hook", "echo hello\necho newline",
diff --git a/runner/settings.c b/runner/settings.c
index eb67f651a..e6f8af358 100644
--- a/runner/settings.c
+++ b/runner/settings.c
@@ -26,6 +26,7 @@ enum {
 	OPT_SAVE_RESULTS_AFTER_EACH_TEST,
 	OPT_PIGLIT_DMESG,
 	OPT_DMESG_WARN_LEVEL,
+	OPT_DMESG_RATE_LIMIT,
 	OPT_OVERALL_TIMEOUT,
 	OPT_PER_TEST_TIMEOUT,
 	OPT_ALLOW_NON_ROOT,
@@ -278,6 +279,8 @@ static const char *usage_str =
 	"  --use-watchdog        Use hardware watchdog for lethal enforcement of the\n"
 	"                        above timeout. Killing the test process is still\n"
 	"                        attempted at timeout trigger.\n"
+	"  --rate-limit N        Keep the first N consecutive identical dmesg records,\n"
+	"                        then summarize repeats. 0 disables limiting (default).\n"
 	"  --dmesg-warn-level <level>\n"
 	"                        Messages with log level equal or lower (more serious)\n"
 	"                        to the given one will override the test result to\n"
@@ -677,6 +680,23 @@ void clear_settings(struct settings *settings)
 	init_settings(settings);
 }
 
+static bool parse_dmesg_rate_limit(const char *value, int *limit)
+{
+	char *end;
+	long parsed;
+
+	if (!value[0] || strspn(value, "0123456789") != strlen(value))
+		return false;
+
+	errno = 0;
+	parsed = strtol(value, &end, 10);
+	if (errno == ERANGE || *end || parsed > INT_MAX)
+		return false;
+
+	*limit = parsed;
+	return true;
+}
+
 bool parse_options(int argc, char **argv,
 		   struct settings *settings)
 {
@@ -714,6 +734,7 @@ bool parse_options(int argc, char **argv,
 		{"overall-timeout", required_argument, NULL, OPT_OVERALL_TIMEOUT},
 		{"use-watchdog", no_argument, NULL, OPT_WATCHDOG},
 		{"piglit-style-dmesg", no_argument, NULL, OPT_PIGLIT_DMESG},
+		{"rate-limit", required_argument, NULL, OPT_DMESG_RATE_LIMIT},
 		{"dmesg-warn-level", required_argument, NULL, OPT_DMESG_WARN_LEVEL},
 		{"prune-mode", required_argument, NULL, OPT_PRUNE_MODE},
 		{"blacklist", required_argument, NULL, OPT_BLACKLIST},
@@ -842,6 +863,12 @@ bool parse_options(int argc, char **argv,
 			if (settings->dmesg_warn_level < 0)
 				settings->dmesg_warn_level = 5; /* KERN_NOTICE */
 			break;
+		case OPT_DMESG_RATE_LIMIT:
+			if (!parse_dmesg_rate_limit(optarg, &settings->dmesg_rate_limit)) {
+				usage(stderr, "Invalid --rate-limit: %s", optarg);
+				goto error;
+			}
+			break;
 		case OPT_DMESG_WARN_LEVEL:
 			settings->dmesg_warn_level = atoi(optarg);
 			break;
@@ -1255,6 +1282,7 @@ bool serialize_settings(struct settings *settings)
 	SERIALIZE_INT(f, settings, use_watchdog);
 	SERIALIZE_INT(f, settings, piglit_style_dmesg);
 	SERIALIZE_INT(f, settings, dmesg_warn_level);
+	SERIALIZE_INT(f, settings, dmesg_rate_limit);
 	SERIALIZE_INT(f, settings, prune_mode);
 	SERIALIZE_STR(f, settings, test_root);
 	SERIALIZE_STR(f, settings, results_path);
@@ -1352,6 +1380,7 @@ bool read_settings_from_file(struct settings *settings, FILE *f)
 	char *name = NULL, *val = NULL;
 
 	settings->dmesg_warn_level = -1;
+	settings->dmesg_rate_limit = 0;
 
 	while (fscanf(f, "%ms : %m[^\n]", &name, &val) == 2) {
 		PARSE_INT(settings, name, val, abort_mask);
@@ -1374,6 +1403,7 @@ bool read_settings_from_file(struct settings *settings, FILE *f)
 		PARSE_INT(settings, name, val, use_watchdog);
 		PARSE_INT(settings, name, val, piglit_style_dmesg);
 		PARSE_INT(settings, name, val, dmesg_warn_level);
+		PARSE_INT(settings, name, val, dmesg_rate_limit);
 		PARSE_INT(settings, name, val, prune_mode);
 		PARSE_STR(settings, name, val, test_root);
 		PARSE_STR(settings, name, val, results_path);
diff --git a/runner/settings.h b/runner/settings.h
index 4d1c17ffb..5d2bbf3e4 100644
--- a/runner/settings.h
+++ b/runner/settings.h
@@ -77,6 +77,7 @@ struct settings {
 	char *results_path;
 	bool piglit_style_dmesg;
 	int dmesg_warn_level;
+	int dmesg_rate_limit; /* 0 disables duplicate suppression */
 	int prune_mode;
 	bool list_all;
 	char *code_coverage_script;
-- 
2.43.0


  reply	other threads:[~2026-10-05 11:16 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-05 11:14 [PATCH i-g-t 0/2] Limit repeated dmesg records in results Sobin Thomas
2026-10-05 11:14 ` Sobin Thomas [this message]
2026-10-05 11:14 ` [PATCH i-g-t 2/2] igt/runner: Add the rate limit feature for dmesg logs for runner Sobin Thomas
2026-10-05 11:57 ` ✓ i915.CI.BAT: success for Limit repeated dmesg records in results Patchwork
2026-10-05 12:06 ` ✓ Xe.CI.BAT: " Patchwork
2026-10-05 14:54 ` ✗ i915.CI.Full: failure " Patchwork
2026-10-05 17:58 ` ✗ Xe.CI.FULL: " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261005111442.2755785-2-sobin.thomas@intel.com \
    --to=sobin.thomas@intel.com \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=zbigniew.kempczynski@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox