Files
serv_benchmark/scripts/bench.sh
2025-12-14 10:40:54 +01:00

1170 lines
40 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# Linux BenchTools - Client Benchmark Script
# Version: 1.1.0
#
# Collecte les informations hardware, exécute les benchmarks
# puis envoie les résultats au serveur backend (payload JSON).
#
set -e
#=========================================================
# Version / variables globales
#=========================================================
BENCH_SCRIPT_VERSION="1.2.0"
# Couleurs
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
TOTAL_STEPS=8
CURRENT_STEP=0
# Paramètres réseau / API (fixés)
SERVER_URL="10.0.1.97:8007" # ajouter le port ou le schéma dans send_benchmark_payload si besoin
API_TOKEN="29855796dacf5cfe75ff9b02d6adf3dd0f9c52db5b53e7abfb4c0df7ece1be0a"
IPERF_SERVER="10.0.1.97"
# Mode DEBUG
# Mettre à 1 pour afficher le payload JSON complet avant envoi
# Mettre à 0 pour désactiver le debug
DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-1}" # Par défaut: 1 (activé)
# Peut être désactivé via: DEBUG_PAYLOAD=0 bash bench.sh
# Forcer locale en anglais pour parsing
export LC_ALL=C
# Ajouter /usr/sbin au PATH pour dmidecode, smartctl, ethtool
export PATH="/usr/sbin:/sbin:$PATH"
# Variables JSON globales
SYSTEM_INFO="null"
CPU_INFO="null"
RAM_INFO="null"
GPU_INFO="null"
MOTHERBOARD_INFO="null"
STORAGE_INFO="[]"
NETWORK_INFO="[]"
BENCHMARK_RESULTS="null"
#=========================================================
# Affichage / logs
#=========================================================
log_step() {
CURRENT_STEP=$((CURRENT_STEP + 1))
echo -e "${BLUE}[${CURRENT_STEP}/${TOTAL_STEPS}]${NC} $1"
}
log_info() {
echo -e " ${GREEN}${NC} $1"
}
log_warn() {
echo -e " ${YELLOW}${NC} $1"
}
log_error() {
echo -e " ${RED}${NC} $1"
}
#=========================================================
# sudo + dépendances
#=========================================================
check_sudo() {
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e " Linux BenchTools - Client Benchmark Script"
echo -e " Version ${BENCH_SCRIPT_VERSION}"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo ""
echo "Ce script nécessite les permissions sudo pour :"
echo " - dmidecode (infos RAM, carte mère, BIOS)"
echo " - smartctl (infos disques)"
echo " - ethtool (vitesse réseau)"
echo ""
if ! sudo -v; then
log_error "Permissions sudo requises. Relance le script avec sudo."
exit 1
fi
# Garder sudo actif
while true; do sudo -n true; sleep 50; kill -0 "$$" || exit; done 2>/dev/null &
log_info "Permissions sudo OK"
echo ""
}
check_dependencies() {
local missing=()
local bench_missing=()
echo -e "${BLUE}Vérification des dépendances...${NC}"
for tool in curl jq lscpu free lsblk ip bc smartctl; do
command -v "$tool" &>/dev/null || missing+=("$tool")
done
for tool in sysbench fio iperf3; do
command -v "$tool" &>/dev/null || bench_missing+=("$tool")
done
if [[ ${#missing[@]} -eq 0 && ${#bench_missing[@]} -eq 0 ]]; then
log_info "Toutes les dépendances de base sont présentes"
log_info "Vérification des dépendances terminée"
echo ""
return
fi
echo ""
if [[ ${#missing[@]} -gt 0 ]]; then
log_warn "Outils systèmes manquants: ${missing[*]}"
fi
if [[ ${#bench_missing[@]} -gt 0 ]]; then
log_warn "Outils de benchmark manquants: ${bench_missing[*]}"
fi
if ! command -v apt-get &>/dev/null; then
log_warn "apt-get non disponible, installation automatique impossible."
log_info "Vérification des dépendances terminée"
echo ""
return
fi
declare -A pkg_map=(
[curl]="curl"
[jq]="jq"
[lscpu]="util-linux"
[free]="procps"
[lsblk]="util-linux"
[ip]="iproute2"
[bc]="bc"
[smartctl]="smartmontools"
[sysbench]="sysbench"
[fio]="fio"
[iperf3]="iperf3"
)
local to_install=()
for t in "${missing[@]}" "${bench_missing[@]}"; do
[[ -n "${pkg_map[$t]}" ]] && to_install+=("${pkg_map[$t]}")
done
if [[ ${#to_install[@]} -gt 0 ]]; then
mapfile -t to_install < <(printf '%s\n' "${to_install[@]}" | sort -u)
echo -e "${BLUE}Installation automatique des paquets manquants...${NC}"
echo " Paquets: ${to_install[*]}"
echo ""
echo "► apt-get update..."
if ! sudo apt-get update -qq 2>&1 | grep -v "Policy will reject signature"; then
log_warn "Warning durant apt-get update (on continue)"
fi
echo "► apt-get install..."
if sudo apt-get install -y "${to_install[@]}"; then
log_info "Installation des dépendances OK"
else
log_error "Échec de l'installation des dépendances"
exit 1
fi
fi
log_info "Vérification des dépendances terminée"
echo ""
}
#=========================================================
# Utilitaire bc robuste
#=========================================================
safe_bc() {
local expr="$1"
local out
out=$(echo "$expr" | bc 2>/dev/null) || out="0"
echo "$out"
}
#=========================================================
# Étape 1 : Système
#=========================================================
collect_system_info() {
log_step "Collecte des informations système de base"
local hostname os_name os_version kernel arch
hostname=$(hostname)
os_name=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
os_version=$(grep '^VERSION=' /etc/os-release | cut -d= -f2 | tr -d '"')
kernel=$(uname -r)
arch=$(uname -m)
SYSTEM_INFO=$(jq -n \
--arg hostname "$hostname" \
--arg os_name "$os_name" \
--arg os_version "$os_version" \
--arg kernel "$kernel" \
--arg arch "$arch" \
'{
hostname: $hostname,
os: {
name: $os_name,
version: $os_version,
kernel_version: $kernel,
architecture: $arch
}
}')
log_info "Hostname: $hostname"
log_info "OS: $os_name $os_version"
log_info "Kernel: $kernel"
echo ""
}
#=========================================================
# Étape 2 : CPU
#=========================================================
collect_cpu_info() {
log_step "Collecte des informations CPU"
local vendor model cores threads
vendor=$(lscpu | awk -F: '/Vendor ID/ {gsub(/^[ \t]+/,"",$2); print $2}')
model=$(lscpu | awk -F: '/Model name/ {gsub(/^[ \t]+/,"",$2); print $2}')
# Calcul du nombre de cores physiques: Core(s) per socket × Socket(s)
local cores_per_socket sockets
cores_per_socket=$(lscpu | awk -F: '/Core\(s\) per socket/ {gsub(/^[ \t]+/,"",$2); gsub(/[^0-9]/,"",$2); print $2}')
sockets=$(lscpu | awk -F: '/Socket\(s\)/ {gsub(/^[ \t]+/,"",$2); gsub(/[^0-9]/,"",$2); print $2}')
# S'assurer que les valeurs sont des nombres valides
[[ -z "$cores_per_socket" || "$cores_per_socket" == "0" ]] && cores_per_socket=1
[[ -z "$sockets" || "$sockets" == "0" ]] && sockets=1
cores=$((cores_per_socket * sockets))
threads=$(nproc)
local cpu_mhz cpu_max_mhz base_freq_ghz max_freq_ghz
cpu_mhz=$(lscpu | awk -F: '/CPU MHz/ {gsub(/^[ \t]+/,"",$2); print $2}')
cpu_max_mhz=$(lscpu | awk -F: '/CPU max MHz/ {gsub(/^[ \t]+/,"",$2); print $2}')
base_freq_ghz="null"
max_freq_ghz="null"
if [[ -n "$cpu_mhz" ]]; then
base_freq_ghz=$(safe_bc "scale=2; $cpu_mhz / 1000")
fi
if [[ -n "$cpu_max_mhz" ]]; then
max_freq_ghz=$(safe_bc "scale=2; $cpu_max_mhz / 1000")
fi
local cache_l1_kb cache_l2_kb cache_l3_kb
# L1 cache = L1d + L1i
# Parser format: "384 KiB (12 instances)" ou "6 MiB (12 instances)"
# Extraire le premier nombre + unité, ignorer "(X instances)"
# Utilisation de sed pour extraire "nombre unité" puis awk pour convertir MiB en KB
local cache_l1d cache_l1i
cache_l1d=$(lscpu | grep 'L1d cache' | sed -n 's/.*:\s*\([0-9]\+\)\s*\(KiB\|MiB\).*/\1 \2/p' | awk '{if ($2 == "MiB") print $1*1024; else print $1}')
cache_l1i=$(lscpu | grep 'L1i cache' | sed -n 's/.*:\s*\([0-9]\+\)\s*\(KiB\|MiB\).*/\1 \2/p' | awk '{if ($2 == "MiB") print $1*1024; else print $1}')
cache_l1_kb=$((${cache_l1d:-0} + ${cache_l1i:-0}))
cache_l2_kb=$(lscpu | grep 'L2 cache' | sed -n 's/.*:\s*\([0-9]\+\)\s*\(KiB\|MiB\).*/\1 \2/p' | awk '{if ($2 == "MiB") print $1*1024; else print $1}')
cache_l3_kb=$(lscpu | grep 'L3 cache' | sed -n 's/.*:\s*\([0-9]\+\)\s*\(KiB\|MiB\).*/\1 \2/p' | awk '{if ($2 == "MiB") print $1*1024; else print $1}')
local flags
flags=$(lscpu | awk -F: '/Flags/ {gsub(/^[ \t]+/,"",$2); print $2}')
local flags_array
flags_array=$(printf '%s\n' $flags | jq -R . | jq -s .)
# Important: on passe les numériques en --arg (string) puis on cast dans jq
CPU_INFO=$(jq -n \
--arg vendor "$vendor" \
--arg model "$model" \
--arg cores "$cores" \
--arg threads "$threads" \
--arg base_freq "$base_freq_ghz" \
--arg max_freq "$max_freq_ghz" \
--arg l1 "$cache_l1_kb" \
--arg l2 "$cache_l2_kb" \
--arg l3 "$cache_l3_kb" \
--argjson flags "$flags_array" \
'{
vendor: $vendor,
model: $model,
cores: ($cores | tonumber? // 0),
threads: ($threads | tonumber? // 0),
base_freq_ghz: (if $base_freq == "null" or $base_freq == "" then null else ($base_freq | tonumber?) end),
max_freq_ghz: (if $max_freq == "null" or $max_freq == "" then null else ($max_freq | tonumber?) end),
cache_l1_kb: ($l1 | tonumber? // 0),
cache_l2_kb: ($l2 | tonumber? // 0),
cache_l3_kb: ($l3 | tonumber? // 0),
flags: $flags
}')
log_info "CPU: $model"
log_info "Cores: ${cores:-0}, Threads: ${threads:-0}"
echo ""
}
#=========================================================
# Étape 3 : RAM
#=========================================================
collect_ram_info() {
log_step "Collecte des informations RAM"
local mem_total_kb mem_used_kb mem_free_kb mem_shared_kb
mem_total_kb=$(free -k | awk '/^Mem:/ {print $2}')
mem_used_kb=$(free -k | awk '/^Mem:/ {print $3}')
mem_free_kb=$(free -k | awk '/^Mem:/ {print $4}')
mem_shared_kb=$(free -k | awk '/^Mem:/ {print $5}')
local total_mb used_mb free_mb shared_mb
total_mb=$((mem_total_kb / 1024))
used_mb=$((mem_used_kb / 1024))
free_mb=$((mem_free_kb / 1024))
shared_mb=$((mem_shared_kb / 1024))
local slots_total=0 slots_used=0 ecc=false
local dimm_layout="[]"
if command -v dmidecode &>/dev/null; then
log_info "[DEBUG] Vérification dmidecode..."
if sudo dmidecode -t 16 &>/dev/null; then
log_info "[DEBUG] dmidecode trouvé: $(command -v dmidecode)"
else
log_warn "dmidecode accessible mais aucune info DMI"
fi
local slots
slots=$(sudo dmidecode -t 16 2>/dev/null | awk -F: '/Number Of Devices/ {gsub(/^[ \t]+/,"",$2); print $2}' | head -1)
[[ -n "$slots" ]] && slots_total="$slots"
if sudo dmidecode -t memory 2>/dev/null | grep -q 'Error Correction Type'; then
if sudo dmidecode -t memory 2>/dev/null | grep 'Error Correction Type' | grep -q 'None'; then
ecc=false
else
ecc=true
fi
fi
local dimm_data
dimm_data=$(sudo dmidecode -t 17 | grep -E 'Locator:|Size:|Type:|Speed:|Manufacturer:' | \
awk '
/Locator:/ && !/Bank/ { slot=$2; gsub(/_/, "", slot) }
/Size:/ && /[0-9]+ [GM]B/ {
size=$2;
if ($3 == "GB") size=size*1024;
if ($3 == "MB") size=size;
}
/Type:/ && !/Detail/ { type=$2 }
/Speed:/ && /MHz/ { speed=$2 }
/Manufacturer:/ {
manu=$2;
printf "%s;%s;%s;%s;%s\n", slot, size, type, speed, manu
}')
if [[ -n "$dimm_data" ]]; then
while IFS=';' read -r slot size type speed manu; do
[[ -z "$slot" ]] && continue
slots_used=$((slots_used + 1))
local entry
entry=$(jq -n \
--arg slot "$slot" \
--arg size_mb "$size" \
--arg type "$type" \
--arg speed_mhz "$speed" \
--arg manu "$manu" \
'{
slot: $slot,
size_mb: ($size_mb | tonumber? // 0),
type: $type,
speed_mhz: ($speed_mhz | tonumber? // 0),
manufacturer: $manu
}')
dimm_layout=$(echo "$dimm_layout" | jq --argjson e "$entry" '. + [$e]')
done <<< "$dimm_data"
fi
else
log_warn "dmidecode non disponible - layout RAM détaillé ignoré"
fi
RAM_INFO=$(jq -n \
--arg total_mb "$total_mb" \
--arg used_mb "$used_mb" \
--arg free_mb "$free_mb" \
--arg shared_mb "$shared_mb" \
--arg slots_total "$slots_total" \
--arg slots_used "$slots_used" \
--arg ecc_str "$([[ "$ecc" = true ]] && echo true || echo false)" \
--argjson layout "$dimm_layout" \
'{
total_mb: ($total_mb | tonumber? // 0),
used_mb: ($used_mb | tonumber? // 0),
free_mb: ($free_mb | tonumber? // 0),
shared_mb: ($shared_mb | tonumber? // 0),
slots_total: ($slots_total | tonumber? // 0),
slots_used: ($slots_used | tonumber? // 0),
ecc: (if $ecc_str == "true" then true else false end),
layout: $layout
}')
log_info "RAM Total: ${total_mb}MB (Utilisée: ${used_mb}MB, Libre: ${free_mb}MB)"
log_info "Slots: ${slots_used}/${slots_total}"
echo ""
}
#=========================================================
# Étape 4 : GPU / Carte mère
#=========================================================
collect_hardware_info() {
log_step "Collecte GPU, Carte mère, BIOS"
local gpu_vendor="null"
local gpu_model="null"
local gpu_vram="null"
local gpu_driver="null"
if command -v lspci &>/dev/null; then
local gpu_line
gpu_line=$(lspci | grep -iE 'vga|3d' | head -1)
if [[ -n "$gpu_line" ]]; then
gpu_model=$(echo "$gpu_line" | sed 's/.*: //')
if echo "$gpu_line" | grep -qi 'nvidia'; then
gpu_vendor="NVIDIA"
# Essayer d'obtenir plus d'infos avec nvidia-smi
if command -v nvidia-smi &>/dev/null; then
local nvidia_model nvidia_vram nvidia_driver
nvidia_model=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1)
nvidia_vram=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1)
nvidia_driver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1)
[[ -n "$nvidia_model" ]] && gpu_model="$nvidia_model"
[[ -n "$nvidia_vram" ]] && gpu_vram="$nvidia_vram"
[[ -n "$nvidia_driver" ]] && gpu_driver="$nvidia_driver"
fi
elif echo "$gpu_line" | grep -qi 'amd'; then
gpu_vendor="AMD"
elif echo "$gpu_line" | grep -qi 'intel'; then
gpu_vendor="Intel"
else
gpu_vendor="Unknown"
fi
fi
fi
GPU_INFO=$(jq -n \
--arg vendor "$gpu_vendor" \
--arg model "$gpu_model" \
--arg vram "$gpu_vram" \
--arg driver "$gpu_driver" \
'{
vendor: $vendor,
model: $model,
memory_dedicated_mb: (if $vram == "null" or $vram == "" then null else ($vram | tonumber?) end),
driver_version: (if $driver == "null" or $driver == "" then null else $driver end)
}')
if [[ "$gpu_model" != "null" ]]; then
if [[ "$gpu_vram" != "null" ]]; then
log_info "GPU: $gpu_model (${gpu_vram}MB VRAM)"
else
log_info "GPU: $gpu_model"
fi
else
log_warn "GPU non détecté"
fi
local mb_manufacturer="Unknown" mb_model="Unknown"
local bios_vendor="Unknown" bios_version="Unknown" bios_date="Unknown"
if command -v dmidecode &>/dev/null; then
mb_manufacturer=$(sudo dmidecode -s baseboard-manufacturer 2>/dev/null || echo "Unknown")
mb_model=$(sudo dmidecode -s baseboard-product-name 2>/dev/null || echo "Unknown")
bios_vendor=$(sudo dmidecode -s bios-vendor 2>/dev/null || echo "Unknown")
bios_version=$(sudo dmidecode -s bios-version 2>/dev/null || echo "Unknown")
bios_date=$(sudo dmidecode -s bios-release-date 2>/dev/null || echo "Unknown")
fi
MOTHERBOARD_INFO=$(jq -n \
--arg manu "$mb_manufacturer" \
--arg model "$mb_model" \
--arg bvend "$bios_vendor" \
--arg bver "$bios_version" \
--arg bdate "$bios_date" \
'{
manufacturer: $manu,
model: $model,
bios_vendor: $bvend,
bios_version: $bver,
bios_date: $bdate
}')
log_info "Motherboard: $mb_manufacturer $mb_model"
log_info "BIOS: $bios_version ($bios_date)"
echo ""
}
#=========================================================
# Étape 5 : Stockage
#=========================================================
collect_storage_info() {
log_step "Collecte des informations de stockage"
local storage_array="[]"
local disks
disks=$(lsblk -d -n -o NAME,TYPE | awk '$2=="disk"{print $1}')
for d in $disks; do
local size_h size_gb rota tran
size_h=$(lsblk -d -n -o SIZE "/dev/$d")
rota=$(lsblk -d -n -o ROTA "/dev/$d")
tran=$(lsblk -d -n -o TRAN "/dev/$d")
local disk_type="unknown"
[[ "$rota" = "0" ]] && disk_type="ssd" || disk_type="hdd"
local interface="unknown"
[[ -n "$tran" ]] && interface="$tran"
local model="Unknown" serial="Unknown" temperature="null" smart_health="null"
if command -v smartctl &>/dev/null; then
if sudo smartctl -i "/dev/$d" &>/dev/null; then
local s
s=$(sudo smartctl -i "/dev/$d" 2>/dev/null)
model=$(echo "$s" | awk -F: '/Device Model|Model Number/ {gsub(/^[ \t]+/,"",$2); print $2}' | head -1)
serial=$(echo "$s" | awk -F: '/Serial Number/ {gsub(/^[ \t]+/,"",$2); print $2}' | head -1)
# Essayer de récupérer la température et le statut SMART
local smart_all
smart_all=$(sudo smartctl -A "/dev/$d" 2>/dev/null || true)
# Température (diverses variantes selon le type de disque)
# SATA/HDD: Temperature_Celsius, Airflow_Temperature_Cel, Current Drive Temperature
# RAW_VALUE est après le "-" (colonne 8 dans la plupart des cas, mais peut varier)
# On extrait tout après le "-" puis prend le premier nombre
# NVMe: Temperature: XX Celsius (colonne 2)
temperature=$(echo "$smart_all" | awk '/Temperature_Celsius|Airflow_Temperature_Cel|Current Drive Temperature/ {for(i=1;i<=NF;i++) if($i=="-" && i<NF) {print $(i+1); exit}}' | head -1 | grep -oE '^[0-9]+' | head -1)
[[ -z "$temperature" ]] && temperature=$(echo "$smart_all" | awk '/^Temperature:/ {print $2}' | head -1)
[[ -z "$temperature" ]] && temperature="null"
# Statut SMART health
local health
health=$(sudo smartctl -H "/dev/$d" 2>/dev/null | awk '/SMART overall-health|SMART Health Status/ {print $NF}' | head -1)
[[ -n "$health" ]] && smart_health="$health" || smart_health="null"
fi
fi
size_gb=$(echo "$size_h" | sed 's/[^0-9.]//g')
local disk_json
disk_json=$(jq -n \
--arg device "$d" \
--arg model "$model" \
--arg size "$size_gb" \
--arg type "$disk_type" \
--arg interface "$interface" \
--arg serial "$serial" \
--arg temp "$temperature" \
--arg health "$smart_health" \
'{
device: $device,
model: $model,
size_gb: $size,
type: $type,
interface: $interface,
serial: $serial,
temperature_c: (if $temp == "null" or $temp == "" then null else ($temp | tonumber?) end),
smart_health: (if $health == "null" or $health == "" then null else $health end)
}')
storage_array=$(echo "$storage_array" | jq --argjson disk "$disk_json" '. + [$disk]')
if [[ "$temperature" != "null" ]]; then
log_info "Disque: /dev/$d - $model ($size_h, $disk_type, ${temperature}°C)"
else
log_info "Disque: /dev/$d - $model ($size_h, $disk_type)"
fi
done
STORAGE_INFO="$storage_array"
echo ""
}
#=========================================================
# Étape 6 : Réseau
#=========================================================
collect_network_info() {
log_step "Collecte des informations réseau"
local network_array="[]"
local iface
for iface in $(ip -br link show | awk '$2 ~ /UP|UNKNOWN/ {print $1}'); do
[[ "$iface" =~ ^(lo|docker|br-|veth) ]] && continue
local mac
mac=$(ip link show "$iface" | awk '/link\/ether/ {print $2}')
[[ -z "$mac" ]] && continue
local type="unknown"
if [[ "$iface" =~ ^(eth|enp|eno) ]]; then
type="ethernet"
elif [[ "$iface" =~ ^(wlan|wl) ]]; then
type="wifi"
fi
local ip_addr
ip_addr=$(ip -4 addr show "$iface" | awk '/inet /{print $2}' | cut -d/ -f1 | head -1)
local speed="" wol_supported=""
if [[ "$type" = "ethernet" && -x /usr/sbin/ethtool ]]; then
local e
e=$(sudo ethtool "$iface" 2>/dev/null || true)
local spd
# Extraire seulement le nombre de la vitesse (enlever "Mb/s")
spd=$(echo "$e" | awk -F: '/Speed:/ {gsub(/^[ \t]+/,"",$2); print $2}' | grep -oE '[0-9]+' | head -1)
[[ -n "$spd" ]] && speed="$spd"
local wol
# Extraire Wake-on-LAN et nettoyer (enlever retours chariot)
wol=$(echo "$e" | awk -F: '/Wake-on:/ {gsub(/^[ \t]+/,"",$2); print $2}' | tr -d '\n' | head -1)
if [[ -n "$wol" && "$wol" != "d" ]]; then
wol_supported="true"
elif [[ -n "$wol" ]]; then
wol_supported="false"
fi
fi
# Convertir wol_supported en booléen ou null pour jq
local wol_json="null"
if [[ "$wol_supported" == "true" ]]; then
wol_json="true"
elif [[ "$wol_supported" == "false" ]]; then
wol_json="false"
fi
local net_json
net_json=$(jq -n \
--arg name "$iface" \
--arg type "$type" \
--arg mac "$mac" \
--arg ip "${ip_addr:-}" \
--arg speed "$speed" \
--argjson wol "$wol_json" \
'{
name: $name,
type: $type,
mac: $mac,
ip_address: ( $ip | select(. != "") ),
speed_mbps: ( ( $speed | tonumber? ) // null ),
wake_on_lan: $wol
}' 2>/dev/null || echo '{}')
# Vérifier que net_json est valide avant de l'ajouter
if [[ "$net_json" != "{}" ]] && echo "$net_json" | jq empty 2>/dev/null; then
network_array=$(echo "$network_array" | jq --argjson net "$net_json" '. + [$net]')
else
log_warn "Interface $iface: JSON invalide, ignorée"
fi
log_info "Interface: $iface ($type) - IP: ${ip_addr:-N/A}"
done
NETWORK_INFO="$network_array"
echo ""
}
#=========================================================
# Étape 7 : Benchmarks
#=========================================================
run_benchmarks() {
log_step "Exécution des benchmarks (peut prendre plusieurs minutes)"
# CPU
local cpu_bench="null"
if command -v sysbench &>/dev/null; then
log_info "Benchmark CPU en cours..."
local cpu_res
cpu_res=$(sysbench cpu --cpu-max-prime=20000 --threads="$(nproc)" run 2>&1 || true)
local eps time_s
eps=$(echo "$cpu_res" | awk '/events per second/ {print $4}')
time_s=$(echo "$cpu_res" | awk '/total time:/ {gsub(/s/,"",$3); print $3}')
[[ -z "$eps" ]] && eps=0
[[ -z "$time_s" ]] && time_s=0
local cpu_score
cpu_score=$(safe_bc "scale=2; $eps / 100")
cpu_bench=$(jq -n \
--arg eps "$eps" \
--arg time "$time_s" \
--arg score "$cpu_score" \
'{
events_per_sec: ($eps | tonumber? // 0),
duration_s: ($time | tonumber? // 0),
score: ($score | tonumber? // 0)
}')
log_info "CPU: ${eps} events/sec (score: ${cpu_score})"
else
log_warn "sysbench non disponible - CPU bench ignoré"
fi
# Mémoire
local mem_bench="null"
if command -v sysbench &>/dev/null; then
log_info "Benchmark Mémoire en cours..."
local mem_res
mem_res=$(sysbench memory --memory-total-size=1G run 2>&1 || true)
local thr
# Extraire le throughput - essayer plusieurs patterns
thr=$(echo "$mem_res" | grep -oP '\d+\.\d+(?= MiB/sec)' | head -1)
[[ -z "$thr" ]] && thr=$(echo "$mem_res" | awk '/transferred/ {print $(NF-1)}' | head -1)
[[ -z "$thr" ]] && thr=0
local mem_score
mem_score=$(safe_bc "scale=2; $thr / 100")
mem_bench=$(jq -n \
--arg thr "$thr" \
--arg score "$mem_score" \
'{
throughput_mib_s: ($thr | tonumber? // 0),
score: ($score | tonumber? // 0)
}')
log_info "Mémoire: ${thr} MiB/s (score: ${mem_score})"
else
log_warn "sysbench non disponible - Memory bench ignoré"
fi
# Disque
local disk_bench="null"
if command -v fio &>/dev/null; then
log_info "Benchmark Disque en cours (23 minutes)..."
local tmpdir
tmpdir=$(mktemp -d /tmp/fio-bench-XXXX)
local fio_res
fio_res=$(fio --name=bench --ioengine=libaio --rw=randrw --bs=4k \
--size=500M --numjobs=1 --direct=1 --runtime=30 --time_based \
--filename="$tmpdir/testfile" --output-format=json 2>/dev/null || echo '{}')
rm -rf "$tmpdir"
if [[ "$fio_res" != "{}" ]]; then
local rbw wbw riops wiops lat_ns
rbw=$(echo "$fio_res" | jq '.jobs[0].read.bw // 0')
wbw=$(echo "$fio_res" | jq '.jobs[0].write.bw // 0')
riops=$(echo "$fio_res" | jq '.jobs[0].read.iops // 0' | cut -d. -f1)
wiops=$(echo "$fio_res" | jq '.jobs[0].write.iops // 0' | cut -d. -f1)
lat_ns=$(echo "$fio_res" | jq '.jobs[0].read.clat_ns.mean // 0')
local rmb wmb lat_ms
rmb=$(safe_bc "scale=2; $rbw / 1024")
wmb=$(safe_bc "scale=2; $wbw / 1024")
lat_ms=$(safe_bc "scale=3; $lat_ns / 1000000")
local disk_score
disk_score=$(safe_bc "scale=2; ($rmb + $wmb) / 20")
disk_bench=$(jq -n \
--arg rmb "$rmb" \
--arg wmb "$wmb" \
--arg riops "$riops" \
--arg wiops "$wiops" \
--arg lat_ms "$lat_ms" \
--arg score "$disk_score" \
'{
read_mb_s: ($rmb | tonumber? // 0),
write_mb_s: ($wmb | tonumber? // 0),
iops_read: ($riops | tonumber? // 0),
iops_write: ($wiops | tonumber? // 0),
latency_ms: ($lat_ms | tonumber? // 0),
score: ($score | tonumber? // 0)
}')
log_info "Disque: R=${rmb}MB/s W=${wmb}MB/s (score: ${disk_score})"
else
log_warn "Échec du benchmark disque (fio)"
fi
else
log_warn "fio non disponible - Disk bench ignoré"
fi
# Réseau (iperf3)
local net_bench="null"
if command -v iperf3 &>/dev/null; then
local target="$IPERF_SERVER"
[[ -z "$target" ]] && target="10.0.1.97"
log_info "Benchmark Réseau en cours (vers $target)..."
if ping -c 1 -W 1 "$target" &>/dev/null; then
if nc -z "$target" 5201 &>/dev/null; then
# Test bidirectionnel (upload + download simultanés)
local bidir_result=$(iperf3 -c "$target" -t 10 --bidir -J 2>/dev/null || echo '{}')
local upload_mbps="0"
local download_mbps="0"
local ping_ms="null"
if [[ "$bidir_result" != "{}" ]]; then
# Extraire upload (end.sum_sent) et download (end.sum_received)
local upload_bps=$(echo "$bidir_result" | jq -r '.end.sum_sent.bits_per_second // 0' | tr -d '\n')
local download_bps=$(echo "$bidir_result" | jq -r '.end.sum_received.bits_per_second // 0' | tr -d '\n')
upload_mbps=$(safe_bc "scale=2; $upload_bps / 1000000" | tr -d '\n')
download_mbps=$(safe_bc "scale=2; $download_bps / 1000000" | tr -d '\n')
# Mesurer le ping
local ping_output=$(ping -c 5 "$target" 2>/dev/null | grep 'avg' || echo "")
if [[ -n "$ping_output" ]]; then
ping_ms=$(echo "$ping_output" | awk -F'/' '{print $5}' | tr -d '\n')
fi
# Score réseau
local net_score=$(safe_bc "scale=2; ($upload_mbps + $download_mbps) / 20" | tr -d '\n')
# S'assurer que les valeurs sont valides pour jq
[[ -z "$upload_mbps" || "$upload_mbps" == "null" ]] && upload_mbps="0"
[[ -z "$download_mbps" || "$download_mbps" == "null" ]] && download_mbps="0"
[[ -z "$ping_ms" || "$ping_ms" == "null" ]] && ping_ms="0"
[[ -z "$net_score" || "$net_score" == "null" ]] && net_score="0"
net_bench=$(jq -n \
--argjson upload "$upload_mbps" \
--argjson download "$download_mbps" \
--argjson ping "$ping_ms" \
--argjson score "$net_score" \
'{upload_mbps: $upload, download_mbps: $download, ping_ms: $ping, score: $score}')
log_info "Réseau: ↑${upload_mbps}Mbps ↓${download_mbps}Mbps (ping: ${ping_ms}ms, score: ${net_score})"
else
log_warn "Test iperf3 bidirectionnel échoué - Network bench ignoré"
fi
else
log_warn "Port iperf3 (5201) fermé sur $target - Network bench ignoré"
fi
else
log_warn "Hôte $target non joignable - Network bench ignoré"
fi
else
log_warn "iperf3 non disponible - Network bench ignoré"
fi
# GPU bench non implémenté
local gpu_bench="null"
log_warn "GPU bench non implémenté - ignoré"
# Score global selon pondérations recommandées :
# CPU 30%, RAM 20%, Disque 25%, Réseau 15%, GPU 10%
local scores="" total_weight=0
if [[ "$cpu_bench" != "null" ]]; then
local cs
cs=$(echo "$cpu_bench" | jq '.score // 0')
scores="$scores + $cs * 0.30"
total_weight=$(safe_bc "$total_weight + 0.30")
fi
if [[ "$mem_bench" != "null" ]]; then
local ms
ms=$(echo "$mem_bench" | jq '.score // 0')
scores="$scores + $ms * 0.20"
total_weight=$(safe_bc "$total_weight + 0.20")
fi
if [[ "$disk_bench" != "null" ]]; then
local ds
ds=$(echo "$disk_bench" | jq '.score // 0')
scores="$scores + $ds * 0.25"
total_weight=$(safe_bc "$total_weight + 0.25")
fi
if [[ "$net_bench" != "null" ]]; then
local ns
ns=$(echo "$net_bench" | jq '.score // 0')
scores="$scores + $ns * 0.15"
total_weight=$(safe_bc "$total_weight + 0.15")
fi
if [[ "$gpu_bench" != "null" ]]; then
local gs
gs=$(echo "$gpu_bench" | jq '.score // 0')
scores="$scores + $gs * 0.10"
total_weight=$(safe_bc "$total_weight + 0.10")
fi
scores=$(echo "$scores" | sed -E 's/^[[:space:]]*\+ //')
local global_score=0
if [[ -n "$scores" && "$total_weight" != "0" ]]; then
global_score=$(safe_bc "scale=2; ($scores) / $total_weight")
fi
[[ -z "$global_score" ]] && global_score=0
BENCHMARK_RESULTS=$(jq -n \
--argjson cpu "$cpu_bench" \
--argjson mem "$mem_bench" \
--argjson disk "$disk_bench" \
--argjson net "$net_bench" \
--argjson gpu "$gpu_bench" \
--argjson global "$global_score" \
'{
cpu: $cpu,
memory: $mem,
disk: $disk,
network: $net,
gpu: $gpu,
global_score: $global
}')
log_info "Score global: ${global_score}/100"
echo ""
}
#=========================================================
# Envoi du payload JSON
#=========================================================
send_benchmark_payload() {
log_step "Construction du payload JSON et envoi au serveur"
# Si SERVER_URL na pas de schéma, on préfixe par http://
local base_url="$SERVER_URL"
if [[ "$base_url" != http*://* ]]; then
base_url="http://$base_url"
fi
local api_url="${base_url%/}/api/benchmark"
local payload
payload=$(
jq -n \
--arg version "$BENCH_SCRIPT_VERSION" \
--argjson system "$SYSTEM_INFO" \
--argjson cpu "$CPU_INFO" \
--argjson ram "$RAM_INFO" \
--argjson gpu "$GPU_INFO" \
--argjson mb "$MOTHERBOARD_INFO" \
--argjson storage "$STORAGE_INFO" \
--argjson network "$NETWORK_INFO" \
--argjson bench "$BENCHMARK_RESULTS" \
'
{
device_identifier: $system.hostname,
bench_script_version: $version,
hardware: {
cpu: ($cpu + {
microarchitecture: null,
tdp_w: null
}),
ram: (
$ram
| {
total_mb: .total_mb,
used_mb: .used_mb,
free_mb: .free_mb,
shared_mb: .shared_mb,
slots_total: .slots_total,
slots_used: .slots_used,
ecc: .ecc,
layout: [
.layout[]
| {
slot,
size_mb,
type,
speed_mhz,
vendor: .manufacturer,
part_number: null
}
]
}
),
gpu: (
if $gpu == null then
{
vendor: null,
model: null,
driver_version: null,
memory_dedicated_mb: null,
memory_shared_mb: null,
api_support: []
}
else
{
vendor: $gpu.vendor,
model: $gpu.model,
driver_version: null,
memory_dedicated_mb: null,
memory_shared_mb: null,
api_support: []
}
end
),
storage: {
devices: [
$storage[]
| {
name: ("/dev/" + .device),
type: (.type | ascii_upcase),
interface,
capacity_gb: (.size_gb | tonumber? // .size_gb),
vendor: null,
model,
smart_health,
temperature_c
}
],
partitions: []
},
network: {
interfaces: [
$network[]
| {
name,
type,
mac,
ip: .ip_address,
speed_mbps,
driver: null,
wake_on_lan
}
]
},
motherboard: {
vendor: $mb.manufacturer,
model: $mb.model,
bios_vendor: $mb.bios_vendor,
bios_version: $mb.bios_version,
bios_date: $mb.bios_date
},
os: (
$system.os
+ { virtualization_type: "none" }
),
sensors: {
cpu_temp_c: null,
disk_temps_c: {}
},
raw_info: {
lscpu: null,
lsblk: null
}
},
results: (
$bench as $b
| {
cpu: $b.cpu,
memory: $b.memory,
disk: $b.disk,
network: (
$b.network
+ {
jitter_ms: null,
packet_loss_percent: null
}
),
gpu: (
if $b.gpu == null then
{ glmark2_score: null, score: null }
else
$b.gpu
end
),
global_score: $b.global_score
}
)
}
'
)
# Debug : afficher le payload complet si DEBUG_PAYLOAD=1
if [[ "${DEBUG_PAYLOAD:-0}" == "1" ]]; then
echo ""
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW}DEBUG: Payload JSON complet${NC}"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo "$payload" | jq '.'
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo ""
# Sauvegarder le payload dans un fichier avec timestamp
local debug_file="/tmp/bench_payload_$(date +%Y%m%d_%H%M%S).json"
echo "$payload" | jq '.' > "$debug_file"
echo -e "${GREEN}${NC} Payload sauvegardé dans: ${debug_file}"
echo ""
read -p "Appuyez sur Entrée pour continuer l'envoi ou Ctrl+C pour annuler..."
fi
log_info "Envoi du payload vers: $api_url"
local tmp_resp http_code
tmp_resp=$(mktemp /tmp/bench_response_XXXXXX.json)
# IMPORTANT : on envoie le payload via stdin (pas de @<(...) foireux)
http_code=$(
printf '%s\n' "$payload" | curl -sS -o "$tmp_resp" -w "%{http_code}" \
-X POST "$api_url" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_TOKEN" \
--data-binary @-
)
if [[ "$http_code" != "200" && "$http_code" != "201" ]]; then
log_error "Erreur lors de l'envoi (HTTP $http_code)"
echo "Réponse serveur :"
cat "$tmp_resp"
rm -f "$tmp_resp"
exit 1
fi
log_info "Payload envoyé avec succès (HTTP $http_code)"
rm -f "$tmp_resp"
echo ""
}
#=========================================================
# MAIN
#=========================================================
main() {
check_sudo
check_dependencies
echo -e "${BLUE}Début de la collecte et des benchmarks...${NC}"
echo ""
collect_system_info
collect_cpu_info
collect_ram_info
collect_hardware_info
collect_storage_info
collect_network_info
run_benchmarks
send_benchmark_payload
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Benchmark terminé avec succès !${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
echo ""
}
main "$@"