On Tue, Jul 25, 2023 at 06:56:51PM -0700, Darrick J. Wong wrote: > From: Darrick J. Wong <djwong@xxxxxxxxxx> > > Support collecting kernel code coverage information as reported in > debugfs. At the start of each section, we reset the gcov counters; > during the section wrapup, we'll collect the kernel gcov data. > > If lcov is installed and the kernel source code is available, it will > also generate a nice html report. If a CLI web browser is available, it > will also format the html report into text for easy grepping. > > This requires the test runner to set REPORT_GCOV=1 explicitly and gcov > to be enabled in the kernel. > > Cc: tytso@xxxxxxx > Cc: kent.overstreet@xxxxxxxxx > Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx> > --- This version looks good to me, Reviewed-by: Zorro Lang <zlang@xxxxxxxxxx> > README | 3 ++ > check | 18 ++++++++++++ > common/gcov | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 108 insertions(+) > create mode 100644 common/gcov > > > diff --git a/README b/README > index d4ec73d10d..966ec48ed6 100644 > --- a/README > +++ b/README > @@ -249,6 +249,9 @@ Kernel/Modules related configuration: > to "forever" and we'll wait forever until the module is gone. > - Set KCONFIG_PATH to specify your preferred location of kernel config > file. The config is used by tests to check if kernel feature is enabled. > + - Set REPORT_GCOV to a directory path to make lcov and genhtml generate > + html reports from any gcov code coverage data collected by the kernel. > + If REPORT_GCOV is set to 1, the report will be written to $REPORT_DIR/gcov/. > > Test control: > - Set LOAD_FACTOR to a nonzero positive integer to increase the amount of > diff --git a/check b/check > index c02e693642..9741be23c4 100755 > --- a/check > +++ b/check > @@ -451,6 +451,11 @@ _global_log() { > fi > } > > +if [ -n "$REPORT_GCOV" ]; then > + . ./common/gcov > + _gcov_check_report_gcov > +fi > + > _wrapup() > { > seq="check" > @@ -527,6 +532,18 @@ _wrapup() > "${#bad[*]}" "${#notrun[*]}" \ > "$((sect_stop - sect_start))" > fi > + > + # Generate code coverage report > + if [ -n "$REPORT_GCOV" ]; then > + # don't trigger multiple times if caller hits ^C > + local gcov_report_dir="$REPORT_GCOV" > + test "$gcov_report_dir" = "1" && \ > + gcov_report_dir="$REPORT_DIR/gcov" > + unset REPORT_GCOV > + > + _gcov_generate_report "$gcov_report_dir" > + fi > + > needwrap=false > fi > > @@ -801,6 +818,7 @@ function run_section() > echo "MOUNT_OPTIONS -- `_scratch_mount_options`" > fi > echo > + test -n "$REPORT_GCOV" && _gcov_reset > needwrap=true > > if [ ! -z "$SCRATCH_DEV" ]; then > diff --git a/common/gcov b/common/gcov > new file mode 100644 > index 0000000000..b7e3ed5a93 > --- /dev/null > +++ b/common/gcov > @@ -0,0 +1,87 @@ > +#!/bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# Copyright (c) 2023 Oracle. All Rights Reserved. > +# > +# Routines for capturing kernel code coverage reports > + > +GCOV_DIR=/sys/kernel/debug/gcov > + > +# Find the topmost directories of the .gcno directory hierarchy > +__gcov_find_topdirs() { > + find "${GCOV_DIR}/" -name '*.gcno' -printf '%d|%h\n' | \ > + sort -g -k 1 | \ > + uniq | \ > + $AWK_PROG -F '|' 'BEGIN { x = -1 } { if (x < 0) x = $1; if ($1 == x) printf("%s\n", $2);}' > +} > + > +# Generate lcov html report from kernel gcov data if configured > +_gcov_generate_report() { > + local output_dir="$1" > + test -n "${output_dir}" || return > + > + # Kernel support built in? > + test -d "$GCOV_DIR" || return > + > + readarray -t gcno_dirs < <(__gcov_find_topdirs) > + test "${#gcno_dirs[@]}" -gt 0 || return > + > + mkdir -p "${output_dir}/raw/" > + > + # Collect raw coverage data from the kernel > + readarray -t source_dirs < <(find "${GCOV_DIR}/" -mindepth 1 -maxdepth 1 -type d) > + for dir in "${source_dirs[@]}"; do > + cp -p -R -d -u "${dir}" "${output_dir}/raw/" > + done > + > + # If lcov is installed, use it to summarize the gcda data. > + # If it is not installed, there's no point in going forward > + command -v lcov > /dev/null || return > + local lcov=(lcov --exclude 'include*' --capture) > + lcov+=(--output-file "${output_dir}/gcov.report") > + for d in "${gcno_dirs[@]}"; do > + lcov+=(--directory "${d}") > + done > + > + # Generate a detailed HTML report from the summary > + local gcov_start_time="$(date --date="${fstests_start_time:-now}")" > + local genhtml=() > + if command -v genhtml > /dev/null; then > + genhtml+=(genhtml -o "${output_dir}/" "${output_dir}/gcov.report") > + genhtml+=(--title "fstests on $(hostname -s) @ ${gcov_start_time}" --legend) > + fi > + > + # Try to convert the HTML report summary as text for easier grepping if > + # there's an HTML renderer present > + local totext=() > + test "${#totext[@]}" -eq 0 && \ > + command -v lynx &>/dev/null && \ > + totext=(lynx -dump "${output_dir}/index.html" -width 120 -nonumbers -nolist) > + test "${#totext[@]}" -eq 0 && \ > + command -v links &>/dev/null && \ > + totext=(links -dump "${output_dir}/index.html" -width 120) > + test "${#totext[@]}" -eq 0 && \ > + command -v elinks &>/dev/null && \ > + totext=(elinks -dump "${output_dir}/index.html" --dump-width 120 --no-numbering --no-references) > + > + # Analyze kernel data > + "${lcov[@]}" > "${output_dir}/gcov.stdout" 2> "${output_dir}/gcov.stderr" > + test "${#genhtml[@]}" -ne 0 && \ > + "${genhtml[@]}" >> "${output_dir}/gcov.stdout" 2>> "${output_dir}/gcov.stderr" > + test "${#totext[@]}" -ne 0 && \ > + "${totext[@]}" > "${output_dir}/index.txt" 2>> "${output_dir}/gcov.stderr" > +} > + > +# Reset gcov usage data > +_gcov_reset() { > + echo 1 > "${GCOV_DIR}/reset" > +} > + > +# If the caller wanted us to capture gcov reports but the kernel doesn't > +# support it, turn it off. > +_gcov_check_report_gcov() { > + test -z "$REPORT_GCOV" && return 0 > + test -w "${GCOV_DIR}/reset" && return 0 > + > + unset REPORT_GCOV > + return 1 > +} >