Commit fdc1e9df authored by Ramon Nou's avatar Ramon Nou
Browse files

diags out

parent a4004a61
Loading
Loading
Loading
Loading
+0 −147
Original line number Diff line number Diff line
import logging
import os
import time
from pathlib import Path

import pytest

from harness.gkfs import Client, Daemon, ShellClient, find_command


log = logging.getLogger(__name__)

SFIND_DIAG_TIMEOUT = 180


def _read_text(path, limit=12000):
    try:
        data = Path(path).read_text(errors="replace")
    except OSError as exc:
        return f"<could not read {path}: {exc}>"
    if len(data) <= limit:
        return data
    return data[-limit:]


def _collect_workspace_logs(test_workspace):
    chunks = []
    for base in (test_workspace.logdir, test_workspace.twd):
        if not Path(base).exists():
            continue
        for path in sorted(Path(base).glob("**/*")):
            if path.is_file() and path.suffix in (".log", ".txt"):
                chunks.append(f"\n--- {path} ---\n{_read_text(path)}")
    return "".join(chunks) if chunks else "<no workspace logs found>"


def _sfind_env(conf, buff_size, iteration):
    return {
        "GKFS_USE_DIRENTS_COMPRESSION": conf["compress"],
        "LIBGKFS_DENTRY_CACHE": conf["cache"],
        "LIBGKFS_DIRENTS_BUFF_SIZE": str(buff_size),
        "LIBGKFS_LOG": "debug",
        "LIBGKFS_LOG_PER_PROCESS": "ON",
        "MALLOC_CHECK_": "3",
        "MALLOC_PERTURB_": str(1 + (iteration % 254)),
        "SFIND_NUM_THREADS": str(conf["threads"]),
    }


@pytest.mark.parametrize("buff_size", ["4096", "5242880"])
@pytest.mark.parametrize("conf", [
    {"compress": "OFF", "cache": "OFF", "threads": 1},
    {"compress": "ON", "cache": "OFF", "threads": 1},
    {"compress": "OFF", "cache": "ON", "threads": 1},
    {"compress": "ON", "cache": "ON", "threads": 1},
    {"compress": "OFF", "cache": "OFF", "threads": 20},
    {"compress": "ON", "cache": "ON", "threads": 20},
])
def test_sfind_repeated_teardown_diagnostic(test_workspace, request, conf, buff_size):
    """Stress sfind until the random SIGSEGV is reproducible with logs.

    The observed failure exits with -11 after printing MATCHED 2000/2000, so
    this diagnostic repeats only the sfind phase and records whether the crash
    depends on compression, dentry cache, buffer size, or sfind thread count.
    """

    repetitions = int(os.environ.get("GKFS_SFIND_DIAG_REPETITIONS", "25"))
    file_count = int(os.environ.get("GKFS_SFIND_DIAG_FILES", "2000"))

    populate_daemon = Daemon(
        request.config.getoption("--interface"),
        "rocksdb",
        test_workspace,
        env={
            "GKFS_DAEMON_LOG_LEVEL": "debug",
            "GKFS_USE_DIRENTS_COMPRESSION": "OFF",
        })
    populate_daemon.run()

    mount_dir = test_workspace.mountdir
    test_dir = mount_dir / "sfind_diagnostic_dir"
    io_client = Client(test_workspace)
    try:
        ret = io_client.mkdir(str(test_dir), 0o755)
        assert ret.retval == 0, f"mkdir failed: errno={ret.errno}"

        ret = io_client.create_n_files(
            str(test_dir),
            file_count,
            env={
                "GKFS_USE_DIRENTS_COMPRESSION": "OFF",
                "LIBGKFS_DENTRY_CACHE": "OFF",
                "LIBGKFS_LOG": "debug",
                "LIBGKFS_LOG_PER_PROCESS": "ON",
            })
        assert ret.retval == 0, f"population failed: errno={ret.errno}"
        assert ret.files_created == file_count
    finally:
        populate_daemon.shutdown()
        time.sleep(1)

    test_daemon = Daemon(
        request.config.getoption("--interface"),
        "rocksdb",
        test_workspace,
        env={
            "GKFS_DAEMON_LOG_LEVEL": "debug",
            "GKFS_USE_DIRENTS_COMPRESSION": conf["compress"],
        })
    test_daemon.run()

    sfind_bin = find_command("sfind", test_workspace.bindirs)
    assert sfind_bin, "sfind binary not found"

    try:
        shell = ShellClient(test_workspace)
        failures = []
        for iteration in range(repetitions):
            env = _sfind_env(conf, buff_size, iteration)
            ret = shell.run(
                str(sfind_bin), str(test_dir), "-S", "1", "-M", str(mount_dir),
                timeout=SFIND_DIAG_TIMEOUT, env=env)
            stdout = ret.stdout.decode(errors="replace") if ret.stdout else ""
            stderr = ret.stderr.decode(errors="replace") if ret.stderr else ""
            log.info(
                "sfind diagnostic iteration=%s conf=%s buff_size=%s exit=%s\n"
                "stdout:\n%s\nstderr:\n%s",
                iteration, conf, buff_size, ret.exit_code, stdout, stderr)

            if ret.exit_code != 0 or f"MATCHED {file_count}/{file_count}" not in stdout:
                failures.append((iteration, ret.exit_code, stdout, stderr))
                if ret.exit_code == -11:
                    break

        if failures:
            iteration, exit_code, stdout, stderr = failures[0]
            pytest.fail(
                "sfind diagnostic reproduced failure\n"
                f"iteration: {iteration}\n"
                f"conf: {conf}\n"
                f"buff_size: {buff_size}\n"
                f"exit_code: {exit_code}\n"
                f"stdout:\n{stdout}\n"
                f"stderr:\n{stderr}\n"
                f"workspace logs:\n{_collect_workspace_logs(test_workspace)}")
    finally:
        test_daemon.shutdown()
 No newline at end of file