#!/bin/bash
# RedVPSHub benchmark — rvh.sh
# Usage: curl -sL https://redvpshub.ru/i/rvh.sh | bash

set -u
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

RVH_VERSION="1.9.1"
METHODOLOGY="1.9.1"
RUNS=3
REPORT_SERVER="https://redvpshub.ru/api/bench-report.php"
REPORT_TOKEN="rvh_report_k7m2p9x4"
SITE="https://redvpshub.ru"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
DIM='\033[2m'
NC='\033[0m'

SPIN='|/-\\'
SPIN_i=0
FIO_DIR=""
FIO_SEQ=""
FIO_RAND=""

spin() {
  printf "\r${CYAN}  %s ${1}${NC}   " "${SPIN:SPIN_i:1}" >&2
  SPIN_i=$(( (SPIN_i + 1) % 4 ))
}

# Последнее число из строки — на случай ANSI/спиннера в захвате.
as_num() {
  python3 -c "import re,sys
s=sys.argv[1] if len(sys.argv)>1 else ''
m=re.findall(r'[0-9]+(?:\\.[0-9]+)?', s)
print(m[-1] if m else '0')" "${1:-}"
}

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

fail() {
  echo -e "\n${RED}✗ $1${NC}" >&2
  exit 1
}

print_header() {
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
  echo -e "${GREEN}  RedVPSHub · проверка VPS v${RVH_VERSION}${NC}"
  echo -e "${DIM}  $(date '+%Y-%m-%d %H:%M:%S') · ${SITE}${NC}"
  echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
}

as_root() {
  if [ "$(id -u)" -eq 0 ]; then
    "$@"
  elif need_cmd sudo && sudo -n true 2>/dev/null; then
    sudo -n "$@"
  else
    return 1
  fi
}

pkg_for_cmd() {
  case "$1" in
    ping)
      if need_cmd apt-get; then echo "iputils-ping"
      else echo "iputils"
      fi
      ;;
    *) echo "$1" ;;
  esac
}

install_hint() {
  local pkgs="$1"
  if need_cmd apt-get; then
    echo "sudo apt-get update && sudo apt-get install -y $pkgs"
  elif need_cmd dnf; then
    echo "sudo dnf install -y $pkgs"
  elif need_cmd yum; then
    echo "sudo yum install -y $pkgs"
  elif need_cmd apk; then
    echo "sudo apk add $pkgs"
  else
    echo "установите: $pkgs"
  fi
}

check_deps() {
  local cmd pkgs pkg missing=()
  for cmd in sysbench fio curl python3 ping; do
    if ! need_cmd "$cmd"; then
      missing+=("$cmd")
    fi
  done
  if [ ${#missing[@]} -eq 0 ]; then
    return 0
  fi

  pkgs=""
  for cmd in "${missing[@]}"; do
    pkg=$(pkg_for_cmd "$cmd")
    case " $pkgs " in
      *" $pkg "*) ;;
      *) pkgs="${pkgs:+$pkgs }$pkg" ;;
    esac
  done

  echo -e "${YELLOW}Не хватает: ${missing[*]}${NC}"

  if [ "${RVH_NO_INSTALL:-}" = "1" ]; then
    fail "Автоустановка выключена (RVH_NO_INSTALL=1). $(install_hint "$pkgs")"
  fi

  echo -e "${DIM}  Ставлю пакеты: ${pkgs}${NC}"
  if ! as_root true >/dev/null 2>&1; then
    fail "Нужен root. Запустите от root или: $(install_hint "$pkgs")"
  fi

  if need_cmd apt-get; then
    export DEBIAN_FRONTEND=noninteractive
    as_root apt-get update -qq || fail "apt-get update не удался"
    as_root apt-get install -y -qq $pkgs || fail "Не удалось поставить: $pkgs"
  elif need_cmd dnf; then
    as_root dnf install -y $pkgs || {
      as_root dnf install -y epel-release >/dev/null 2>&1 || true
      as_root dnf install -y $pkgs || fail "Не удалось поставить: $pkgs"
    }
  elif need_cmd yum; then
    as_root yum install -y $pkgs || {
      as_root yum install -y epel-release >/dev/null 2>&1 || true
      as_root yum install -y $pkgs || fail "Не удалось поставить: $pkgs"
    }
  elif need_cmd apk; then
    as_root apk add --no-cache $pkgs || fail "Не удалось поставить: $pkgs"
  else
    fail "Неизвестный пакетный менеджер. $(install_hint "$pkgs")"
  fi

  missing=()
  for cmd in sysbench fio curl python3 ping; do
    if ! need_cmd "$cmd"; then
      missing+=("$cmd")
    fi
  done
  if [ ${#missing[@]} -gt 0 ]; then
    fail "После установки всё ещё нет: ${missing[*]}"
  fi
  echo -e "${GREEN}  ✓ Зависимости установлены${NC}\n"
}

get_os() {
  if [ -f /etc/os-release ]; then
    . /etc/os-release
    echo "${PRETTY_NAME:-$NAME}"
  else
    echo "Linux"
  fi
}

get_virt() {
  local v p
  if need_cmd systemd-detect-virt; then
    v=$(systemd-detect-virt 2>/dev/null || true)
    if [ -n "$v" ] && [ "$v" != "none" ]; then
      echo "$v"
      return
    fi
  fi
  if [ -r /sys/class/dmi/id/product_name ]; then
    p=$(cat /sys/class/dmi/id/product_name 2>/dev/null | head -1)
    case "$p" in
      *KVM*|*QEMU*|*Virtual*) echo "KVM"; return ;;
    esac
  fi
  if [ -r /proc/cpuinfo ] && grep -qw hypervisor /proc/cpuinfo; then
    echo "KVM"
    return
  fi
  echo "KVM"
}

get_country() {
  local parsed
  COUNTRY=""
  LOCATION=""
  parsed=$(curl -fsS --max-time 6 http://ipinfo.io/json 2>/dev/null | python3 -c '
import json, sys
try:
    d = json.load(sys.stdin)
except Exception:
    sys.exit(0)
city = (d.get("city") or "").strip()
country = (d.get("country") or "").strip()
parts = []
if city:
    parts.append(city)
if country:
    parts.append(country)
sys.stdout.write(country + "\n" + ", ".join(parts))
' 2>/dev/null || true)
  if [ -n "$parsed" ]; then
    COUNTRY=$(printf '%s\n' "$parsed" | sed -n '1p' | tr -d '\r')
    LOCATION=$(printf '%s\n' "$parsed" | sed -n '2p' | tr -d '\r')
  fi
  if [ -z "$COUNTRY" ]; then
    COUNTRY=$(curl -fsS --max-time 5 http://ipinfo.io/country 2>/dev/null || true)
  fi
  if [ -z "$LOCATION" ]; then
    local city
    city=$(curl -fsS --max-time 5 http://ipinfo.io/city 2>/dev/null || true)
    if [ -n "$city" ] && [ -n "$COUNTRY" ]; then
      LOCATION="${city}, ${COUNTRY}"
    else
      LOCATION="${city:-$COUNTRY}"
    fi
  fi
}

guess_host() {
  local h
  h=$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo "")
  h=$(printf '%s' "$h" | tr '[:upper:]' '[:lower:]')
  case "$h" in
    *adminvps*) echo "adminvps" ;;
    *firstvds*) echo "firstvds" ;;
    *aeza*) echo "aeza" ;;
    *timeweb*) echo "timeweb" ;;
    *vdsina*) echo "vdsina" ;;
    *4vps*) echo "4vps" ;;
    *ruvds*) echo "ruvds" ;;
    *) echo "" ;;
  esac
}

pick_host() {
  if [ -n "${RVH_HOST:-}" ]; then
    HOST_SLUG=$(printf '%s' "$RVH_HOST" | tr '[:upper:]' '[:lower:]')
    HOST_NAME="${RVH_HOST_NAME:-}"
    return
  fi

  local guess choice
  guess=$(guess_host)

  echo -e "${CYAN}Хостер VPS${NC}"
  echo "  1) AdminVPS"
  echo "  2) FirstVDS"
  echo "  3) Aeza"
  echo "  4) Timeweb Cloud"
  echo "  5) VDSina"
  echo "  6) 4VPS"
  echo "  7) RuVDS"
  echo "  8) Другой"
  if [ -n "$guess" ]; then
    echo -e "  ${DIM}По hostname похоже на: ${guess}${NC}"
  fi

  choice=""
  if [ -r /dev/tty ] 2>/dev/null; then
    if [ -n "$guess" ]; then
      printf "  Номер [Enter = %s]: " "$guess"
    else
      printf "  Номер: "
    fi
    read -r choice </dev/tty 2>/dev/null || choice=""
  fi

  if [ -z "$choice" ] && [ -n "$guess" ]; then
    HOST_SLUG="$guess"
    return
  fi

  case "${choice:-}" in
    1|adminvps) HOST_SLUG="adminvps" ;;
    2|firstvds) HOST_SLUG="firstvds" ;;
    3|aeza) HOST_SLUG="aeza" ;;
    4|timeweb) HOST_SLUG="timeweb" ;;
    5|vdsina) HOST_SLUG="vdsina" ;;
    6|4vps) HOST_SLUG="4vps" ;;
    7|ruvds) HOST_SLUG="ruvds" ;;
    8|other)
      HOST_SLUG="other"
      if [ -r /dev/tty ] 2>/dev/null; then
        printf "  Название: "
        read -r HOST_NAME </dev/tty 2>/dev/null || HOST_NAME="Другой"
      else
        HOST_NAME="Другой"
      fi
      ;;
    *)
      if [ -n "$guess" ]; then
        HOST_SLUG="$guess"
      else
        HOST_SLUG="other"
        HOST_NAME="Другой"
      fi
      ;;
  esac
}

path_is_tmpfs() {
  local dir="$1"
  local fstype
  fstype=$(df -T "$dir" 2>/dev/null | awk 'NR==2 {print $2}')
  [ "$fstype" = "tmpfs" ]
}

pick_fio_paths() {
  local candidate dir
  for candidate in /var/tmp /tmp; do
    if [ -d "$candidate" ] && [ -w "$candidate" ] && ! path_is_tmpfs "$candidate"; then
      dir="$candidate/rvh_bench"
      mkdir -p "$dir" 2>/dev/null || continue
      FIO_DIR="$dir"
      FIO_SEQ="${dir}/seq"
      FIO_RAND="${dir}/rand"
      return 0
    fi
  done
  # Последний шанс: домашняя или /var/tmp даже если df врёт; tmpfs — только с явной пометкой.
  for candidate in /var/tmp /tmp "$HOME"; do
    [ -n "$candidate" ] || continue
    dir="$candidate/rvh_bench"
    mkdir -p "$dir" 2>/dev/null || continue
    [ -w "$dir" ] || continue
    FIO_DIR="$dir"
    FIO_SEQ="${dir}/seq"
    FIO_RAND="${dir}/rand"
    if path_is_tmpfs "$FIO_DIR"; then
      echo -e "${YELLOW}  ⚠ ${FIO_DIR} на tmpfs: цифры диска будут как у RAM${NC}" >&2
    fi
    return 0
  done
  fail "Нет writable каталога для fio (/var/tmp, /tmp)"
}

preflight_checks() {
  local load1 load5 disk_pct swap_used
  pick_fio_paths

  load1=$(awk '{print $1}' /proc/loadavg 2>/dev/null || echo 0)
  load5=$(awk '{print $2}' /proc/loadavg 2>/dev/null || echo 0)
  if awk -v l="$load1" -v c="$CPU_CORES" 'BEGIN { exit !(l > c + 0.5) }' 2>/dev/null; then
    echo -e "${YELLOW}  ⚠ Нагрузка ${load1}/${load5}: CPU может быть ниже обычного${NC}"
  fi

  disk_pct=$(df -P "$FIO_DIR" 2>/dev/null | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
  if [ -n "${disk_pct:-}" ] && [ "$disk_pct" -gt 90 ] 2>/dev/null; then
    echo -e "${YELLOW}  ⚠ Диск заполнен на ${disk_pct}%: fio может врать${NC}"
  fi

  swap_used=$(awk '/SwapTotal/ {t=$2} /SwapFree/ {f=$2} END {if (t>0) print t-f; else print 0}' /proc/meminfo 2>/dev/null || echo 0)
  if [ "${swap_used:-0}" -gt 0 ] 2>/dev/null; then
    echo -e "${YELLOW}  ⚠ Идёт swap: замер может просесть${NC}"
  fi

  if path_is_tmpfs "$FIO_DIR"; then
    echo -e "${YELLOW}  ⚠ ${FIO_DIR} на tmpfs: диск меряем в RAM, не на storage${NC}"
  fi
}

drop_caches() {
  # Гостевой page cache. Кэш гипервизора этим не сбрасывается.
  if as_root sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches' 2>/dev/null; then
    return 0
  fi
  return 1
}

fio_seq_size() {
  # Больше файла — меньше шанс уложить весь seq в RAM/кэш хоста за прогон.
  local avail=0
  avail=$(df -Pm "$FIO_DIR" 2>/dev/null | awk 'NR==2 {print int($4)}')
  if [ "${avail:-0}" -ge 1536 ] 2>/dev/null; then
    echo "1024M"
  elif [ "${avail:-0}" -ge 768 ] 2>/dev/null; then
    echo "512M"
  else
    echo "256M"
  fi
}

cleanup_fio() {
  rm -f "${FIO_SEQ}" "${FIO_RAND}" 2>/dev/null || true
}

sysbench_cpu() {
  local threads="$1" time="$2"
  sysbench cpu --cpu-max-prime=20000 --threads="$threads" --time="$time" run 2>/dev/null \
    | awk '/events per second/ {print $4; exit}'
}

sysbench_ram() {
  local threads="$1"
  sysbench memory --threads="$threads" --memory-block-size=1M --memory-total-size=32G --time=8 run 2>/dev/null \
    | awk -F'[()]' '/transferred/ {
        n=$2; gsub(/[^0-9.]/, " ", n);
        split(n, a, " ");
        if (a[1] != "") { print a[1]; exit }
      }'
}

lan_mbps() {
  local iface name speed best=0
  for iface in /sys/class/net/*; do
    [ -d "$iface" ] || continue
    name=$(basename "$iface")
    case "$name" in
      lo|docker*|veth*|br-*|virbr*|tun*|tap*|wg*|cni*|flannel*) continue ;;
    esac
    if [ -r "$iface/speed" ]; then
      speed=$(cat "$iface/speed" 2>/dev/null || echo 0)
      if [ "$speed" -gt 0 ] 2>/dev/null && [ "$speed" -lt 200000 ] 2>/dev/null && [ "$speed" -gt "$best" ]; then
        best=$speed
      fi
    fi
  done
  echo "$best"
}

fio_mbps() {
  local rw="$1" bs="$2" name="$3" file="$4" size="${5:-256M}" mode="${6:-timed}"
  local extra=() args=()
  # Запись с fsync в конце — меньше «мгновенного» чтения из writeback-кэша хоста.
  if [ "$rw" = "write" ] || [ "$rw" = "randwrite" ]; then
    extra+=(--end_fsync=1)
  fi
  # Чтение: один проход по файлу. time_based на маленьком файле крутит его в кэше хоста.
  if [ "$mode" = "once" ]; then
    args=(--name="$name" --filename="$file" --size="$size" --ioengine=libaio
      --direct=1 --invalidate=1 --rw="$rw" --bs="$bs" --iodepth=32 --numjobs=1
      --group_reporting --output-format=json)
  else
    args=(--name="$name" --filename="$file" --size="$size" --ioengine=libaio
      --direct=1 --invalidate=1 --rw="$rw" --bs="$bs" --iodepth=32 --numjobs=1
      --runtime=8 --time_based --group_reporting --output-format=json)
  fi
  fio "${args[@]}" "${extra[@]}" 2>/dev/null \
    | python3 -c "import json,sys
rw=sys.argv[1]
try:
  d=json.load(sys.stdin)
  j=d['jobs'][0]
  side=j.get('read' if rw in ('read','randread') else 'write',{})
  bw=side.get('bw_bytes') or side.get('bw',0)*1024
  print(int(float(bw)/1048576))
except Exception:
  print(0)" "$rw"
}

fio_iops() {
  local rw="$1" name="$2" file="$3"
  fio --name="$name" --filename="$file" --size=128M --ioengine=libaio \
    --direct=1 --rw="$rw" --bs=4k --iodepth=64 --numjobs=1 --runtime=8 \
    --time_based --group_reporting --output-format=json 2>/dev/null \
    | python3 -c "import json,sys
rw=sys.argv[1]
try:
  d=json.load(sys.stdin)
  side=d['jobs'][0]['read' if rw == 'randread' else 'write']
  iops=side.get('iops',0)
  print(int(float(iops)))
except Exception:
  print(0)" "$rw"
}

ping_median() {
  local out
  if need_cmd ping; then
    out=$(ping -c 20 -W 1 1.1.1.1 2>/dev/null | awk -F'/' '/^(rtt|round-trip)/ {print $5; exit}')
    if [ -n "$out" ]; then
      echo "$out"
      return
    fi
  fi
  echo "0"
}

median_float() {
  python3 - "$@" <<'PY'
import sys

vals = []
for a in sys.argv[1:]:
    try:
        v = float(a)
    except ValueError:
        continue
    if v > 0:
        vals.append(v)
if not vals:
    print(0)
    raise SystemExit
vals.sort()
n = len(vals)
if n % 2:
    med = vals[n // 2]
else:
    med = (vals[n // 2 - 1] + vals[n // 2]) / 2
print(int(round(med)))
PY
}

# Для диска: 0 — валидный результат (сброс кэша / сбой), иначе медиана завышается одним «удачным» прогоном.
median_float_disk() {
  python3 - "$@" <<'PY'
import sys

vals = []
for a in sys.argv[1:]:
    try:
        v = float(a)
    except ValueError:
        continue
    if v >= 0:
        vals.append(v)
if not vals:
    print(0)
    raise SystemExit
vals.sort()
n = len(vals)
if n % 2:
    med = vals[n // 2]
else:
    med = (vals[n // 2 - 1] + vals[n // 2]) / 2
print(int(round(med)))
PY
}

run_median() {
  local label="$1"
  shift
  local i val vals=()
  for i in $(seq 1 "$RUNS"); do
    spin "${label} (${i}/${RUNS})"
    val=$("$@")
    vals+=("${val:-0}")
    if [ "$i" -lt "$RUNS" ]; then
      sleep 1
    fi
  done
  median_float "${vals[@]}"
}

disk_read_looks_cached() {
  local w="$1" r="$2"
  [ "${r:-0}" -gt 0 ] || return 1
  if [ "${w:-0}" -le 0 ] && [ "$r" -gt 6000 ]; then
    return 0
  fi
  if [ "${w:-0}" -gt 0 ] && [ "$r" -gt $((w * 3)) ]; then
    return 0
  fi
  return 1
}

disk_round() {
  local tag="$1"
  local w rr rw r size
  cleanup_fio
  size=$(fio_seq_size)
  # Сначала seq write→read (после fsync+drop), потом 4K — иначе rand греет кэш хоста перед чтением.
  w=$(fio_mbps write 1M "seqwrite_${tag}" "$FIO_SEQ" "$size" timed)
  if ! drop_caches; then
    echo -e "${YELLOW}  ⚠ Не удалось сбросить page cache (нужен root): чтение может быть завышено${NC}" >&2
  fi
  sleep 1
  r=$(fio_mbps read 1M "seqread_${tag}" "$FIO_SEQ" "$size" once)
  if disk_read_looks_cached "${w:-0}" "${r:-0}"; then
    echo -e "${YELLOW}  ⚠ Чтение ${r} МБ/с при записи ${w} МБ/с: похоже на кэш хоста, чтение = 0${NC}" >&2
    r=0
  fi
  rr=$(fio_iops randread "rand4k_r_${tag}" "$FIO_RAND")
  rw=$(fio_iops randwrite "rand4k_w_${tag}" "$FIO_RAND")
  cleanup_fio
  printf '%s %s %s %s' "${w:-0}" "${rr:-0}" "${rw:-0}" "${r:-0}"
}

run_disk_medians() {
  local i w rr rw r
  local -a seq_w=() seq_r=() iops_r=() iops_w=()
  for i in $(seq 1 "$RUNS"); do
    spin "Диск (${i}/${RUNS})"
    read -r w rr rw r <<< "$(disk_round "$i")"
    seq_w+=("${w:-0}")
    iops_r+=("${rr:-0}")
    iops_w+=("${rw:-0}")
    seq_r+=("${r:-0}")
    if [ "$i" -lt "$RUNS" ]; then
      sleep 2
    fi
  done
  SEQ_W=$(median_float_disk "${seq_w[@]}")
  IOPS4K_R=$(median_float_disk "${iops_r[@]}")
  IOPS4K_W=$(median_float_disk "${iops_w[@]}")
  SEQ_R=$(median_float_disk "${seq_r[@]}")
}

build_json() {
  python3 <<'PY'
import json, os

def f(key, default=0.0, cast=float):
    import re
    v = os.environ.get(key, "")
    if v == "":
        return default
    try:
        return cast(v)
    except Exception:
        m = re.findall(r"[0-9]+(?:\.[0-9]+)?", v)
        if not m:
            return default
        try:
            return cast(m[-1])
        except Exception:
            return default

payload = {
    "host": os.environ.get("RVH_HOST_SLUG", ""),
    "host_name": os.environ.get("RVH_HOST_NAME", ""),
    "duration_sec": int(f("RVH_DUR", 0, int)),
    "methodology": os.environ.get("RVH_METHODOLOGY", "1.9.1"),
    "metrics": {
        "cpu_single": f("RVH_CPU1"),
        "cpu_multi": f("RVH_CPUN"),
        "cpu_cores": int(f("RVH_CPU_CORES", 1, int)),
        "ram_mb": int(f("RVH_RAM_MB", 0, int)),
        "disk_seq_read_mbps": f("RVH_SEQ_R"),
        "disk_seq_write_mbps": f("RVH_SEQ_W"),
        "disk_rand4k_iops": f("RVH_IOPS_R"),
        "disk_rand4k_write_iops": f("RVH_IOPS_W"),
        "ping_ms": f("RVH_PING"),
        "ram_io_mbps": f("RVH_RAM_IO"),
        "lan_mbps": f("RVH_LAN"),
    },
    "token": os.environ.get("RVH_TOKEN", ""),
    "system": {
        "os": os.environ.get("RVH_OS", ""),
        "virt": os.environ.get("RVH_VIRT", ""),
        "country": os.environ.get("RVH_COUNTRY", ""),
        "location": os.environ.get("RVH_LOCATION", ""),
    },
}
print(json.dumps(payload, ensure_ascii=False))
PY
}

START=$(date +%s)
CPU_CORES=$(nproc 2>/dev/null || echo 1)
RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo 2>/dev/null || echo 0)
OS_INFO=$(get_os)
VIRT_INFO=$(get_virt | head -c 40)
COUNTRY=""
LOCATION=""
HOST_SLUG=""
HOST_NAME=""

clear 2>/dev/null || true
print_header
check_deps
preflight_checks

echo -e "${CYAN}Система${NC}"
echo -e "  ${DIM}ОС:${NC} $OS_INFO"
echo -e "  ${DIM}CPU:${NC} ${CPU_CORES} поток(ов) · RAM ${RAM_MB} MB"
echo -e "  ${DIM}Диск fio:${NC} ${FIO_DIR}"
get_country
if [ -n "$LOCATION" ]; then
  echo -e "  ${DIM}Локация (IP):${NC} $LOCATION"
fi
echo ""
pick_host
case "$HOST_SLUG" in
  adminvps) HOST_NAME="AdminVPS" ;;
  firstvds) HOST_NAME="FirstVDS" ;;
  aeza) HOST_NAME="Aeza" ;;
  timeweb) HOST_NAME="Timeweb Cloud" ;;
  vdsina) HOST_NAME="VDSina" ;;
  4vps) HOST_NAME="4VPS" ;;
  ruvds) HOST_NAME="RuVDS" ;;
  other) HOST_NAME="${HOST_NAME:-Другой}" ;;
  *) HOST_SLUG="other"; HOST_NAME="${HOST_NAME:-Другой}" ;;
esac
echo -e "  ${DIM}Хостер:${NC} ${HOST_NAME}"
echo ""

spin "CPU: разогрев"
sysbench_cpu 1 3 >/dev/null || true
CPU1=$(as_num "$(run_median "CPU 1 поток" sysbench_cpu 1 10)")
CPUN=$(as_num "$(run_median "CPU ${CPU_CORES}T" sysbench_cpu "$CPU_CORES" 10)")
printf "\r${GREEN}  ✓ CPU${NC}  1T: ${CPU1} evt/s · ${CPUN} evt/s (${CPU_CORES}T) · медиана ${RUNS}     \n"

RAM_IO=$(as_num "$(run_median "RAM IO" sysbench_ram "$CPU_CORES")")
printf "\r${GREEN}  ✓ RAM${NC}  ${RAM_IO} MiB/s · медиана ${RUNS}     \n"

run_disk_medians
printf "\r${GREEN}  ✓ Диск${NC}  запись ${SEQ_W:-0} · чтение ${SEQ_R:-0} МБ/с · 4K ${IOPS4K_R:-0}/${IOPS4K_W:-0} IOPS · медиана ${RUNS}     \n"

spin "Сеть: пинг 1.1.1.1"
PING=$(ping_median)
LAN=$(lan_mbps)
if [ "${LAN:-0}" != "0" ]; then
  printf "\r${GREEN}  ✓ Сеть${NC}  пинг ${PING:-0} мс · NIC ${LAN} Mbps     \n"
else
  printf "\r${GREEN}  ✓ Пинг${NC}  медиана ${PING:-0} мс     \n"
fi

END=$(date +%s)
DUR=$((END - START))
echo ""
echo -e "${DIM}  Время: ${DUR} сек · методология ${METHODOLOGY} · медиана ${RUNS} прогонов${NC}\n"

export RVH_DUR="$DUR" RVH_CPU1="${CPU1:-0}" RVH_CPUN="${CPUN:-0}" RVH_CPU_CORES="$CPU_CORES"
export RVH_RAM_MB="$RAM_MB" RVH_SEQ_R="${SEQ_R:-0}" RVH_SEQ_W="${SEQ_W:-0}"
export RVH_IOPS_R="${IOPS4K_R:-0}" RVH_IOPS_W="${IOPS4K_W:-0}" RVH_PING="${PING:-0}"
export RVH_RAM_IO="${RAM_IO:-0}" RVH_LAN="${LAN:-0}" RVH_OS="$OS_INFO" RVH_VIRT="$VIRT_INFO"
export RVH_COUNTRY="$COUNTRY" RVH_LOCATION="$LOCATION" RVH_HOST_SLUG="$HOST_SLUG"
export RVH_HOST_NAME="$HOST_NAME" RVH_TOKEN="$REPORT_TOKEN" RVH_METHODOLOGY="$METHODOLOGY"
if awk -v n="${CPU1:-0}" 'BEGIN { exit !(n+0 < 1) }'; then
  fail "CPU 1T не посчитался: отчёт не отправляем. Проверьте sysbench."
fi

JSON=$(build_json)

if [ -z "$JSON" ]; then
  fail "Не собрался JSON отчёта"
fi

echo -e "${CYAN}Отправка анонимного отчёта…${NC}"
TMP_RESP=$(mktemp /tmp/rvh_resp.XXXXXX 2>/dev/null || echo /tmp/rvh_resp.json)
HTTP=$(curl -sS --max-time 30 \
  -o "$TMP_RESP" \
  -w "%{http_code}" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${REPORT_TOKEN}" \
  --data-binary "$JSON" \
  "$REPORT_SERVER" 2>/tmp/rvh_curl_err) || HTTP="000"
RESP=$(cat "$TMP_RESP" 2>/dev/null || true)
rm -f "$TMP_RESP" 2>/dev/null || true

if [ "$HTTP" != "200" ] && [ "$HTTP" != "201" ]; then
  ERR=$(printf '%s' "$RESP" | python3 -c "import sys,json
try:
  d=json.load(sys.stdin)
  e=d.get('error','')
  f=d.get('field','')
  print((e + (':' + f if f else '')).strip(':'))
except Exception:
  print('')" 2>/dev/null)
  case "$HTTP-$ERR" in
    401-*|*-unauthorized) fail "Сервер отклонил доступ (401). Залейте свежие api/bench-report.php и .htaccess" ;;
    404-*) fail "API не найден (404). На сайте нет /api/bench-report.php" ;;
    429-*|*-rate_limit) fail "Лимит: слишком много отчётов с этого IP за час" ;;
    500-*|*-save_failed) fail "Сервер не записал отчёт (500). Проверьте базу на сервере" ;;
    422-*|*-invalid_metrics) fail "Сервер не принял цифры (422${ERR:+, $ERR}). CPU 1T должен быть > 0" ;;
    503-*|*-db_unavailable) fail "Нет связи с базой (503). Проверьте config.local.php на сервере" ;;
    000-*) fail "Нет связи с ${REPORT_SERVER}" ;;
    *) fail "Отчёт не ушёл (HTTP ${HTTP}${ERR:+, $ERR})" ;;
  esac
fi

URL=$(printf '%s' "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('url',''))" 2>/dev/null)

if [ -z "$URL" ]; then
  fail "Сервер не вернул ссылку"
fi

echo -e "${GREEN}✓ Отчёт готов${NC}"
echo -e "${CYAN}  ${URL}${NC}"
echo -e "${DIM}  Рейтинг на сайте от этого не зависит. Это ваш замер.${NC}\n"
