From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8F6E3812FE for ; Sun, 31 May 2026 08:23:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780215788; cv=none; b=hclIQgA5Ir+GhXrgBtusPsJP+m2wFndR/jsqUHoJ7XKYCBV/c/OaQ10ZdadjEvVm0Jhm5rddn0s7eQU8jt9xkFx4WvCDIAv8Gq294HiftdMPqolM5Ydu4BDBda0NeV18Kwuj3uAU0mawkWPb1haX6Za9K7abBDfYItLxlYpODVk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780215788; c=relaxed/simple; bh=oMIaOA1Afe+1V0MX+e2eHUTjWRN4Vz6JbVTJc3uJF+g=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=R9HHMCb/xTx3ylaDcbbvRhY6IIyy+mMvw4aMkLaDHHqfd2SvHm5FX2Ee+U49nSJipTRhfIUK/KVTV1ZqQ266kQCN4NcZ6/lBjaag4ogDGGhR6IxTQ0pUaslTXTYlKODEVbb7Fx8KpwHq5U4WeUMQ5N1Mj/1fo3pd2kVt/2uQGIE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=Te3Vc85/; arc=none smtp.client-ip=74.125.82.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="Te3Vc85/" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-135916eefa0so9829084c88.1 for ; Sun, 31 May 2026 01:23:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1780215783; x=1780820583; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=4RLWv61y0h4B1VafULYkuEcaVr7I3ppSYA0mqa8j5k4=; b=Te3Vc85/g2yfmBr+8aw8Dn8akt58xzcngyVUdyYmNFBdVwBEQ8+PLqQMU7Yqgrf/64 QuI8RsaGPxUH6h9oSmekDpgJTk9s5glD6xow5A3aajnWng2lY4+ethYINFjOXr27PdyV reQ0aGYojUEKgIxZW4jz58ndEeeZWSKMJXNsdCpz0eTcYZzpykaUuAIiXZfZLi4gB5yh rpP4LGbG1fDZb1fPL+aukwHi65rVmTmO20hSZF96z9baRVYmsslFMYwJbiRDyQhNvYzC Ioi8F4bVQA/BU8mMfdm7Kzpw9Fp8B0jd0gTm0/DAIGBXnyUdpWAJboY8it0B71YWtYON EKMQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780215783; x=1780820583; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=4RLWv61y0h4B1VafULYkuEcaVr7I3ppSYA0mqa8j5k4=; b=dx9kCFpu5y5VurT+1vuJ3gIoyia5E4KBCXwcrZJPBXDDyD3wGcVR7j3NESBJr54wme QGtQZFBBnDG8YhE93kzcuXjj6nSH7UI81lWhyfA36McLZcaBYrOiwsj0N+4N1YNiyCNI lwEXb7nI1CmTUXnxxqjRpWG4HY1z2mlY2TpvH3OjYSe+l7ocZoijGvoe4ap/UfyIdV73 /9nuTP5ZyhnbBLN9vrs9+6jR6rXMlFjoPt3OrM+IXp/f3oCTss5t8z6jPr0QgXw3nykM Subof558aB6+gX7vQIT71XVeRusruPIAoiTkaykUJXJ0RipjFBntMNcJB9ZLuaR9VzAs 8lwQ== X-Forwarded-Encrypted: i=1; AFNElJ8tVbE7NXYGE9pG4lFPIzXNJd/LiIWgckBqoWpolE3mwxMpbo6TVkYbugFAgXfKg04JCPhH3D/F4LYyqXU=@vger.kernel.org X-Gm-Message-State: AOJu0YyjESsLnLW+GMfTb0G6+MbkAlYeA0n9zgoxZyBR6Fh5J3u07dYL BmkSSaVES4O0CPMyd8110hyOAgI0+WQXKKWRd2XYgnQfeb/K8R92NTMSXU3pUJ1w76yPQBxPf8y L+PcAW/OeCQ== X-Received: from dlnn31.prod.google.com ([2002:a05:7022:619f:b0:135:5a9c:4291]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:4581:b0:135:51d0:6df6 with SMTP id a92af1059eb24-137d4241f27mr2861645c88.28.1780215783013; Sun, 31 May 2026 01:23:03 -0700 (PDT) Date: Sun, 31 May 2026 01:22:28 -0700 In-Reply-To: <20260531082235.940833-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260531063736.871777-1-irogers@google.com> <20260531082235.940833-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.823.g6e5bcc1fc9-goog Message-ID: <20260531082235.940833-9-irogers@google.com> Subject: [PATCH v4 08/15] perf test: Show snippet failure output for verbose=1 From: Ian Rogers To: irogers@google.com, acme@kernel.org, adrian.hunter@intel.com, namhyung@kernel.org Cc: alexander.shishkin@linux.intel.com, james.clark@linaro.org, jolsa@kernel.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org Content-Type: text/plain; charset="UTF-8" Currently, when running tests in verbose mode (-v), if a test case fails, the entire raw standard error buffer is dumped to stderr via fprintf(stderr, "%s", child->err_output.buf). For tests that generate massive amounts of debugging or logging output before dying, this results in multi-page terminal dumps where highly critical diagnostic keywords (error, fail, segv) are easily lost. Implement a smart, bounded snippet string processor to improve failure triaging: 1. Introduce a configurable quota limit static unsigned int failure_snippet_lines = 10; accessible via a new command-line option --failure-snippet-lines . 2. Parse the raw error buffer dynamically into lines and run a three-pass extraction algorithm: - Pass 0: Always select the very first line of the log as an initial outline marker. - Pass 1: Scan forward from the top of the log to pick up to N lines that contain case-insensitive failure keywords (error, fail, segv, abort) to isolate the root cause. Automatically pull in the immediate subsequent line as highly-prioritized context. Allow adjacent matching lines to overlap without dropping context by evaluating keywords for all lines (e.g. when "Failed to report" is followed by "Error:"). - Pass 2: If quota remains, scan backward from the absolute tail of the log to capture trailing crash or abort context. 3. Output the selected lines in their original chronological order, inserting a clear ... separator between non-contiguous line jumps. 4. Wrap matched failure keywords dynamically in bold red (PERF_COLOR_RED) to immediately draw the eye to failures. 5. Invoke the smart processor purely when verbose == 1 && ret == TEST_FAIL in both finish_test and finish_tests_parallel, leaving raw full-output dumping completely untouched when running highly verbose (-vv). Assisted-by: Gemini-CLI:Google Gemini 3 Signed-off-by: Ian Rogers --- tools/perf/tests/builtin-test.c | 200 +++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 3 deletions(-) diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 9f71f11928c6..e00b0bffa67c 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -48,6 +48,8 @@ static bool dont_fork; static bool sequential; /* Number of times each test is run. */ static unsigned int runs_per_test = 1; +/* Number of lines to include in failure snippet. */ +static unsigned int failure_snippet_lines = 10; const char *dso_to_test; const char *test_objdump_path = "objdump"; @@ -344,7 +346,7 @@ static int run_test_child(struct child_process *process) for (size_t i = 0; i < ARRAY_SIZE(signals); i++) signal(signals[i], child_test_sig_handler); - pr_debug("--- start ---\n"); + pr_debug("---- start ----\n"); pr_debug("test child forked, pid %d\n", getpid()); err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num); pr_debug("---- end(%d) ----\n", err); @@ -398,6 +400,192 @@ static int print_test_result(struct test_suite *t, int curr_suite, int curr_test return 0; } +static const char * const fail_keywords[] = { + "error", "fail", "segv", "abort", + "signal", "fatal", "panic", "corrupt", NULL +}; + +static const char *find_next_keyword(const char *str, size_t max_len, size_t *kw_len) +{ + const char *best = NULL; + size_t best_len = 0; + int k; + + for (k = 0; fail_keywords[k]; k++) { + const char *s = str; + size_t len = strlen(fail_keywords[k]); + + while ((size_t)(s - str) + len <= max_len) { + size_t i; + + for (i = 0; i < len; i++) { + if (tolower(s[i]) != fail_keywords[k][i]) + break; + } + if (i == len) { + if (!best || s < best) { + best = s; + best_len = len; + } + break; + } + s++; + } + } + if (best) { + *kw_len = best_len; + return best; + } + return NULL; +} + +static void print_line_highlighted(FILE *fp, const char *line, size_t len) +{ + const char *s = line; + + while (len > 0) { + size_t kw_len = 0; + const char *match = find_next_keyword(s, len, &kw_len); + + if (!match) { + fwrite(s, 1, len, fp); + break; + } + if (match > s) + fwrite(s, 1, match - s, fp); + if (perf_use_color_default) + fprintf(fp, "%s", PERF_COLOR_RED); + fwrite(match, 1, kw_len, fp); + if (perf_use_color_default) + fprintf(fp, "%s", PERF_COLOR_RESET); + + len -= (match + kw_len) - s; + s = match + kw_len; + } +} + + +static void print_test_failure_snippet(FILE *fp, const char *buf) +{ + size_t num_lines = 0; + size_t max_lines = 128; + const char **lines = calloc(max_lines, sizeof(const char *)); + size_t *line_lens = calloc(max_lines, sizeof(size_t)); + const char *s = buf; + size_t i; + unsigned int picked_count = 0; + bool *pick; + int last_printed = -1; + + if (!lines || !line_lens) { + free(lines); free(line_lens); + fprintf(fp, "%s", buf); + return; + } + + while (*s) { + const char *eol = strchr(s, '\n'); + size_t len; + + if (eol) + len = eol - s + 1; + else + len = strlen(s); + + if (num_lines == max_lines) { + const char **new_lines; + size_t *new_lens; + + max_lines *= 2; + new_lines = realloc(lines, max_lines * sizeof(const char *)); + if (!new_lines) { + free(lines); free(line_lens); + fprintf(fp, "%s", buf); + return; + } + lines = new_lines; + + new_lens = realloc(line_lens, max_lines * sizeof(size_t)); + if (!new_lens) { + free(lines); free(line_lens); + fprintf(fp, "%s", buf); + return; + } + line_lens = new_lens; + } + lines[num_lines] = s; + line_lens[num_lines] = len; + num_lines++; + s += len; + } + + if (num_lines <= failure_snippet_lines) { + for (i = 0; i < num_lines; i++) + print_line_highlighted(fp, lines[i], line_lens[i]); + free(lines); free(line_lens); + return; + } + + pick = calloc(num_lines, sizeof(bool)); + if (!pick) { + for (i = 0; i < num_lines; i++) + print_line_highlighted(fp, lines[i], line_lens[i]); + free(lines); free(line_lens); + return; + } + + /* Pass 0: Always pick the very first line */ + if (num_lines > 0 && picked_count < failure_snippet_lines) { + pick[0] = true; + picked_count++; + } + + /* Pass 1: Pick lines with failure keywords from start (Highest Priority) */ + for (i = 0; i < num_lines && picked_count < failure_snippet_lines; i++) { + size_t dummy; + + if (find_next_keyword(lines[i], line_lens[i], &dummy)) { + if (!pick[i]) { + pick[i] = true; + picked_count++; + } + /* Prioritize getting the immediate next line for context */ + if (i + 1 < num_lines && !pick[i + 1] && + picked_count < failure_snippet_lines) { + pick[i + 1] = true; + picked_count++; + } + } + } + + /* Pass 2: Fill remaining quota from the end backwards */ + i = num_lines; + while (i > 0 && picked_count < failure_snippet_lines) { + i--; + if (!pick[i]) { + pick[i] = true; + picked_count++; + } + } + + for (i = 0; i < num_lines; i++) { + if (!pick[i]) + continue; + if (last_printed != -1 && (int)i > last_printed + 1) { + if (perf_use_color_default) + fprintf(fp, "%s...%s\n", PERF_COLOR_BLUE, PERF_COLOR_RESET); + else + fprintf(fp, "...\n"); + } + print_line_highlighted(fp, lines[i], line_lens[i]); + last_printed = i; + } + + free(pick); + free(lines); + free(line_lens); +} + static void finish_test(struct child_test **child_tests, int running_test, int child_test_num, int width) { @@ -505,8 +693,10 @@ static void finish_test(struct child_test **child_tests, int running_test, int c } /* Clean up child process. */ ret = finish_command(&child_test->process); - if (verbose > 1 || (verbose == 1 && ret == TEST_FAIL)) + if (verbose > 1) fprintf(stderr, "%s", err_output.buf); + else if (verbose == 1 && ret == TEST_FAIL) + print_test_failure_snippet(stderr, err_output.buf); strbuf_release(&err_output); strbuf_release(&child_test->err_output); @@ -689,8 +879,10 @@ static int finish_tests_parallel(struct child_test **child_tests, size_t num_tes } } - if (verbose > 1 || (verbose == 1 && child->result == TEST_FAIL)) + if (verbose > 1) fprintf(stderr, "%s", child->err_output.buf); + else if (verbose == 1 && child->result == TEST_FAIL) + print_test_failure_snippet(stderr, child->err_output.buf); print_test_result(child->test, child->suite_num, child->test_case_num, child->result, width, 0); @@ -1026,6 +1218,8 @@ int cmd_test(int argc, const char **argv) OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"), OPT_STRING(0, "objdump", &test_objdump_path, "path", "objdump binary to use for disassembly and annotations"), + OPT_UINTEGER(0, "failure-snippet-lines", &failure_snippet_lines, + "Number of lines to include in failure snippet, default 10"), OPT_END() }; const char * const test_subcommands[] = { "list", NULL }; -- 2.54.0.823.g6e5bcc1fc9-goog