diff --git a/.github/actions/veristat_baseline_compare/action.yml b/.github/actions/veristat_baseline_compare/action.yml new file mode 100644 index 0000000000000..7ea487af61b8d --- /dev/null +++ b/.github/actions/veristat_baseline_compare/action.yml @@ -0,0 +1,49 @@ +name: 'run-veristat' +description: 'Run veristat benchmark' +inputs: + veristat_output: + description: 'Veristat output filepath' + required: true + baseline_name: + description: 'Veristat baseline cache name' + required: true +runs: + using: "composite" + steps: + - uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.baseline_name }} + if-no-files-found: error + path: ${{ github.workspace }}/${{ inputs.veristat_output }} + + # For pull request: + # - get baseline log from cache + # - compare it to current run + - if: ${{ github.event_name == 'pull_request' }} + uses: actions/cache/restore@v3 + with: + key: ${{ inputs.baseline_name }} + restore-keys: | + ${{ inputs.baseline_name }}- + path: '${{ github.workspace }}/${{ inputs.baseline_name }}' + + - if: ${{ github.event_name == 'pull_request' }} + name: Show veristat comparison + shell: bash + run: ./.github/scripts/compare-veristat-results.sh + env: + BASELINE_PATH: ${{ github.workspace }}/${{ inputs.baseline_name }} + VERISTAT_OUTPUT: ${{ inputs.veristat_output }} + + # For push: just put baseline log to cache + - if: ${{ github.event_name == 'push' }} + shell: bash + run: | + mv "${{ github.workspace }}/${{ inputs.veristat_output }}" \ + "${{ github.workspace }}/${{ inputs.baseline_name }}" + + - if: ${{ github.event_name == 'push' }} + uses: actions/cache/save@v3 + with: + key: ${{ inputs.baseline_name }}-${{ github.run_id }} + path: '${{ github.workspace }}/${{ inputs.baseline_name }}' diff --git a/.github/scripts/bpf-objects-rootfs.sh b/.github/scripts/bpf-objects-rootfs.sh new file mode 100755 index 0000000000000..223e3ee350777 --- /dev/null +++ b/.github/scripts/bpf-objects-rootfs.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +eval "$(guestfish --listen)" + +guestfish --verbose --remote \ + add /tmp/root.img label:img : \ + launch : \ + mount /dev/disk/guestfs/img / : \ + copy-in /tmp/bpf_objects / : \ + chmod 0755 /bpf_objects + +guestfish --remote exit diff --git a/.github/scripts/compare-veristat-results.sh b/.github/scripts/compare-veristat-results.sh new file mode 100755 index 0000000000000..f15e759f53b5c --- /dev/null +++ b/.github/scripts/compare-veristat-results.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [[ ! -f "${BASELINE_PATH}" ]]; then + echo "# No ${BASELINE_PATH} available" >> "${GITHUB_STEP_SUMMARY}" + + echo "No ${BASELINE_PATH} available" + echo "Printing veristat results" + cat "${VERISTAT_OUTPUT}" + + exit +fi + +selftests/bpf/veristat \ + --output-format csv \ + --emit file,prog,verdict,states \ + --compare "${BASELINE_PATH}" "${VERISTAT_OUTPUT}" > compare.csv + +python3 ./.github/scripts/veristat-compare.py compare.csv diff --git a/.github/scripts/get-commit-metadata.sh b/.github/scripts/get-commit-metadata.sh new file mode 100644 index 0000000000000..f178786e4e58a --- /dev/null +++ b/.github/scripts/get-commit-metadata.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +branch="${GITHUB_BASE_REF}" + +if [ "${GITHUB_EVENT_NAME}" = 'push' ]; then + branch="${GITHUB_REF_NAME}" +fi + +echo "branch=${branch}" >> "${GITHUB_OUTPUT}" + +upstream="${branch//_base/}" +commit="$( + git rev-parse "origin/${upstream}" &> /dev/null \ + || ( + git fetch --quiet --prune --no-tags --depth=1 --no-recurse-submodules origin "+refs/heads/${upstream}:refs/remotes/origin/${upstream}" && \ + git rev-parse "origin/${upstream}" + ) +)" +timestamp_utc="$(TZ=utc git show --format='%cd' --no-patch --date=iso-strict-local "${commit}")" + +echo "timestamp=${timestamp_utc}" >> "${GITHUB_OUTPUT}" +echo "commit=${commit}" >> "${GITHUB_OUTPUT}" +echo "Most recent upstream commit is ${commit}" diff --git a/.github/scripts/matrix.py b/.github/scripts/matrix.py new file mode 100644 index 0000000000000..4e8ef4c738a3c --- /dev/null +++ b/.github/scripts/matrix.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +from json import dumps +from enum import Enum +import os + + +class Arch(Enum): + """ + CPU architecture supported by CI. + """ + + AARCH64 = "aarch64" + S390X = "s390x" + X86_64 = "x86_64" + + +def set_output(name, value): + """Write an output variable to the GitHub output file.""" + with open(os.getenv("GITHUB_OUTPUT"), "a", encoding="utf-8") as file: + file.write(f"{name}={value}\n") + + +def generate_test_config(test): + """Create the configuration for the provided test.""" + experimental = test.endswith("_parallel") + config = { + "test": test, + "continue_on_error": experimental, + # While in experimental mode, parallel jobs may get stuck + # anywhere, including in user space where the kernel won't detect + # a problem and panic. We add a second layer of (smaller) timeouts + # here such that if we get stuck in a parallel run, we hit this + # timeout and fail without affecting the overall job success (as + # would be the case if we hit the job-wide timeout). For + # non-experimental jobs, 360 is the default which will be + # superseded by the overall workflow timeout (but we need to + # specify something). + "timeout_minutes": 30 if experimental else 360, + } + return config + + +def get_tests(config): + tests = [ + "test_progs", + "test_progs_parallel", + "test_progs_no_alu32", + "test_progs_no_alu32_parallel", + "test_maps", + "test_verifier", + ] + if config.get("parallel_tests", True): + return tests + return [test for test in tests if not test.endswith("parallel")] + + +matrix = [ + { + "kernel": "LATEST", + "runs_on": [], + "arch": Arch.X86_64.value, + "toolchain": "gcc", + "llvm-version": "16", + }, + { + "kernel": "LATEST", + "runs_on": [], + "arch": Arch.X86_64.value, + "toolchain": "llvm", + "llvm-version": "16", + }, + { + "kernel": "LATEST", + "runs_on": [], + "arch": Arch.AARCH64.value, + "toolchain": "gcc", + "llvm-version": "16", + }, + # { + # "kernel": "LATEST", + # "runs_on": [], + # "arch": Arch.AARCH64.value, + # "toolchain": "llvm", + # "llvm-version": "16", + # }, + { + "kernel": "LATEST", + "runs_on": [], + "arch": Arch.S390X.value, + "toolchain": "gcc", + "llvm-version": "16", + "parallel_tests": False, + }, +] +self_hosted_repos = [ + "kernel-patches/bpf", + "kernel-patches/vmtest", +] + +for idx in range(len(matrix) - 1, -1, -1): + if matrix[idx]["toolchain"] == "gcc": + matrix[idx]["toolchain_full"] = "gcc" + else: + matrix[idx]["toolchain_full"] = "llvm-" + matrix[idx]["llvm-version"] + +# Only a few repository within "kernel-patches" use self-hosted runners. +if ( + os.environ["GITHUB_REPOSITORY_OWNER"] != "kernel-patches" + or os.environ["GITHUB_REPOSITORY"] not in self_hosted_repos +): + # Outside of those repositories, we only run on x86_64 GH hosted runners (ubuntu-latest) + for idx in range(len(matrix) - 1, -1, -1): + if matrix[idx]["arch"] != Arch.X86_64.value: + del matrix[idx] + else: + matrix[idx]["runs_on"] = ["ubuntu-latest"] +else: + # Otherwise, run on (self-hosted, arch) runners + for idx in range(len(matrix) - 1, -1, -1): + matrix[idx]["runs_on"].extend(["self-hosted", matrix[idx]["arch"]]) + +build_matrix = {"include": matrix} +set_output("build_matrix", dumps(build_matrix)) + +test_matrix = { + "include": [ + {**config, **generate_test_config(test)} + for config in matrix + for test in get_tests(config) + ] +} +set_output("test_matrix", dumps(test_matrix)) + +veristat_runs_on = next( + x["runs_on"] + for x in matrix + if x["arch"] == os.environ["veristat_arch"] + and x["toolchain"] == os.environ["veristat_toolchain"] +) +set_output("veristat_runs_on", veristat_runs_on) diff --git a/.github/scripts/prepare-incremental-builds.sh b/.github/scripts/prepare-incremental-builds.sh new file mode 100644 index 0000000000000..17f825480ff47 --- /dev/null +++ b/.github/scripts/prepare-incremental-builds.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +set -eu + +commit_id="${1}" + +# $1 - the SHA-1 to fetch and check out +fetch_and_checkout() { + local build_base_sha + + build_base_sha="${1}" + # If cached artifacts became stale for one reason or another, we + # may not have the build base SHA available. Fetch it and retry. + git fetch origin "${build_base_sha}" && git checkout --quiet "${build_base_sha}" +} + +# $1 - value of KBUILD_OUTPUT +clear_cache_artifacts() { + local output_dir + + output_dir="${1}" + echo "Unable to find earlier upstream ref. Discarding KBUILD_OUTPUT contents..." + rm --recursive --force "${output_dir}" + mkdir "${output_dir}" + false +} + +# $1 - value of KBUILD_OUTPUT +# $2 - current time in ISO 8601 format +restore_source_code_times() { + local build_output + local current_time + local src_time + local obj_time + + build_output="${1}" + current_time="${2}" + src_time="$(date --iso-8601=ns --date="${current_time} - 2 minutes")" + obj_time="$(date --iso-8601=ns --date="${current_time} - 1 minute")" + + git ls-files | xargs --max-args=10000 touch -m --no-create --date="${src_time}" + find "${build_output}" -type f | xargs --max-args=10000 touch -m --no-create --date="${obj_time}" + git checkout --quiet - + echo "Adjusted src and obj time stamps relative to system time" +} + +mkdir --parents "${KBUILD_OUTPUT}" +current_time="$(date --iso-8601=ns)" + +if [ -f "${KBUILD_OUTPUT}/.build-base-sha" ]; then + build_base_sha="$(cat "${KBUILD_OUTPUT}/.build-base-sha")" + echo "Setting up base build state for ${build_base_sha}" + + ( + git checkout --quiet "${build_base_sha}" \ + || fetch_and_checkout "${build_base_sha}" \ + || clear_cache_artifacts "${KBUILD_OUTPUT}" + ) && restore_source_code_times "${KBUILD_OUTPUT}" "${current_time}" +else + echo "No previous build data found" +fi + +echo -n "${commit_id}" > "${KBUILD_OUTPUT}/.build-base-sha" diff --git a/.github/scripts/tar-artifact.sh b/.github/scripts/tar-artifact.sh new file mode 100644 index 0000000000000..cd88f9e84ba46 --- /dev/null +++ b/.github/scripts/tar-artifact.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -eux + +arch="${1}" +toolchain="${2}" + +# Remove intermediate object files that we have no use for. Ideally +# we'd just exclude them from tar below, but it does not provide +# options to express the precise constraints. +find selftests/ -name "*.o" -a ! -name "*.bpf.o" -print0 | \ + xargs --null --max-args=10000 rm + +# Strip debug information, which is excessively large (consuming +# bandwidth) while not actually being used (the kernel does not use +# DWARF to symbolize stacktraces). +strip --strip-debug "${KBUILD_OUTPUT}"/vmlinux + +additional_file_list=() +if [ "${GITHUB_REPOSITORY}" == "kernel-patches/vmtest" ]; then + # Package up a bunch of additional infrastructure to support running + # 'make kernelrelease' and bpf tool checks later on. + mapfile -t additional_file_list < <(find . -iname Makefile) + additional_file_list+=( + "scripts/" + "tools/testing/selftests/bpf/" + "tools/include/" + "tools/bpf/bpftool/" + ) +fi + +image_name=$(make -s image_name) + +# zstd is installed by default in the runner images. +tar -cf - \ + "${KBUILD_OUTPUT}/.config" \ + "${KBUILD_OUTPUT}/${image_name}" \ + "${KBUILD_OUTPUT}/include/config/auto.conf" \ + "${KBUILD_OUTPUT}/include/generated/autoconf.h" \ + "${KBUILD_OUTPUT}/vmlinux" \ + "${additional_file_list[@]}" \ + --exclude '*.cmd' \ + --exclude '*.d' \ + --exclude '*.h' \ + --exclude '*.output' \ + selftests/bpf/ | zstd -T0 -19 -o "vmlinux-${arch}-${toolchain}.tar.zst" diff --git a/.github/scripts/veristat-compare.py b/.github/scripts/veristat-compare.py new file mode 100644 index 0000000000000..9ff1ac8f21da3 --- /dev/null +++ b/.github/scripts/veristat-compare.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 + +# This script reads a CSV file produced by the following invocation: +# +# veristat --emit file,prog,verdict,states \ +# --output-format csv \ +# --compare ... +# +# And produces a markdown summary for the file. +# The summary is printed to standard output and appended to a file +# pointed to by GITHUB_STEP_SUMMARY variable. +# +# Script exits with return code 1 if there are new failures in the +# veristat results. +# +# For testing purposes invoke as follows: +# +# GITHUB_STEP_SUMMARY=/dev/null python3 veristat-compare.py test.csv +# +# File format (columns): +# 0. file_name +# 1. prog_name +# 2. verdict_base +# 3. verdict_comp +# 4. verdict_diff +# 5. total_states_base +# 6. total_states_comp +# 7. total_states_diff +# +# Records sample: +# file-a,a,success,failure,MISMATCH,12,12,+0 (+0.00%) +# file-b,b,success,success,MATCH,67,67,+0 (+0.00%) +# +# For better readability suffixes '_OLD' and '_NEW' +# are used instead of '_base' and '_comp' for variable +# names etc. + +import io +import os +import sys +import re +import csv +import logging +import argparse +import enum +from dataclasses import dataclass +from typing import Dict, List, Final + + +TRESHOLD_PCT: Final[int] = 0 + +SUMMARY_HEADERS = ["File", "Program", "Verdict", "States Diff (%)"] + +# expected format: +0 (+0.00%) / -0 (-0.00%) +TOTAL_STATES_DIFF_REGEX = \ + r"(?P[+-]\d+) \((?P[+-]\d+\.\d+)\%\)" + + +TEXT_SUMMARY_TEMPLATE: Final[str] = """ +# {title} + +{table} +""".strip() + +HTML_SUMMARY_TEMPLATE: Final[str] = """ +# {title} + +
+Click to expand + +{table} +
+""".strip() + +GITHUB_MARKUP_REPLACEMENTS: Final[Dict[str, str]] = { + "->": "→", + "(!!)": ":bangbang:", +} + +NEW_FAILURE_SUFFIX: Final[str] = "(!!)" + + +class VeristatFields(str, enum.Enum): + FILE_NAME = "file_name" + PROG_NAME = "prog_name" + VERDICT_OLD = "verdict_base" + VERDICT_NEW = "verdict_comp" + VERDICT_DIFF = "verdict_diff" + TOTAL_STATES_OLD = "total_states_base" + TOTAL_STATES_NEW = "total_states_comp" + TOTAL_STATES_DIFF = "total_states_diff" + + @classmethod + def headers(cls) -> List[str]: + return [ + cls.FILE_NAME, + cls.PROG_NAME, + cls.VERDICT_OLD, + cls.VERDICT_NEW, + cls.VERDICT_DIFF, + cls.TOTAL_STATES_OLD, + cls.TOTAL_STATES_NEW, + cls.TOTAL_STATES_DIFF, + ] + + +@dataclass +class VeristatInfo: + table: list + changes: bool + new_failures: bool + + def get_results_title(self) -> str: + if self.new_failures: + return "There are new veristat failures" + + if self.changes: + return "There are changes in verification performance" + + return "No changes in verification performance" + + def get_results_summary(self, markup: bool = False) -> str: + title = self.get_results_title() + if not self.table: + return f"# {title}\n" + + template = TEXT_SUMMARY_TEMPLATE + table = format_table(headers=SUMMARY_HEADERS, rows=self.table) + + if markup: + template = HTML_SUMMARY_TEMPLATE + table = github_markup_decorate(table) + + return template.format(title=title, table=table) + + +def get_state_diff(value: str) -> float: + matches = re.match(TOTAL_STATES_DIFF_REGEX, value) + if percentage_diff := matches.group("percentage_diff"): + return float(percentage_diff) + + raise ValueError( + f"Invalid {VeristatFields.TOTAL_STATES_DIFF} field value: {value}" + ) + + +def parse_table(csv_file): + reader = csv.DictReader(csv_file) + assert reader.fieldnames == VeristatFields.headers() + + new_failures = False + changes = False + table = [] + + for record in reader: + add = False + + verdict_old, verdict_new = ( + record[VeristatFields.VERDICT_OLD], + record[VeristatFields.VERDICT_NEW], + ) + + if record[VeristatFields.VERDICT_DIFF] == "MISMATCH": + changes = True + add = True + verdict = f"{verdict_old} -> {verdict_new}" + if verdict_new == "failure": + new_failures = True + verdict += f" {NEW_FAILURE_SUFFIX}" + else: + verdict = record[VeristatFields.VERDICT_NEW] + + diff = get_state_diff(record[VeristatFields.TOTAL_STATES_DIFF]) + if abs(diff) > TRESHOLD_PCT: + changes = True + add = True + + if not add: + continue + + table.append([ + record[VeristatFields.FILE_NAME], + record[VeristatFields.PROG_NAME], + verdict, + f"{diff:+.2f} %" + ]) + + return VeristatInfo( + table=table, + changes=changes, + new_failures=new_failures + ) + + +def github_markup_decorate(input_str: str) -> str: + for text, markup in GITHUB_MARKUP_REPLACEMENTS.items(): + input_str = input_str.replace(text, markup) + return input_str + + +def format_table(headers: List[str], rows: List[List[str]]) -> str: + column_width = [ + max(len(row[column_idx]) for row in [headers] + rows) + for column_idx in range(len(headers)) + ] + + # Row template string in the following format: + # "{0:8}|{1:10}|{2:15}|{3:7}|{4:10}" + row_template = "|".join( + f"{{{idx}:{width}}}" + for idx, width in enumerate(column_width) + ) + row_template_nl = f"|{row_template}|\n" + + with io.StringIO() as out: + out.write(row_template_nl.format(*headers)) + + separator_row = ["-" * width for width in column_width] + out.write(row_template_nl.format(*separator_row)) + + for row in rows: + row_str = row_template_nl.format(*row) + out.write(row_str) + + return out.getvalue() + + +def main( + compare_csv_filename: os.PathLike, + output_filename: os.PathLike +) -> None: + with open(compare_csv_filename, newline="", encoding="utf-8") as csv_file: + veristat_results = parse_table(csv_file) + + sys.stdout.write(veristat_results.get_results_summary()) + + with open(output_filename, encoding="utf-8", mode="a") as file: + file.write(veristat_results.get_results_summary(markup=True)) + + if veristat_results.new_failures: + return 1 + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Print veristat comparison output as markdown step summary" + ) + parser.add_argument("filename") + args = parser.parse_args() + summary_filename = os.getenv("GITHUB_STEP_SUMMARY") + if not summary_filename: + logging.error("GITHUB_STEP_SUMMARY environment variable is not set") + sys.exit(1) + sys.exit(main(args.filename, summary_filename)) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000000000..8805283a42271 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +name: "lint" + +on: + pull_request: + push: + branches: + - master + +jobs: + shellcheck: + # This workflow gets injected into other Linux repositories, but we don't + # want it to run there. + if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: ShellCheck + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + env: + SHELLCHECK_OPTS: --severity=warning --exclude=SC1091 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000000..cf4af2693b429 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,260 @@ +name: bpf-ci + +on: + pull_request: + push: + branches: + - bpf_base + - bpf-next_base + +env: + veristat_arch: x86_64 + veristat_toolchain: gcc + +concurrency: + group: ci-test-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + set-matrix: + # FIXME: set-matrix is lightweight, run it on any self-hosted machines for kernel-patches org + # so we do not wait for GH hosted runners when there potentially all are busy because of bpf-rc + # repo for instance. + # This could be somehow fixed long term by making this action/workflow re-usable and letting the called + # specify what to run on. + runs-on: ${{ github.repository_owner == 'kernel-patches' && 'x86_64' || 'ubuntu-latest' }} + outputs: + build-matrix: ${{ steps.set-matrix-impl.outputs.build_matrix }} + test-matrix: ${{ steps.set-matrix-impl.outputs.test_matrix }} + veristat-runs-on: ${{ steps.set-matrix-impl.outputs.veristat_runs_on }} + steps: + - uses: actions/checkout@v3 + - id: set-matrix-impl + run: | + python3 .github/scripts/matrix.py + build: + name: build for ${{ matrix.arch }} with ${{ matrix.toolchain_full }} + needs: set-matrix + runs-on: ${{ matrix.runs_on }} + timeout-minutes: 100 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.set-matrix.outputs.build-matrix) }} + env: + KERNEL: ${{ matrix.kernel }} + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + steps: + - uses: actions/checkout@v3 + # We fetch an actual bit of history here to facilitate incremental + # builds (which may check out some earlier upstream change). + with: + fetch-depth: 50 + - if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: Download bpf-next tree + uses: libbpf/ci/get-linux-source@main + with: + dest: '.kernel' + - if: ${{ github.repository == 'kernel-patches/vmtest' }} + name: Move linux source in place + shell: bash + run: | + rm -rf .kernel/.git + cp -rf .kernel/. . + rm -rf .kernel + - name: Get commit meta-data + id: get-commit-metadata + run: | + bash .github/scripts/get-commit-metadata.sh + - name: Pull recent KBUILD_OUTPUT contents + uses: actions/cache@v3 + with: + path: ${{ env.KBUILD_OUTPUT }} + key: kbuild-output-${{ matrix.arch }}-${{ matrix.toolchain_full }}-${{ steps.get-commit-metadata.outputs.branch }}-${{ steps.get-commit-metadata.outputs.timestamp }}-${{ steps.get-commit-metadata.outputs.commit }} + restore-keys: | + kbuild-output-${{ matrix.arch }}-${{ matrix.toolchain_full }}-${{ steps.get-commit-metadata.outputs.branch }}-${{ steps.get-commit-metadata.outputs.timestamp }}- + kbuild-output-${{ matrix.arch }}-${{ matrix.toolchain_full }}-${{ steps.get-commit-metadata.outputs.branch }}- + kbuild-output-${{ matrix.arch }}-${{ matrix.toolchain_full }}- + - name: Prepare incremental build + shell: bash + run: | + bash .github/scripts/prepare-incremental-builds.sh ${{ steps.get-commit-metadata.outputs.commit }} + - uses: libbpf/ci/patch-kernel@main + with: + patches-root: '${{ github.workspace }}/ci/diffs' + repo-root: '${{ github.workspace }}' + - name: Setup build environment + uses: libbpf/ci/setup-build-env@main + with: + llvm-version: ${{ matrix.llvm-version }} + - name: Build kernel image + uses: libbpf/ci/build-linux@main + with: + arch: ${{ matrix.arch }} + toolchain: ${{ matrix.toolchain }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + max-make-jobs: 32 + llvm-version: ${{ matrix.llvm-version }} + - name: Build selftests + uses: libbpf/ci/build-selftests@main + with: + toolchain: ${{ matrix.toolchain }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + max-make-jobs: 32 + llvm-version: ${{ matrix.llvm-version }} + - if: ${{ github.event_name != 'push' }} + name: Build samples + uses: libbpf/ci/build-samples@main + with: + toolchain: ${{ matrix.toolchain }} + kbuild-output: ${{ env.KBUILD_OUTPUT }} + max-make-jobs: 32 + llvm-version: ${{ matrix.llvm-version }} + - name: Tar artifacts + run: | + bash .github/scripts/tar-artifact.sh ${{ matrix.arch }} ${{ matrix.toolchain_full }} + - if: ${{ github.event_name != 'push' }} + name: Remove KBUILD_OUTPUT content + shell: bash + run: | + # Remove $KBUILD_OUTPUT to prevent cache creation for pull requests. + # Only on pushed changes are build artifacts actually cached, because + # of github.com/actions/cache's cache isolation logic. + rm -rf "${KBUILD_OUTPUT}" + - uses: actions/upload-artifact@v3 + with: + name: vmlinux-${{ matrix.arch }}-${{ matrix.toolchain_full }} + if-no-files-found: error + path: vmlinux-${{ matrix.arch }}-${{ matrix.toolchain_full }}.tar.zst + test: + if: ${{ github.event_name != 'push' }} + name: ${{ matrix.test }} on ${{ matrix.arch }} with ${{ matrix.toolchain_full }} + needs: [set-matrix, build] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.set-matrix.outputs.test-matrix) }} + runs-on: ${{ matrix.runs_on }} + timeout-minutes: 100 + env: + KERNEL: ${{ matrix.kernel }} + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: vmlinux-${{ matrix.arch }}-${{ matrix.toolchain_full }} + path: . + - name: Untar artifacts + # zstd is installed by default in the runner images. + run: zstd -d -T0 vmlinux-${{ matrix.arch }}-${{ matrix.toolchain_full }}.tar.zst --stdout | tar -xf - + - name: Prepare rootfs + uses: libbpf/ci/prepare-rootfs@main + with: + project-name: 'libbpf' + arch: ${{ matrix.arch }} + kernel: ${{ matrix.kernel }} + kernel-root: '.' + kbuild-output: ${{ env.KBUILD_OUTPUT }} + image-output: '/tmp/root.img' + test: ${{ matrix.test }} + - name: Run selftests + uses: libbpf/ci/run-qemu@main + continue-on-error: ${{ matrix.continue_on_error }} + timeout-minutes: ${{ matrix.timeout_minutes }} + with: + arch: ${{ matrix.arch}} + img: '/tmp/root.img' + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + max-cpu: 8 + kernel-test: ${{ matrix.test }} + veristat: + name: veristat + needs: [set-matrix, build] + runs-on: ${{ fromJSON(needs.set-matrix.outputs.veristat-runs-on) }} + timeout-minutes: 100 + env: + KERNEL: LATEST + REPO_ROOT: ${{ github.workspace }} + REPO_PATH: "" + KBUILD_OUTPUT: kbuild-output/ + permissions: + id-token: write + contents: read + steps: + - name: Setup environment variables + run: | + echo ARCH_AND_TOOL=${{ env.veristat_arch }}-${{ env.veristat_toolchain }} >> \ + ${GITHUB_ENV} + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + name: vmlinux-${{ env.ARCH_AND_TOOL }} + path: . + - name: Untar artifacts + # zstd is installed by default in the runner images. + run: zstd -d -T0 vmlinux-${{ env.ARCH_AND_TOOL }}.tar.zst --stdout | tar -xf - + + - name: Prepare rootfs + uses: libbpf/ci/prepare-rootfs@main + with: + project-name: 'libbpf' + arch: x86_64 + kernel: LATEST + kernel-root: '.' + kbuild-output: ${{ env.KBUILD_OUTPUT }} + image-output: '/tmp/root.img' + + - name: Configure AWS Credentials + # Disabling BPF objects download and veristat-meta benchmark for PRs + # created from fork repositories. These won't have access to required + # enviroment variables and secrets, and otherwise would consistently fail + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + role-session-name: github-action-bpf-ci + + - name: Download BPF objects + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + run: | + set -eux + if [ -n "$AWS_ROLE_ARN" ]; then + mkdir /tmp/bpf_objects + aws s3 sync s3://veristat-bpf-binaries /tmp/bpf_objects + fi + env: + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} + + - name: Add BPF objects to rootfs + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + shell: bash + run: ./.github/scripts/bpf-objects-rootfs.sh + + - name: Run veristat + uses: libbpf/ci/run-qemu@main + with: + arch: x86_64 + img: '/tmp/root.img' + vmlinuz: '${{ github.workspace }}/vmlinuz' + kernel-root: '.' + max-cpu: 8 + kernel-test: run_veristat_kernel,run_veristat_meta + output-dir: '${{ github.workspace }}' + + - name: Compare and save veristat.kernel.csv + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-kernel + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-kernel + + - name: Compare and save veristat.meta.csv + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + uses: ./.github/actions/veristat_baseline_compare + with: + veristat_output: veristat-meta + baseline_name: ${{ env.ARCH_AND_TOOL}}-baseline-veristat-meta diff --git a/README b/README index 669ac7c322927..e69de29bb2d1d 100644 --- a/README +++ b/README @@ -1,18 +0,0 @@ -Linux kernel -============ - -There are several guides for kernel developers and users. These guides can -be rendered in a number of formats, like HTML and PDF. Please read -Documentation/admin-guide/README.rst first. - -In order to build the documentation, use ``make htmldocs`` or -``make pdfdocs``. The formatted documentation can also be read online at: - - https://www.kernel.org/doc/html/latest/ - -There are various text files in the Documentation/ subdirectory, -several of them using the Restructured Text markup notation. - -Please read the Documentation/process/changes.rst file, as it contains the -requirements for building and running the kernel, and information about -the problems which may result by upgrading your kernel. diff --git a/ci/diffs/.keep b/ci/diffs/.keep new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ci/diffs/0001-Revert-bpf-Avoid-unnecessary-audit-log-for-CPU-secur.patch b/ci/diffs/0001-Revert-bpf-Avoid-unnecessary-audit-log-for-CPU-secur.patch new file mode 100644 index 0000000000000..3b6139225e7bf --- /dev/null +++ b/ci/diffs/0001-Revert-bpf-Avoid-unnecessary-audit-log-for-CPU-secur.patch @@ -0,0 +1,33 @@ +From 5440a12ac8fb2a8e051c597fcf5d85b427fe612a Mon Sep 17 00:00:00 2001 +From: Andrii Nakryiko +Date: Fri, 13 Oct 2023 12:44:34 -0700 +Subject: [PATCH] Revert "bpf: Avoid unnecessary audit log for CPU security + mitigations" + +This reverts commit 236334aeec0f93217cf9235f2004e61a0a1a5985. +--- + include/linux/bpf.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/include/linux/bpf.h b/include/linux/bpf.h +index f0891ba24cb1..61bde4520f5c 100644 +--- a/include/linux/bpf.h ++++ b/include/linux/bpf.h +@@ -2164,12 +2164,12 @@ static inline bool bpf_allow_uninit_stack(void) + + static inline bool bpf_bypass_spec_v1(void) + { +- return cpu_mitigations_off() || perfmon_capable(); ++ return perfmon_capable() || cpu_mitigations_off(); + } + + static inline bool bpf_bypass_spec_v4(void) + { +- return cpu_mitigations_off() || perfmon_capable(); ++ return perfmon_capable() || cpu_mitigations_off(); + } + + int bpf_map_new_fd(struct bpf_map *map, int flags); +-- +2.34.1 + diff --git a/ci/vmtest/configs/DENYLIST b/ci/vmtest/configs/DENYLIST new file mode 100644 index 0000000000000..e53b4640180e8 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST @@ -0,0 +1,7 @@ +# TEMPORARY +btf_dump/btf_dump: syntax +kprobe_multi_bench_attach +core_reloc/enum64val +core_reloc/size___diff_sz +core_reloc/type_based___diff_sz +test_ima # All of CI is broken on it following 6.3-rc1 merge diff --git a/ci/vmtest/configs/DENYLIST.aarch64 b/ci/vmtest/configs/DENYLIST.aarch64 new file mode 100644 index 0000000000000..487b19ede4b61 --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.aarch64 @@ -0,0 +1,4 @@ +cgrp_local_storage # libbpf: prog 'update_cookie_tracing': failed to attach: ERROR: strerror_r(-524)=22 +core_reloc_btfgen # run_core_reloc_tests:FAIL:run_btfgen unexpected error: 32512 (errno 22) +usdt/multispec # usdt_300_bad_attach unexpected pointer: 0x558c63d8f0 +xdp_bonding # whole test suite is very unstable on aarch64 diff --git a/ci/vmtest/configs/DENYLIST.s390x b/ci/vmtest/configs/DENYLIST.s390x new file mode 100644 index 0000000000000..e6829c94bdaae --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.s390x @@ -0,0 +1,5 @@ +deny_namespace # not yet in bpf denylist +tc_redirect/tc_redirect_dtime # very flaky +lru_bug # not yet in bpf-next denylist +usdt/basic # failing verifier due to bounds check after LLVM update +usdt/multispec # same as above diff --git a/ci/vmtest/configs/DENYLIST.x86_64 b/ci/vmtest/configs/DENYLIST.x86_64 new file mode 100644 index 0000000000000..6fc3413daab9f --- /dev/null +++ b/ci/vmtest/configs/DENYLIST.x86_64 @@ -0,0 +1 @@ +netcnt # with kvm enabled, fail with packets unexpected packets: actual 10001 != expected 10000 diff --git a/ci/vmtest/configs/run_veristat.kernel.cfg b/ci/vmtest/configs/run_veristat.kernel.cfg new file mode 100644 index 0000000000000..6ac3940672d91 --- /dev/null +++ b/ci/vmtest/configs/run_veristat.kernel.cfg @@ -0,0 +1,3 @@ +VERISTAT_OBJECTS_DIR="/${PROJECT_NAME}/selftests/bpf" +VERISTAT_OBJECTS_GLOB=$(awk '/^#/ { next; } { print $0 ".bpf.o"; }' "${BPF_SELFTESTS_DIR}/veristat.cfg") +VERISTAT_OUTPUT="veristat-kernel" diff --git a/ci/vmtest/configs/run_veristat.meta.cfg b/ci/vmtest/configs/run_veristat.meta.cfg new file mode 100644 index 0000000000000..3b40d25d048fb --- /dev/null +++ b/ci/vmtest/configs/run_veristat.meta.cfg @@ -0,0 +1,3 @@ +VERISTAT_OBJECTS_DIR="/bpf_objects" +VERISTAT_OBJECTS_GLOB="*.o" +VERISTAT_OUTPUT="veristat-meta" diff --git a/ci/vmtest/helpers.sh b/ci/vmtest/helpers.sh new file mode 100755 index 0000000000000..c44d0983156d0 --- /dev/null +++ b/ci/vmtest/helpers.sh @@ -0,0 +1,38 @@ +# shellcheck shell=bash + +# $1 - start or end +# $2 - fold identifier, no spaces +# $3 - fold section description +foldable() { + local YELLOW='\033[1;33m' + local NOCOLOR='\033[0m' + if [ $1 = "start" ]; then + line="::group::$2" + if [ ! -z "${3:-}" ]; then + line="$line - ${YELLOW}$3${NOCOLOR}" + fi + else + line="::endgroup::" + fi + echo -e "$line" +} + +__print() { + local TITLE="" + if [[ -n $2 ]]; then + TITLE=" title=$2" + fi + echo "::$1${TITLE}::$3" +} + +# $1 - title +# $2 - message +print_error() { + __print error $1 $2 +} + +# $1 - title +# $2 - message +print_notice() { + __print notice $1 $2 +} diff --git a/ci/vmtest/run_selftests.sh b/ci/vmtest/run_selftests.sh new file mode 100755 index 0000000000000..8d0f9e02eb2b4 --- /dev/null +++ b/ci/vmtest/run_selftests.sh @@ -0,0 +1,189 @@ +#!/bin/bash + +# run_selftest.sh will run the tests within /${PROJECT_NAME}/selftests/bpf +# If no specific test names are given, all test will be ran, otherwise, it will +# run the test passed as parameters. +# There is 2 ways to pass test names. +# 1) command-line arguments to this script +# 2) a comma-separated list of test names passed as `run_tests` boot parameters. +# test names passed as any of those methods will be ran. + +set -euo pipefail + +source "$(cd "$(dirname "$0")" && pwd)/helpers.sh" + +ARCH=$(uname -m) + +STATUS_FILE=/exitstatus +OUTPUT_DIR=/command_output + +BPF_SELFTESTS_DIR="/${PROJECT_NAME}/selftests/bpf" +VMTEST_CONFIGS_PATH="/${PROJECT_NAME}/vmtest/configs" + +read_lists() { + (for path in "$@"; do + if [[ -s "$path" ]]; then + cat "$path" + fi; + done) | cut -d'#' -f1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | tr -s '\n' ',' +} + +DENYLIST=$(read_lists \ + "$BPF_SELFTESTS_DIR/DENYLIST" \ + "$BPF_SELFTESTS_DIR/DENYLIST.${ARCH}" \ + "$VMTEST_CONFIGS_PATH/DENYLIST" \ + "$VMTEST_CONFIGS_PATH/DENYLIST.${ARCH}" \ +) +ALLOWLIST=$(read_lists \ + "$BPF_SELFTESTS_DIR/ALLOWLIST" \ + "$BPF_SELFTESTS_DIR/ALLOWLIST.${ARCH}" \ + "$VMTEST_CONFIGS_PATH/ALLOWLIST" \ + "$VMTEST_CONFIGS_PATH/ALLOWLIST.${ARCH}" \ +) + +declare -a TEST_NAMES=() + +read_test_names() { + foldable start read_test_names "Reading test names from boot parameters and command line arguments" + # Check if test names were passed as boot parameter. + # We expect `run_tests` to be a comma-separated list of test names. + IFS=',' read -r -a test_names_from_boot <<< \ + "$(sed -n 's/.*run_tests=\([^ ]*\).*/\1/p' /proc/cmdline)" + + echo "${#test_names_from_boot[@]} tests extracted from boot parameters: ${test_names_from_boot[*]}" + # Sort and only keep unique test names from both boot params and arguments + # TEST_NAMES will contain a sorted list of uniq tests to be ran. + # Only do this if any of $test_names_from_boot[@] or $@ has elements as + # "printf '%s\0'" will otherwise generate an empty element. + if [[ ${#test_names_from_boot[@]} -gt 0 || $# -gt 0 ]] + then + readarray -t TEST_NAMES < \ + <(printf '%s\0' "${test_names_from_boot[@]}" "$@" | \ + sort --zero-terminated --unique | \ + xargs --null --max-args=1) + fi + foldable end read_test_names +} + +test_progs_helper() { + local selftest="test_progs${1}" + local args="$2" + + json_file=${selftest/-/_} + if [ "$2" == "-j" ] + then + json_file+="_parallel" + fi + json_file="/${json_file}.json" + + foldable start ${selftest} "Testing ${selftest}" + # "&& true" does not change the return code (it is not executed + # if the Python script fails), but it prevents exiting on a + # failure due to the "set -e". + ./${selftest} ${args} ${DENYLIST:+-d"$DENYLIST"} ${ALLOWLIST:+-a"$ALLOWLIST"} --json-summary "${json_file}" && true + echo "${selftest}:$?" >>"${STATUS_FILE}" + foldable end ${selftest} +} + +test_progs() { + test_progs_helper "" "" +} + +test_progs_parallel() { + test_progs_helper "" "-j" +} + +test_progs_no_alu32() { + test_progs_helper "-no_alu32" "" +} + +test_progs_no_alu32_parallel() { + test_progs_helper "-no_alu32" "-j" +} + +test_maps() { + foldable start test_maps "Testing test_maps" + taskset 0xF ./test_maps && true + echo "test_maps:$?" >>"${STATUS_FILE}" + foldable end test_maps +} + +test_verifier() { + foldable start test_verifier "Testing test_verifier" + ./test_verifier && true + echo "test_verifier:$?" >>"${STATUS_FILE}" + foldable end test_verifier +} + +run_veristat_helper() { + local mode="${1}" + + # Make veristat commands visible in the log + if [ -o xtrace ]; then + xtrace_was_on="1" + else + xtrace_was_on="" + set -x + fi + + ( + # shellcheck source=ci/vmtest/configs/run_veristat.default.cfg + # shellcheck source=ci/vmtest/configs/run_veristat.meta.cfg + source "${VMTEST_CONFIGS_PATH}/run_veristat.${mode}.cfg" + mkdir -p ${OUTPUT_DIR} + pushd "${VERISTAT_OBJECTS_DIR}" + + "${BPF_SELFTESTS_DIR}/veristat" -o csv -q -e file,prog,verdict,states ${VERISTAT_OBJECTS_GLOB} > \ + "${OUTPUT_DIR}/${VERISTAT_OUTPUT}" + + echo "run_veristat_${mode}:$?" >> ${STATUS_FILE} + popd + ) + + # Hide commands again + if [ -z "$xtrace_was_on" ]; then + set +x + fi + +} + +run_veristat_kernel() { + foldable start run_veristat_kernel "Running veristat.kernel" + run_veristat_helper "kernel" + foldable end run_veristat_kernel +} + +run_veristat_meta() { + foldable start run_veristat_meta "Running veristat.meta" + run_veristat_helper "meta" + foldable end run_veristat_meta +} + +foldable end vm_init + +foldable start kernel_config "Kconfig" + +zcat /proc/config.gz + +foldable end kernel_config + +echo "DENYLIST: ${DENYLIST}" +echo "ALLOWLIST: ${ALLOWLIST}" + +cd ${PROJECT_NAME}/selftests/bpf + +# populate TEST_NAMES +read_test_names "$@" +# if we don't have any test name provided to the script, we run all tests. +if [ ${#TEST_NAMES[@]} -eq 0 ]; then + test_progs + test_progs_no_alu32 + test_maps + test_verifier +else + # else we run the tests passed as command-line arguments and through boot + # parameter. + for test_name in "${TEST_NAMES[@]}"; do + "${test_name}" + done +fi