Files
serv_benchmark/scripts/bench.sh
Gilles Soulier c67befc549 addon
2026-01-05 16:08:01 +01:00

1696 lines
61 KiB
Bash
Executable File
Raw Permalink 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.3.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.3.2"
# 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 (peuvent être surchargés par variables d'environnement)
SERVER_URL="${SERVER_URL:-10.0.0.50:8007}" # ajouter le port ou le schéma dans send_benchmark_payload si besoin
API_TOKEN="${API_TOKEN:-29855796dacf5cfe75ff9b02d6adf3dd0f9c52db5b53e7abfb4c0df7ece1be0a}"
IPERF_SERVER="${IPERF_SERVER:-10.0.0.50}"
# Mode DEBUG
# Mettre à 1 pour afficher le payload JSON complet avant envoi
# Mettre à 0 pour désactiver le debug
DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-0}" # Par défaut: 0 (désactivé)
# Peut être activé via: DEBUG_PAYLOAD=1 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="[]"
PARTITION_INFO="[]"
NETWORK_INFO="[]"
PCI_INFO="[]"
USB_INFO="[]"
NETWORK_SHARES_INFO="[]"
BENCHMARK_RESULTS="null"
SMARTCTL_CMD="sudo smartctl"
if command -v timeout &>/dev/null; then
SMARTCTL_CMD="timeout 10s sudo smartctl"
fi
#=========================================================
# 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 ""
# Pre-configure iperf3 to not install daemon
if printf '%s\n' "${to_install[@]}" | grep -q "^iperf3$"; then
echo "► Configuration iperf3 (désactivation du daemon)..."
echo "iperf3 iperf3/start_daemon boolean false" | sudo debconf-set-selections
fi
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 DEBIAN_FRONTEND=noninteractive 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"
}
bytes_to_gb() {
local bytes="$1"
if [[ -z "$bytes" || "$bytes" == "0" ]]; then
echo ""
else
safe_bc "scale=2; $bytes / (1024*1024*1024)"
fi
}
# Detect the current graphical session type (wayland/x11/tty)
detect_session_type() {
local session_type="${XDG_SESSION_TYPE:-}"
if [[ -z "$session_type" && -n "$USER" && $(command -v loginctl) ]]; then
local session_id
session_id=$(loginctl 2>/dev/null | awk -v user="$USER" '$3==user {print $1; exit}')
if [[ -n "$session_id" ]]; then
session_type=$(loginctl show-session "$session_id" -p Type 2>/dev/null | awk -F= '/Type=/ {print $2}')
fi
fi
if [[ -z "$session_type" && -n "$WAYLAND_DISPLAY" ]]; then
session_type="wayland"
elif [[ -z "$session_type" && -n "$DISPLAY" ]]; then
session_type="x11"
fi
echo "$session_type"
}
# Try to detect the primary screen resolution
detect_screen_resolution() {
local resolution=""
if command -v xrandr &>/dev/null && [[ -n "$DISPLAY" ]]; then
resolution=$(xrandr --current 2>/dev/null | awk '/\*/ {print $1; exit}')
fi
if [[ -z "$resolution" && -n "$DISPLAY" ]] && command -v xdpyinfo &>/dev/null; then
resolution=$(xdpyinfo 2>/dev/null | awk '/dimensions:/ {print $2; exit}')
fi
if [[ -z "$resolution" ]] && command -v swaymsg &>/dev/null; then
resolution=$(swaymsg -t get_outputs 2>/dev/null | jq -r '
[.[] | select(.active == true) | "\(.current_mode.width)x\(.current_mode.height)"] | .[0]'
)
[[ "$resolution" == "null" ]] && resolution=""
fi
echo "$resolution"
}
#=========================================================
# Étape 1 : Système
#=========================================================
collect_system_info() {
log_step "Collecte des informations système de base"
local hostname os_name os_version kernel arch
local session_type screen_resolution last_boot uptime_seconds
local battery_percentage="" battery_status="" battery_health=""
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)
session_type=$(detect_session_type)
screen_resolution=$(detect_screen_resolution)
if command -v who &>/dev/null; then
last_boot=$(who -b 2>/dev/null | awk '{print $3 " " $4}')
fi
if [[ -r /proc/uptime ]]; then
uptime_seconds=$(awk '{print int($1)}' /proc/uptime)
fi
for bat in /sys/class/power_supply/BAT*; do
[[ -d "$bat" ]] || continue
if [[ -f "$bat/present" ]]; then
local present
present=$(cat "$bat/present")
[[ "$present" != "1" ]] && continue
fi
if [[ -z "$battery_percentage" && -f "$bat/capacity" ]]; then
battery_percentage=$(cat "$bat/capacity" | tr -dc '0-9.')
fi
if [[ -z "$battery_status" && -f "$bat/status" ]]; then
battery_status=$(cat "$bat/status")
fi
if [[ -z "$battery_health" ]]; then
if [[ -f "$bat/health" ]]; then
battery_health=$(cat "$bat/health")
elif [[ -f "$bat/uevent" ]]; then
battery_health=$(grep -m1 'POWER_SUPPLY_HEALTH' "$bat/uevent" 2>/dev/null | cut -d= -f2)
fi
fi
break
done
SYSTEM_INFO=$(jq -n \
--arg hostname "$hostname" \
--arg os_name "$os_name" \
--arg os_version "$os_version" \
--arg kernel "$kernel" \
--arg arch "$arch" \
--arg session "$session_type" \
--arg resolution "$screen_resolution" \
--arg last_boot "$last_boot" \
--arg uptime "$uptime_seconds" \
--arg battery_pct "$battery_percentage" \
--arg battery_status "$battery_status" \
--arg battery_health "$battery_health" \
'{
hostname: $hostname,
os: {
name: $os_name,
version: $os_version,
kernel_version: $kernel,
architecture: $arch,
session_type: (if $session != "" then $session else null end),
display_server: (if $session != "" then $session else null end),
screen_resolution: (if $resolution != "" then $resolution else null end),
last_boot_time: (if $last_boot != "" then $last_boot else null end),
uptime_seconds: (if $uptime != "" then ($uptime | tonumber?) else null end),
battery_percentage: (if $battery_pct != "" then ($battery_pct | tonumber?) else null end),
battery_status: (if $battery_status != "" then $battery_status else null end),
battery_health: (if $battery_health != "" then $battery_health else null end)
}
}')
log_info "Hostname: $hostname"
log_info "OS: $os_name $os_version"
log_info "Kernel: $kernel"
[[ -n "$session_type" ]] && log_info "Session: $session_type"
[[ -n "$screen_resolution" ]] && log_info "Résolution écran: $screen_resolution"
[[ -n "$last_boot" ]] && log_info "Dernier boot: $last_boot"
if [[ -n "$battery_percentage" ]]; then
log_info "Batterie: ${battery_percentage}% ${battery_status:+($battery_status)}"
fi
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
# Vérifier que nvidia-smi fonctionne avant d'extraire les infos
if nvidia-smi &>/dev/null; then
nvidia_model=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 | tr -d '\n')
nvidia_vram=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d '\n')
nvidia_driver=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader 2>/dev/null | head -1 | tr -d '\n')
# Ne remplacer que si les valeurs sont non-vides et valides
if [[ -n "$nvidia_model" && ! "$nvidia_model" =~ (failed|error|Error) ]]; then
gpu_model="$nvidia_model"
fi
if [[ -n "$nvidia_vram" && "$nvidia_vram" =~ ^[0-9]+$ ]]; then
gpu_vram="$nvidia_vram"
fi
if [[ -n "$nvidia_driver" && ! "$nvidia_driver" =~ (failed|error|Error) ]]; then
gpu_driver="$nvidia_driver"
fi
fi
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 ""
collect_pci_devices
collect_usb_devices
}
collect_pci_devices() {
PCI_INFO="[]"
if ! command -v lspci &>/dev/null; then
log_warn "lspci non disponible - périphériques PCI ignorés"
return
fi
local count=0
while IFS= read -r line; do
[[ -z "$line" ]] && continue
local slot class vendor device
slot=$(echo "$line" | awk '{print $1}')
class=$(echo "$line" | cut -d'"' -f2)
vendor=$(echo "$line" | cut -d'"' -f4)
device=$(echo "$line" | cut -d'"' -f6)
[[ -z "$slot" ]] && continue
local entry
entry=$(jq -n \
--arg slot "$slot" \
--arg class "$class" \
--arg vendor "$vendor" \
--arg device "$device" \
'{slot: $slot, class: $class, vendor: $vendor, device: $device}')
PCI_INFO=$(echo "$PCI_INFO" | jq --argjson e "$entry" '. + [$e]')
count=$((count + 1))
done < <(lspci -mm 2>/dev/null || true)
log_info "Périphériques PCI détectés: $count"
}
collect_usb_devices() {
USB_INFO="[]"
if ! command -v lsusb &>/dev/null; then
log_warn "lsusb non disponible - périphériques USB ignorés"
return
fi
local count=0
while IFS= read -r line; do
[[ -z "$line" ]] && continue
if [[ "$line" =~ Bus[[:space:]]([0-9]+)[[:space:]]Device[[:space:]]([0-9]+):[[:space:]]ID[[:space:]]([0-9a-fA-F]+):([0-9a-fA-F]+)[[:space:]](.*)$ ]]; then
local bus="${BASH_REMATCH[1]}"
local dev="${BASH_REMATCH[2]}"
local vendor_id="${BASH_REMATCH[3]}"
local product_id="${BASH_REMATCH[4]}"
local name="${BASH_REMATCH[5]}"
local entry
entry=$(jq -n \
--arg bus "$bus" \
--arg device "$dev" \
--arg vendor_id "$vendor_id" \
--arg product_id "$product_id" \
--arg name "$name" \
'{bus: $bus, device: $device, vendor_id: $vendor_id, product_id: $product_id, name: $name}')
USB_INFO=$(echo "$USB_INFO" | jq --argjson e "$entry" '. + [$e]')
count=$((count + 1))
fi
done < <(lsusb 2>/dev/null || true)
log_info "Périphériques USB détectés: $count"
}
#=========================================================
# É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
log_info "→ SMART (/dev/$d): collecte en cours (timeout 10s)"
if $SMARTCTL_CMD -i "/dev/$d" &>/dev/null; then
local s
s=$($SMARTCTL_CMD -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=$($SMARTCTL_CMD -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=$($SMARTCTL_CMD -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"
local partitions_json="[]"
if command -v lsblk &>/dev/null; then
log_info "→ Récupération des partitions via lsblk"
local lsblk_payload=""
local lsblk_fields="NAME,TYPE,MOUNTPOINT,SIZE,FSTYPE,FSUSED,FSAVAIL"
local lsblk_cmd="lsblk -b -J -o"
if command -v timeout &>/dev/null; then
lsblk_cmd="timeout 10s lsblk -b -J -o"
fi
if ! lsblk_payload=$(bash -c "$lsblk_cmd \"$lsblk_fields\" 2>/dev/null"); then
log_warn "lsblk avec colonnes étendues indisponible, tentative en mode simplifié"
lsblk_fields="NAME,TYPE,MOUNTPOINT,SIZE,FSTYPE"
lsblk_payload=$(bash -c "$lsblk_cmd \"$lsblk_fields\" 2>/dev/null" || true)
fi
if [[ -n "$lsblk_payload" ]]; then
if ! partitions_json=$(echo "$lsblk_payload" | jq '
def flatten(node):
[node]
+ ( (node.children // []) | map(flatten(.)) | add );
(.blockdevices // [])
| map(flatten(.))
| add
| map(select(.type == "part"))
| map({
name: (if .name then "/dev/" + .name else null end),
mount_point: (if (.mountpoint // "") == "" then null else .mountpoint end),
fs_type: (.fstype // null),
total_gb: (if (.size? // null) then (((.size | tonumber) / (1024*1024*1024)) * 100 | round / 100) else null end),
used_gb: (if (.fsused? // null) then (((.fsused | tonumber) / (1024*1024*1024)) * 100 | round / 100) else null end),
free_gb: (if (.fsavail? // null) then (((.fsavail | tonumber) / (1024*1024*1024)) * 100 | round / 100) else null end)
})
' 2>/dev/null); then
log_warn "Parsing JSON lsblk échoué"
partitions_json="[]"
fi
[[ -z "$partitions_json" ]] && partitions_json="[]"
else
log_warn "Impossible d'obtenir la sortie lsblk (timeout ?)"
fi
fi
PARTITION_INFO="$partitions_json"
if [[ "$partitions_json" != "[]" ]]; then
local partition_count
partition_count=$(echo "$partitions_json" | jq 'length')
log_info "Partitions détectées: $partition_count"
fi
collect_network_shares
echo ""
}
collect_network_shares() {
NETWORK_SHARES_INFO="[]"
if [[ ! -r /proc/mounts ]]; then
log_warn "Impossible de lire /proc/mounts - partages réseau ignorés"
return
fi
local count=0
while read -r raw_src raw_target raw_type raw_opts _; do
local fstype
fstype=$(printf '%b' "$raw_type" | tr '[:upper:]' '[:lower:]')
case "$fstype" in
nfs|nfs4|cifs|smbfs|fuse.cifs|fuse.smbfs|afp|afpfs|fuse.afpfs|sshfs|fuse.sshfs|ftpfs|curlftpfs|fuse.ftpfs|davfs|davfs2|fuse.webdavfs)
;;
*)
continue
;;
esac
local protocol="$fstype"
case "$fstype" in
cifs|smbfs|fuse.cifs|fuse.smbfs) protocol="smb" ;;
nfs|nfs4) protocol="nfs" ;;
afp|afpfs|fuse.afpfs) protocol="afp" ;;
sshfs|fuse.sshfs) protocol="sshfs" ;;
ftpfs|curlftpfs|fuse.ftpfs) protocol="ftp" ;;
davfs|davfs2|fuse.webdavfs) protocol="webdav" ;;
esac
local source target options
source=$(printf '%b' "$raw_src")
target=$(printf '%b' "$raw_target")
options=$(printf '%b' "$raw_opts")
local total_gb="" used_gb="" free_gb=""
if [[ -d "$target" ]]; then
local df_line
df_line=$(df -B1 "$target" 2>/dev/null | tail -1)
if [[ -n "$df_line" ]]; then
local total_bytes used_bytes free_bytes
total_bytes=$(echo "$df_line" | awk '{print $2}')
used_bytes=$(echo "$df_line" | awk '{print $3}')
free_bytes=$(echo "$df_line" | awk '{print $4}')
[[ -n "$total_bytes" ]] && total_gb=$(bytes_to_gb "$total_bytes")
[[ -n "$used_bytes" ]] && used_gb=$(bytes_to_gb "$used_bytes")
[[ -n "$free_bytes" ]] && free_gb=$(bytes_to_gb "$free_bytes")
fi
fi
local entry
entry=$(jq -n \
--arg protocol "$protocol" \
--arg source "$source" \
--arg mount "$target" \
--arg fs "$fstype" \
--arg opts "$options" \
--arg total "${total_gb:-}" \
--arg used "${used_gb:-}" \
--arg free "${free_gb:-}" \
'{
protocol: ( $protocol | select(. != "") ),
source: $source,
mount_point: $mount,
fs_type: $fs,
options: ( $opts | select(. != "") ),
total_gb: ( if $total == "" then null else ($total | tonumber?) end ),
used_gb: ( if $used == "" then null else ($used | tonumber?) end ),
free_gb: ( if $free == "" then null else ($free | tonumber?) end )
}')
NETWORK_SHARES_INFO=$(echo "$NETWORK_SHARES_INFO" | jq --argjson e "$entry" '. + [$e]')
count=$((count + 1))
done < /proc/mounts
if (( count > 0 )); then
log_info "Partages réseau détectés: $count"
else
log_info "Aucun partage réseau monté"
fi
}
#=========================================================
# Étape 6 : Réseau
#=========================================================
collect_network_info() {
log_step "Collecte des informations réseau"
echo " → Début de collect_network_info()"
local network_array="[]"
local iface
echo " → Début de la boucle sur les interfaces réseau"
for iface in $(ip -br link show | awk '$2 ~ /UP|UNKNOWN/ {print $1}'); do
# Filtrer les interfaces virtuelles (loopback, docker, bridges, veth, tap, fw*, vmbr)
[[ "$iface" =~ ^(lo|docker|br-|veth|tap|fw|vmbr) ]] && 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="" driver="" ssid=""
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
if [[ "$type" = "wifi" ]] && command -v iw &>/dev/null; then
local iw_info
iw_info=$(iw dev "$iface" link 2>/dev/null || true)
if echo "$iw_info" | grep -q "SSID"; then
ssid=$(echo "$iw_info" | awk -F': ' '/SSID/ {print $2; exit}' | sed 's/[[:space:]]*$//')
fi
local bitrate_line
bitrate_line=$(echo "$iw_info" | awk -F': ' '/tx bitrate/ {print $2; exit}' | xargs)
if [[ -n "$bitrate_line" ]]; then
local br_value=$(echo "$bitrate_line" | awk '{print $1}')
local br_unit=$(echo "$bitrate_line" | awk '{print $2}')
if [[ "$br_value" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
if [[ "$br_unit" =~ ^MBit/s$ ]]; then
speed=$(printf "%.0f" "$br_value")
elif [[ "$br_unit" =~ ^GBit/s$ ]]; then
speed=$(awk "BEGIN {printf \"%.0f\", $br_value * 1000}")
fi
fi
fi
fi
if command -v ethtool &>/dev/null; then
local ethtool_info
ethtool_info=$(sudo ethtool -i "$iface" 2>/dev/null || true)
if [[ -n "$ethtool_info" ]]; then
driver=$(echo "$ethtool_info" | awk -F: '/driver:/ {gsub(/^[ \t]+/,"",$2); print $2; exit}')
fi
fi
if [[ -z "$driver" && -L "/sys/class/net/$iface/device/driver" ]]; then
driver=$(basename "$(readlink -f "/sys/class/net/$iface/device/driver")" 2>/dev/null)
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
# Debug: afficher les variables avant la création du JSON
echo " → DEBUG Interface: $iface"
echo " type='$type' mac='$mac' ip='${ip_addr:-}' speed='$speed' wol='$wol_json'"
local net_json jq_error
net_json=$(jq -n \
--arg name "$iface" \
--arg type "$type" \
--arg mac "$mac" \
--arg ip "${ip_addr:-}" \
--arg speed "$speed" \
--arg driver "$driver" \
--arg ssid "$ssid" \
--argjson wol "$wol_json" \
'{
name: $name,
type: $type,
mac: $mac,
ip_address: (if $ip != "" then $ip else null end),
speed_mbps: (if $speed != "" then ($speed | tonumber) else null end),
driver: (if $driver != "" then $driver else null end),
ssid: (if $ssid != "" then $ssid else null end),
wake_on_lan: $wol
}' 2>&1)
local jq_exit=$?
if [[ $jq_exit -ne 0 ]]; then
echo " ✗ jq ERREUR (code: $jq_exit): $net_json"
log_warn "Interface $iface: erreur jq, ignorée"
continue
fi
echo " net_json result: '$net_json'"
# Vérifier que net_json est valide avant de l'ajouter
if echo "$net_json" | jq empty 2>/dev/null; then
echo " ✓ JSON valide, ajout à network_array"
network_array=$(echo "$network_array" | jq --argjson net "$net_json" '. + [$net]')
else
echo " ✗ JSON invalide, interface ignorée"
log_warn "Interface $iface: JSON invalide, ignorée"
fi
local log_line="Interface: $iface ($type) - IP: ${ip_addr:-N/A}"
if [[ -n "$speed" ]]; then
log_line+=" - Speed: ${speed}Mbps"
fi
if [[ "$type" = "wifi" && -n "$ssid" ]]; then
log_line+=" - SSID: $ssid"
fi
log_info "$log_line"
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..."
# Test single-core (1 thread)
log_info "→ Test single-core (1 thread)..."
local cpu_single
cpu_single=$(sysbench cpu --cpu-max-prime=20000 --threads=1 run 2>&1 || true)
local eps_single
eps_single=$(echo "$cpu_single" | awk '/events per second/ {print $4}')
[[ -z "$eps_single" ]] && eps_single=0
local cpu_score_single
cpu_score_single=$(safe_bc "scale=2; $eps_single")
log_info " Single-core: ${eps_single} events/sec (score: ${cpu_score_single})"
# Test multi-core (tous les threads)
log_info "→ Test multi-core ($(nproc) threads)..."
local cpu_multi
cpu_multi=$(sysbench cpu --cpu-max-prime=20000 --threads="$(nproc)" run 2>&1 || true)
local eps_multi time_s
eps_multi=$(echo "$cpu_multi" | awk '/events per second/ {print $4}')
time_s=$(echo "$cpu_multi" | awk '/total time:/ {gsub(/s/,"",$3); print $3}')
[[ -z "$eps_multi" ]] && eps_multi=0
[[ -z "$time_s" ]] && time_s=0
local cpu_score_multi
cpu_score_multi=$(safe_bc "scale=2; $eps_multi")
log_info " Multi-core: ${eps_multi} events/sec (score: ${cpu_score_multi})"
# Score global = moyenne des deux
local cpu_score
cpu_score=$(safe_bc "scale=2; ($cpu_score_single + $cpu_score_multi) / 2")
cpu_bench=$(jq -n \
--arg eps "$eps_multi" \
--arg eps_single "$eps_single" \
--arg eps_multi "$eps_multi" \
--arg time "$time_s" \
--arg score "$cpu_score" \
--arg score_single "$cpu_score_single" \
--arg score_multi "$cpu_score_multi" \
'{
events_per_sec: ($eps | tonumber? // 0),
events_per_sec_single: ($eps_single | tonumber? // 0),
events_per_sec_multi: ($eps_multi | tonumber? // 0),
duration_s: ($time | tonumber? // 0),
score: ($score | tonumber? // 0),
score_single: ($score_single | tonumber? // 0),
score_multi: ($score_multi | tonumber? // 0)
}')
log_info "CPU Global: score ${cpu_score} (single: ${cpu_score_single}, multi: ${cpu_score_multi})"
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")
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")
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.0.50"
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" | 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 :
# CPU 40% (double du reste), RAM 20%, Disque 20%, Réseau 10%, 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.40"
total_weight=$(safe_bc "$total_weight + 0.40")
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.20"
total_weight=$(safe_bc "$total_weight + 0.20")
fi
if [[ "$net_bench" != "null" ]]; then
local ns
ns=$(echo "$net_bench" | jq '.score // 0')
scores="$scores + $ns * 0.10"
total_weight=$(safe_bc "$total_weight + 0.10")
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 n'a 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"
# Validation des variables JSON avant envoi (éviter les variables vides qui causent des erreurs jq)
# Si DEBUG_PAYLOAD=1, on affiche les variables pour diagnostic
if [[ "${DEBUG_PAYLOAD:-0}" == "1" ]]; then
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW}DEBUG: Validation des variables JSON${NC}"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
for var in SYSTEM_INFO CPU_INFO RAM_INFO GPU_INFO MOTHERBOARD_INFO STORAGE_INFO PARTITION_INFO NETWORK_INFO PCI_INFO USB_INFO NETWORK_SHARES_INFO BENCHMARK_RESULTS; do
local val="${!var}"
if [[ -z "$val" ]]; then
echo -e "${RED}$var est VIDE${NC}"
elif echo "$val" | jq empty 2>/dev/null; then
echo -e "${GREEN}$var est JSON valide${NC} (${#val} chars)"
else
echo -e "${RED}$var est JSON INVALIDE${NC}: ${val:0:100}..."
fi
done
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo ""
fi
[[ -z "$SYSTEM_INFO" ]] && SYSTEM_INFO="null"
[[ -z "$CPU_INFO" ]] && CPU_INFO="null"
[[ -z "$RAM_INFO" ]] && RAM_INFO="null"
[[ -z "$GPU_INFO" ]] && GPU_INFO="null"
[[ -z "$MOTHERBOARD_INFO" ]] && MOTHERBOARD_INFO="null"
[[ -z "$STORAGE_INFO" ]] && STORAGE_INFO="[]"
[[ -z "$PARTITION_INFO" ]] && PARTITION_INFO="[]"
[[ -z "$NETWORK_INFO" ]] && NETWORK_INFO="[]"
[[ -z "$PCI_INFO" ]] && PCI_INFO="[]"
[[ -z "$USB_INFO" ]] && USB_INFO="[]"
[[ -z "$NETWORK_SHARES_INFO" ]] && NETWORK_SHARES_INFO="[]"
[[ -z "$BENCHMARK_RESULTS" ]] && BENCHMARK_RESULTS="null"
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 partitions "$PARTITION_INFO" \
--argjson network "$NETWORK_INFO" \
--argjson pci "$PCI_INFO" \
--argjson usb "$USB_INFO" \
--argjson shares "$NETWORK_SHARES_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: $partitions
},
pci_devices: $pci,
usb_devices: $usb,
network_shares: $shares,
network: {
interfaces: [
$network[]
| {
name,
type,
mac,
ip: .ip_address,
speed_mbps,
driver: (.driver // null),
ssid: (.ssid // 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
+ {
hostname: $system.hostname,
virtualization_type: "none",
desktop_environment: (.session_type // .display_server // null)
}
),
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 ""
# Demander confirmation seulement si on a un terminal interactif
if [[ -t 0 ]]; then
read -p "Appuyez sur Entrée pour continuer l'envoi ou Ctrl+C pour annuler..."
else
log_warn "Mode non-interactif détecté - envoi automatique dans 2 secondes..."
sleep 2
fi
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
#=========================================================
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--server)
SERVER_URL="$2"
shift 2
;;
--token)
API_TOKEN="$2"
shift 2
;;
--iperf-server)
IPERF_SERVER="$2"
shift 2
;;
--debug)
DEBUG_PAYLOAD=1
shift
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --server URL Backend server URL (default: $SERVER_URL)"
echo " --token TOKEN API authentication token"
echo " --iperf-server IP iperf3 server IP (default: $IPERF_SERVER)"
echo " --debug Enable debug mode (shows full JSON payload)"
echo " --help, -h Show this help message"
echo ""
exit 0
;;
*)
log_warn "Option inconnue: $1 (ignorée)"
shift
;;
esac
done
}
main() {
parse_args "$@"
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 "$@"