#!/usr/bin/env sh

# 安湖沪深 Level-2 SDK 运行环境检查脚本。
# 只读取系统配置，不会修改 sysctl、网卡或大页设置。

set -u

config_file=""
interface_name=""
fpga_ip=""
require_onload=0
strict_mode=0
warning_count=0
failure_count=0
result_count=0
result_rows=""

usage() {
    cat <<'EOF'
用法: check_env.sh IFACE [选项]

IFACE                 必填，要检查的网卡名称，例如 eth0
-c, --config FILE     SDK 配置文件，默认查找 conf/ah_sdk_sf.conf
-i, --interface IFACE 以选项形式指定网卡（兼容写法）
    --require-onload   强制要求 Onload 版本在 8.0 至 9.1.1 之间
    --strict           将所有警告也视为失败
-h, --help             显示帮助
EOF
}

pass() {
    result_count=$((result_count + 1))
    result_rows="${result_rows}${result_count}|PASS|$1
"
    printf '[检查通过] %s\n' "$1"
}

warn() {
    warning_count=$((warning_count + 1))
    result_count=$((result_count + 1))
    result_rows="${result_rows}${result_count}|WARN|$1
"
    printf '[警告] %s\n' "$1"
}

fail() {
    failure_count=$((failure_count + 1))
    result_count=$((result_count + 1))
    result_rows="${result_rows}${result_count}|FAIL|$1
"
    printf '[检查失败] %s\n' "$1"
}

print_results() {
    echo
    echo '检查结果汇总：'
    printf '%-4s | %-6s | %s\n' 'NO' 'STATUS' 'RESULT'
    printf '%-4s-+-%-6s-+-%s\n' '----' '------' '------------------------------------------------------------'

    while IFS='|' read -r number status detail; do
        [ -n "$number" ] || continue
        printf '%-4s | %-6s | %s\n' "$number" "$status" "$detail"
    done <<EOF
$result_rows
EOF
    echo
}

has_command() {
    command -v "$1" >/dev/null 2>&1
}

is_integer() {
    case "$1" in
        ''|*[!0-9]*) return 1 ;;
        *) return 0 ;;
    esac
}

version_ge() {
    awk -v left="$1" -v right="$2" '
        BEGIN {
            left_count = split(left, left_parts, ".")
            right_count = split(right, right_parts, ".")
            count = (left_count > right_count ? left_count : right_count)
            for (i = 1; i <= count; i++) {
                left_value = (i <= left_count ? left_parts[i] + 0 : 0)
                right_value = (i <= right_count ? right_parts[i] + 0 : 0)
                if (left_value > right_value) exit 0
                if (left_value < right_value) exit 1
            }
            exit 0
        }
    '
}

version_le() {
    version_ge "$2" "$1"
}

read_sysctl_value() {
    if has_command sysctl && sysctl -n "$1" 2>/dev/null; then
        return 0
    fi

    case "$1" in
        net.core.rmem_default) cat /proc/sys/net/core/rmem_default 2>/dev/null ;;
        net.core.rmem_max) cat /proc/sys/net/core/rmem_max 2>/dev/null ;;
    esac
}

print_checklist() {
    echo
    echo '检查清单与文档建议值（固定宽度表格）：'
    echo 'Check list and documented recommendations:'
    printf '%-20s | %-62s | %-24s\n' 'CHECK ITEM' 'RECOMMENDED VALUE' 'CHECK METHOD'
    printf '%-20s-+-%-62s-+-%-24s\n' '--------------------' '--------------------------------------------------------------' '------------------------'
    printf '%-20s | %-62s | %-24s\n' 'OS' 'RHEL 7.4+; CentOS 7.9; Rocky 8.5+; Ubuntu 22.04/24.04' 'uname, os-release'
    printf '%-20s | %-62s | %-24s\n' 'CPU architecture' 'x86_64' 'uname -m'
    printf '%-20s | %-62s | %-24s\n' 'Linux kernel' 'Match SDK, Onload and NIC driver; no single minimum in doc' 'uname -r'
    printf '%-20s | %-62s | %-24s\n' 'Network link' 'SDK host and FPGA on the same network' 'ip link/route, ping'
    printf '%-20s | %-62s | %-24s\n' 'Socket RX buffer' 'rmem_default >= 268435456; rmem_max >= 536870912' 'sysctl'
    printf '%-20s | %-62s | %-24s\n' 'NIC RX ring' 'Current RX should reach the NIC preset maximum' 'ethtool -g'
    printf '%-20s | %-62s | %-24s\n' 'HugePages' 'HugePages_Total > 0' '/proc/meminfo'
    printf '%-20s | %-62s | %-24s\n' 'CPU pinning' '6 receive threads + 3 dispatch threads; same NUMA preferred' 'CPU, NUMA, config'
    printf '%-20s | %-62s | %-24s\n' 'Config file' 'conf/ah_sdk_sf.conf' 'config fields'
    printf '%-20s | %-62s | %-24s\n' 'Onload (Solarflare)' 'Version 8.0 to 9.1.1 inclusive; optional for normal NIC mode' 'onload --version'
    echo
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        -c|--config)
            [ "$#" -ge 2 ] || { echo '选项 --config 缺少参数' >&2; exit 2; }
            config_file=$2
            shift 2
            ;;
        -i|--interface)
            [ "$#" -ge 2 ] || { echo '选项 --interface 缺少参数' >&2; exit 2; }
            interface_name=$2
            shift 2
            ;;
        --fpga-ip)
            [ "$#" -ge 2 ] || { echo '选项 --fpga-ip 缺少参数' >&2; exit 2; }
            fpga_ip=$2
            shift 2
            ;;
        --require-onload) require_onload=1; shift ;;
        --strict) strict_mode=1; shift ;;
        -h|--help) usage; exit 0 ;;
        -*) echo "未知参数: $1" >&2; usage >&2; exit 2 ;;
        *)
            if [ -z "$interface_name" ]; then
                interface_name=$1
                shift
            else
                echo "多余的网卡参数: $1" >&2
                usage >&2
                exit 2
            fi
            ;;
    esac
done

if [ -z "$interface_name" ]; then
    echo '必须提供网卡名称，例如: check_hushensdk_env.sh eth0' >&2
    usage >&2
    exit 2
fi

echo '===  SDK 环境检查 ==='
print_checklist

# 操作系统、发行版、架构和内核。
os_name=$(uname -s 2>/dev/null || echo unknown)
architecture=$(uname -m 2>/dev/null || echo unknown)
kernel_version=$(uname -r 2>/dev/null || echo unknown)

if [ "$os_name" = Linux ]; then
    pass "操作系统为 Linux（内核 $kernel_version）"
else
    fail "操作系统为 $os_name，SDK 要求 Linux"
fi

distro_id=unknown
distro_version=unknown
if [ -r /etc/os-release ]; then
    # shellcheck disable=SC1091
    . /etc/os-release
    distro_id=${ID:-unknown}
    distro_version=${VERSION_ID:-unknown}
fi

case "$distro_id" in
    rhel|redhat|centos|rocky|almalinux)
        if version_ge "$distro_version" 7.4; then
            pass "发行版 $distro_id $distro_version 满足 RHEL/CentOS 7.4+"
        else
            fail "发行版 $distro_id $distro_version 低于 7.4"
        fi
        ;;
    ubuntu)
        if version_ge "$distro_version" 22.04; then
            pass "发行版 Ubuntu $distro_version（文档适配 22.04+）"
        else
            fail "Ubuntu $distro_version 低于 22.04"
        fi
        ;;
    *) warn "发行版 $distro_id $distro_version 未列入文档适配清单，请人工确认" ;;
esac

case "$architecture" in
    x86_64|amd64) pass 'CPU 架构为 x86_64' ;;
    *) warn "CPU 架构为 $architecture；文档推荐 x64" ;;
esac

# 网络接口、链路和可选 FPGA 地址。
if ! has_command ip; then
    fail '未找到 ip 命令，无法检查网络接口'
else
    link_up=0
    if ! ip link show dev "$interface_name" >/dev/null 2>&1; then
        fail "网卡 $interface_name 不存在"
    else
        link_state=$(cat "/sys/class/net/$interface_name/operstate" 2>/dev/null || echo unknown)
        if [ "$link_state" = up ]; then
            pass "网卡 $interface_name 链路状态为 UP"
            link_up=1
        else
            warn "网卡 $interface_name 链路状态为 $link_state"
        fi
    fi
    [ "$link_up" -eq 1 ] || fail '没有处于 UP 状态的可用网卡'

    if [ -n "$fpga_ip" ]; then
        route_info=$(ip route get "$fpga_ip" 2>/dev/null || true)
        [ -n "$route_info" ] && pass "到 FPGA 地址 $fpga_ip 存在路由" || fail "到 FPGA 地址 $fpga_ip 没有路由"
        if [ -n "$route_info" ] && has_command ping && ping -c 1 -W 1 "$fpga_ip" >/dev/null 2>&1; then
            pass "FPGA 地址 $fpga_ip 可达"
        else
            warn "FPGA 地址 $fpga_ip 无法 ping 通（可能禁 ping）"
        fi
    else
        warn '未指定 --fpga-ip，SDK 与 FPGA 是否处于相同网络需人工确认'
    fi
fi

# Socket 接收缓冲区。
rmem_default=$(read_sysctl_value net.core.rmem_default)
rmem_max=$(read_sysctl_value net.core.rmem_max)
if is_integer "$rmem_default" && is_integer "$rmem_max"; then
    [ "$rmem_default" -ge 268435456 ] && pass "net.core.rmem_default=$rmem_default（达到建议值）" || warn "net.core.rmem_default=$rmem_default，低于建议值 268435456"
    [ "$rmem_max" -ge 536870912 ] && pass "net.core.rmem_max=$rmem_max（达到建议值）" || warn "net.core.rmem_max=$rmem_max，低于建议值 536870912"
else
    fail '无法读取 Socket 接收缓冲区参数'
fi

# 网卡 RX 环形缓冲区。
if has_command ethtool; then
    ethtool_output=$(ethtool -g "$interface_name" 2>/dev/null || true)
    if [ -z "$ethtool_output" ]; then
        warn "ethtool 无法读取网卡 $interface_name 的 RX 缓冲区"
    else
        rx_max=$(printf '%s\n' "$ethtool_output" | awk '/Pre-set maximums/{s=1;next}/Current hardware settings/{s=2;next}s==1&&$1=="RX:"{print $2;exit}')
        rx_current=$(printf '%s\n' "$ethtool_output" | awk '/Current hardware settings/{s=1;next}s==1&&$1=="RX:"{print $2;exit}')
        if is_integer "$rx_current"; then
            if is_integer "$rx_max" && [ "$rx_current" -lt "$rx_max" ]; then
                warn "网卡 $interface_name RX 当前值 $rx_current，小于最大值 $rx_max"
            else
                pass "网卡 $interface_name RX 环形缓冲区当前值为 $rx_current"
            fi
        else
            warn "网卡 $interface_name 的 ethtool 输出未找到 RX 当前值"
        fi
    fi
else
    warn '未安装 ethtool，跳过网卡 RX 环形缓冲区检查'
fi

# 大页内存。
if [ -r /proc/meminfo ]; then
    huge_pages_total=$(awk '/^HugePages_Total:/{print $2}' /proc/meminfo)
    if is_integer "$huge_pages_total" && [ "$huge_pages_total" -gt 0 ]; then
        pass "已启用大页内存（HugePages_Total=$huge_pages_total）"
    else
        fail "未启用大页内存（HugePages_Total=${huge_pages_total:-0}）"
    fi
else
    fail '无法读取 /proc/meminfo'
fi

# CPU、NUMA 和配置文件绑核。
cpu_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc 2>/dev/null || echo 0)
if is_integer "$cpu_count" && [ "$cpu_count" -ge 9 ]; then
    pass "在线逻辑 CPU 数为 $cpu_count（满足 6+3 线程规划）"
else
    warn "在线逻辑 CPU 数为 ${cpu_count:-unknown}，少于建议的 9 个核心"
fi

numa_sysfs=0
for node_dir in /sys/devices/system/node/node[0-9]*; do
    if [ -d "$node_dir" ]; then numa_sysfs=1; break; fi
done
if has_command numactl; then
    numactl --hardware >/dev/null 2>&1 && pass 'NUMA 拓扑可正常读取' || warn 'numactl 无法读取 NUMA 拓扑'
elif [ "$numa_sysfs" -eq 1 ]; then
    pass '检测到 NUMA 拓扑（使用 sysfs）'
else
    warn '未检测到 NUMA 拓扑工具，建议安装 numactl'
fi

if [ -z "$config_file" ]; then
    for candidate in conf/ah_sdk_sf.conf ./ah_sdk_sf.conf; do
        if [ -f "$candidate" ]; then config_file=$candidate; break; fi
    done
fi
if [ -n "$config_file" ] && [ -f "$config_file" ]; then
    pass "找到 SDK 配置文件 $config_file"
    invalid_cpu=0
    binding_count=0
    cpu_bindings=$(sed 's/#.*//' "$config_file" | awk -F= '/^[[:space:]]*(recv_np|index_np|snapshot_np|tick_np)[[:space:]]*=/{gsub(/[[:space:]]/,"",$2);print $2}')
    for binding in $cpu_bindings; do
        binding_count=$((binding_count + 1))
        if ! is_integer "$binding" || [ "$binding" -ge "$cpu_count" ]; then
            warn "配置文件包含无效 CPU 核心编号: $binding"
            invalid_cpu=1
        fi
    done
    if [ "$binding_count" -eq 0 ]; then
        warn '配置文件未找到 recv_np/index_np/snapshot_np/tick_np 绑核配置'
    elif [ "$invalid_cpu" -eq 0 ]; then
        pass '配置文件中的 CPU 绑核编号有效'
    fi
else
    warn '未找到 conf/ah_sdk_sf.conf，跳过配置文件和 CPU 绑核检查'
fi

# Onload 版本：Solarflare 模式要求 8.0 至 9.1.1（含边界）。
if has_command onload; then
    onload_version=$(onload --version 2>&1 || true)
    onload_version=$(printf '%s\n' "$onload_version" | grep -Eo '[0-9]+\.[0-9]+([.][0-9]+)?' | head -n 1)
    if [ -n "$onload_version" ] && version_ge "$onload_version" 8.0 && version_le "$onload_version" 9.1.1; then
        pass "Onload 版本为 $onload_version（满足 8.0 至 9.1.1）"
    elif [ "$require_onload" -eq 1 ]; then
        fail "Onload 版本 ${onload_version:-未知} 不在 8.0 至 9.1.1 范围内"
    else
        warn "Onload 版本 ${onload_version:-未知} 不在 Solarflare 要求的 8.0 至 9.1.1 范围内"
    fi
elif [ "$require_onload" -eq 1 ]; then
    fail '未安装 Onload；Solarflare 模式要求 8.0 至 9.1.1'
else
    warn '未安装 Onload；普通网卡模式可忽略，Solarflare 模式必须安装 8.0 至 9.1.1'
fi

print_results

echo '=== 检查结束：通过项已逐项标记 ==='
if [ "$failure_count" -gt 0 ] || { [ "$strict_mode" -eq 1 ] && [ "$warning_count" -gt 0 ]; }; then
    echo "结果：不满足运行要求（失败 $failure_count 项，警告 $warning_count 项）"
    exit 1
fi
echo "结果：未发现硬性失败（警告 $warning_count 项，请按提示人工确认）"
exit 0
