#!/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/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 (2–3 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 "$@"