This commit is contained in:
2025-12-08 05:42:52 +01:00
parent 80d8b7aa87
commit 5d483b0df5
32 changed files with 9837 additions and 579 deletions

767
ANALYSE_DONNEES.md Normal file
View File

@@ -0,0 +1,767 @@
# Analyse des Données de Benchmarking
Ce document identifie toutes les données intéressantes à collecter basées sur les résultats réels des benchmarks.
---
## 📊 1. BENCHMARKS - Données à récupérer
### 1.1 CPU (sysbench)
**Commande testée** :
```bash
sysbench cpu --cpu-max-prime=10000 --threads=$(nproc) run
```
**Output analysé** :
```
CPU speed:
events per second: 3409.87
General statistics:
total time: 10.0005s
total number of events: 34107
Latency (ms):
min: 1.10
avg: 1.17
max: 15.92
95th percentile: 1.23
```
**✅ Données IMPORTANTES à extraire** :
- `events_per_second` : **3409.87** (métrique principale pour score)
- `total_time` : 10.0005s (durée du test)
- `avg_latency_ms` : 1.17 (latence moyenne)
**❌ Données OPTIONNELLES** (peuvent être ignorées pour l'instant) :
- min/max latency
- 95th percentile
- threads fairness
**Parsing recommandé** :
```bash
events_per_sec=$(sysbench cpu ... | grep 'events per second' | awk '{print $4}')
```
---
### 1.2 MEMORY (sysbench)
**Commande testée** :
```bash
sysbench memory --memory-block-size=1M --memory-total-size=512M run
```
**Output analysé** :
```
512.00 MiB transferred (13806.03 MiB/sec)
General statistics:
total time: 0.0351s
```
**✅ Données IMPORTANTES à extraire** :
- `throughput_mib_s` : **13806.03** MiB/sec (métrique principale)
- `total_time` : 0.0351s
**Parsing recommandé** :
```bash
throughput=$(sysbench memory ... | grep 'MiB/sec' | grep -oP '\d+\.\d+ MiB/sec' | awk '{print $1}')
```
---
### 1.3 DISK (fio) - Format JSON
**Commande testée** :
```bash
fio --name=test --ioengine=libaio --rw=randrw --bs=4k --size=100M \
--numjobs=1 --direct=1 --filename=/tmp/fio-test-file --output-format=json
```
**Output JSON (extrait)** :
```json
{
"read": {
"bw_bytes": 728177777,
"bw": 711111,
"iops": 177777.777778,
"runtime": 72
},
"write": {
"bw_bytes": 728177777,
"bw": 711111,
"iops": 177777.777778
}
}
```
**✅ Données IMPORTANTES à extraire** :
- `read.bw` : 711111 KiB/s → convertir en MB/s : **711 MB/s**
- `write.bw` : 711111 KiB/s → convertir en MB/s : **711 MB/s**
- `read.iops` : **177777** (operations/sec)
- `write.iops` : **177777** (operations/sec)
- `read.clat_ns.mean` : latence moyenne en nanosecondes
**Parsing recommandé (avec jq)** :
```bash
fio_output=$(fio ... --output-format=json)
read_mb_s=$(echo "$fio_output" | jq '.jobs[0].read.bw / 1024')
write_mb_s=$(echo "$fio_output" | jq '.jobs[0].write.bw / 1024')
iops_read=$(echo "$fio_output" | jq '.jobs[0].read.iops')
iops_write=$(echo "$fio_output" | jq '.jobs[0].write.iops')
latency_ns=$(echo "$fio_output" | jq '.jobs[0].read.clat_ns.mean')
latency_ms=$(echo "scale=3; $latency_ns / 1000000" | bc)
```
---
### 1.4 NETWORK (iperf3) - Format JSON
**Commande testée** :
```bash
iperf3 -c 10.0.1.97 -t 5 -J
```
**Output JSON (extrait)** :
```json
{
"end": {
"sum_sent": {
"bits_per_second": 327057987.07346243,
"retransmits": 102
},
"sum_received": {
"bits_per_second": 323523050.66083658
}
}
}
```
**✅ Données IMPORTANTES à extraire** :
- `upload_mbps` : 327057987 / 1000000 = **327 Mbps** (bits_per_second sender)
- `download_mbps` : **323 Mbps** (pour test en reverse -R)
- `retransmits` : 102 (nombre de retransmissions)
- `mean_rtt` : 53143 µs (ping moyen)
**Parsing recommandé (avec jq)** :
```bash
upload_bits=$(echo "$iperf_json" | jq '.end.sum_sent.bits_per_second')
upload_mbps=$(echo "scale=2; $upload_bits / 1000000" | bc)
rtt_us=$(echo "$iperf_json" | jq '.end.streams[0].sender.mean_rtt')
ping_ms=$(echo "scale=2; $rtt_us / 1000" | bc)
```
---
## 🖥️ 2. HARDWARE - Données à récupérer
### 2.1 CPU (lscpu + dmidecode)
**Commande testée** : `lscpu` (output en français sur ta machine)
**Output analysé** :
```
Identifiant constructeur : GenuineIntel
Nom de modèle : Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz
Processeur(s) : 4
Thread(s) par cœur : 1
Cœur(s) par socket : 4
Socket(s) : 1
Vitesse maximale du processeur en MHz : 3400,0000
Vitesse minimale du processeur en MHz : 1600,0000
Caches (somme de toutes) :
L1d : 128 KiB (4 instances)
L1i : 128 KiB (4 instances)
L2 : 1 MiB (4 instances)
L3 : 6 MiB (1 instance)
Drapeaux : fpu vme de pse tsc msr ... avx sse4_1 sse4_2 ...
```
**✅ Données IMPORTANTES à extraire** :
- `vendor` : **GenuineIntel**
- `model` : **Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz**
- `microarchitecture` : Peut être déduit du model (Sandy Bridge pour i5-2400)
- `cores` : **4** (cœurs physiques)
- `threads` : **4** (4 cœurs × 1 thread = 4)
- `base_freq_ghz` : **3.1** GHz (du nom du modèle)
- `max_freq_ghz` : **3.4** GHz
- `cache_l1_kb` : 128 + 128 = **256 KB** (L1d + L1i)
- `cache_l2_kb` : 1024 KB = **1024 KB**
- `cache_l3_kb` : 6144 KB = **6144 KB**
- `flags` : ["avx", "sse4_1", "sse4_2", "aes", "pclmulqdq", ...] (important pour virtualisation)
**⚠️ PROBLÈME DÉTECTÉ** :
```bash
echo "Cores: $(lscpu | grep '^CPU(s):' | awk '{print $2}')"
# Retourne vide car en français : "Processeur(s) :"
```
**✅ SOLUTION - Forcer locale en anglais** :
```bash
LC_ALL=C lscpu
# Ou utiliser lscpu -J pour JSON et parser avec jq
```
**Parsing recommandé** :
```bash
export LC_ALL=C
vendor=$(lscpu | grep 'Vendor ID' | awk '{print $3}')
model=$(lscpu | grep 'Model name' | sed 's/Model name: *//' | xargs)
cores=$(lscpu | grep '^CPU(s):' | awk '{print $2}')
threads=$(nproc)
max_freq=$(lscpu | grep 'CPU max MHz' | awk '{print $4}')
max_freq_ghz=$(echo "scale=2; $max_freq / 1000" | bc)
# Caches
cache_l1d=$(lscpu | grep 'L1d cache' | awk '{print $3}' | sed 's/K//')
cache_l1i=$(lscpu | grep 'L1i cache' | awk '{print $3}' | sed 's/K//')
cache_l2=$(lscpu | grep 'L2 cache' | awk '{print $3}' | sed 's/[MK]//')
cache_l3=$(lscpu | grep 'L3 cache' | awk '{print $3}' | sed 's/[MK]//')
# Flags (liste complète)
flags=$(lscpu | grep 'Flags:' | sed 's/Flags: *//')
```
**❌ Données MANQUANTES actuellement** :
- `tdp_w` : Non disponible via lscpu, peut être récupéré via dmidecode ou base de données constructeur
---
### 2.2 RAM (free + dmidecode)
**Commande testée** : `free -m` + `sudo dmidecode -t memory`
**Output free -m** :
```
total utilisé libre partagé tamp/cache disponible
Mem: 7771 5847 1479 678 1410 1923
```
**Output dmidecode -t memory** (extrait) :
```
Physical Memory Array
Error Correction Type: None
Maximum Capacity: 32 GB
Number Of Devices: 4
Memory Device
Size: 2 GB
Form Factor: DIMM
Locator: A1_DIMM0
Type: DDR3
Speed: 1333 MT/s
Manufacturer: Samsung
Part Number: M378B5773DH0-CH9
Rank: 1
```
**✅ Données IMPORTANTES à extraire** :
- `total_mb` : **7771 MB** (du free -m)
- `slots_total` : **4** (Number Of Devices)
- `slots_used` : **4** (compter les Memory Device avec Size > 0)
- `ecc` : **false** (Error Correction Type: None)
- `layout` : Array de:
- `slot` : "A1_DIMM0", "A1_DIMM1", "A1_DIMM2", "A1_DIMM3"
- `size_mb` : 2048 (2 GB)
- `type` : "DDR3"
- `speed_mhz` : 1333
- `manufacturer` : "Samsung" ou "Kingston"
- `part_number` : "M378B5773DH0-CH9" ou "ACR256X64D3U1333C9"
**Parsing recommandé** :
```bash
# Total RAM
total_mb=$(free -m | grep '^Mem:' | awk '{print $2}')
# Avec dmidecode (nécessite sudo)
if command -v dmidecode &> /dev/null && [ $(id -u) -eq 0 ]; then
ecc=$(sudo dmidecode -t 16 | grep 'Error Correction Type' | grep -q 'None' && echo "false" || echo "true")
slots_total=$(sudo dmidecode -t 16 | grep 'Number Of Devices' | awk '{print $4}')
# Parser chaque DIMM (complexe, nécessite parsing multi-lignes)
# Voir script complet ci-dessous
fi
```
---
### 2.3 GPU (lspci + nvidia-smi)
**Commande testée** : `lspci | grep -i vga`
**Output analysé** :
```
00:02.0 VGA compatible controller: Intel Corporation 2nd Generation Core Processor Family Integrated Graphics Controller (rev 09)
```
**✅ Données IMPORTANTES à extraire** :
- `vendor` : **Intel Corporation**
- `model` : **2nd Generation Core Processor Family Integrated Graphics Controller**
- `pci_id` : **00:02.0**
**Pour GPU NVIDIA** (nvidia-smi) :
```bash
nvidia-smi --query-gpu=name,memory.total,driver_version,vbios_version --format=csv,noheader
# Output: GeForce RTX 3070, 8192 MiB, 525.105.17, 94.02.42.00.35
```
**Parsing recommandé** :
```bash
gpu_info=$(lspci | grep -i vga | head -1)
if echo "$gpu_info" | grep -qi 'nvidia'; then
gpu_vendor="NVIDIA"
gpu_model=$(nvidia-smi --query-gpu=name --format=csv,noheader | head -1)
gpu_vram=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1)
elif echo "$gpu_info" | grep -qi 'intel'; then
gpu_vendor="Intel"
gpu_model=$(echo "$gpu_info" | sed 's/.*: //' | sed 's/ (rev.*//')
gpu_vram=null
else
gpu_vendor=$(echo "$gpu_info" | awk -F: '{print $3}' | awk '{print $1, $2}')
gpu_model=$(echo "$gpu_info" | sed 's/.*: //')
fi
```
---
### 2.4 MOTHERBOARD (dmidecode)
**Commande testée** : `sudo dmidecode -t baseboard`
**Output analysé** :
```
Base Board Information
Manufacturer: LENOVO
Product Name:
Version:
Serial Number: INVALID
```
**⚠️ PROBLÈME** : Product Name est vide sur ta machine Lenovo (OEM)
**✅ Données IMPORTANTES à extraire** :
- `manufacturer` : **LENOVO**
- `model` : **(vide sur certaines machines)**
- `version` : (vide)
**Alternative - dmidecode -t 1 (System Information)** :
```bash
sudo dmidecode -t 1 | grep -E 'Manufacturer|Product Name|Version'
```
**Parsing recommandé** :
```bash
if command -v dmidecode &> /dev/null && [ $(id -u) -eq 0 ]; then
mb_manufacturer=$(sudo dmidecode -t 2 | grep 'Manufacturer:' | sed 's/.*: *//' | xargs)
mb_model=$(sudo dmidecode -t 2 | grep 'Product Name:' | sed 's/.*: *//' | xargs)
[ -z "$mb_model" ] && mb_model="Unknown"
fi
```
**❌ Données MANQUANTES actuellement** :
- `bios_version` : Disponible via `dmidecode -t 0`
- `bios_date` : Disponible via `dmidecode -t 0`
---
### 2.5 STORAGE (lsblk + smartctl)
**Commande testée** : `lsblk -J` + `sudo smartctl -i /dev/sda`
**Output lsblk -J** (extrait) :
```json
{
"blockdevices": [
{
"name": "sda",
"size": "447,1G",
"type": "disk",
"children": [
{"name": "sda1", "size": "7,4G", "type": "part", "mountpoints": ["[SWAP]"]},
{"name": "sda5", "size": "439,7G", "type": "part", "mountpoints": ["/"]}
]
}
]
}
```
**Output smartctl** :
```
Device Model: KINGSTON SA400S37480G
Serial Number: 50026B77833E25E3
User Capacity: 480 103 981 056 bytes [480 GB]
Rotation Rate: Solid State Device
SATA Version is: SATA 3.2, 6.0 Gb/s (current: 6.0 Gb/s)
```
**✅ Données IMPORTANTES à extraire** :
- `device` : **sda**
- `model` : **KINGSTON SA400S37480G**
- `size_gb` : **480** GB
- `type` : **ssd** (ou hdd si Rotation Rate ≠ SSD)
- `interface` : **sata** (ou nvme, usb)
- `serial` : **50026B77833E25E3**
**Parsing recommandé** :
```bash
# Liste des disques (JSON)
disks_json=$(lsblk -J -o NAME,SIZE,TYPE,ROTA,TRAN)
# Pour chaque disque
for disk in $(lsblk -d -n -o NAME); do
model=$(sudo smartctl -i /dev/$disk 2>/dev/null | grep 'Device Model' | sed 's/.*: *//')
size=$(lsblk -d -n -o SIZE /dev/$disk | sed 's/,/./')
type=$(lsblk -d -n -o ROTA /dev/$disk) # 0=SSD, 1=HDD
[ "$type" = "0" ] && disk_type="ssd" || disk_type="hdd"
interface=$(lsblk -d -n -o TRAN /dev/$disk) # sata, nvme, usb
done
```
---
### 2.6 NETWORK (ip link)
**Commande testée** : `ip link show`
**Output analysé** :
```
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
link/ether 44:37:e6:6b:53:86 brd ff:ff:ff:ff:ff:ff
3: wlp2s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN
link/ether 14:f6:d8:67:27:b7 brd ff:ff:ff:ff:ff:ff
```
**✅ Données IMPORTANTES à extraire** :
- `name` : **eno1**, **wlp2s0**
- `mac` : **44:37:e6:6b:53:86**
- `state` : **UP** ou **DOWN**
- `type` : **ethernet** (eno*, eth*) ou **wifi** (wlp*, wlan*)
- `speed_mbps` : Via `ethtool eno1 | grep Speed` → "Speed: 1000Mb/s"
**Parsing recommandé** :
```bash
# Interfaces actives
for iface in $(ip -br link show | grep UP | awk '{print $1}'); do
# Ignorer loopback, docker, bridges
[[ "$iface" =~ ^(lo|docker|br-|veth) ]] && continue
mac=$(ip link show $iface | grep 'link/ether' | awk '{print $2}')
# Type d'interface
if [[ "$iface" =~ ^(eth|eno|enp) ]]; then
type="ethernet"
elif [[ "$iface" =~ ^(wlan|wlp) ]]; then
type="wifi"
else
type="unknown"
fi
# Vitesse (nécessite ethtool)
if command -v ethtool &> /dev/null; then
speed=$(sudo ethtool $iface 2>/dev/null | grep 'Speed:' | awk '{print $2}' | sed 's/Mb\/s//')
fi
done
```
---
### 2.7 OS (os-release + uname)
**Commande testée** : `cat /etc/os-release` + `uname -r` + `uname -m`
**Output analysé** :
```
ID=debian
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
6.12.57+deb13-amd64
x86_64
```
**✅ Données IMPORTANTES à extraire** :
- `name` : **debian**
- `version` : **13 (trixie)**
- `kernel_version` : **6.12.57+deb13-amd64**
- `architecture` : **x86_64**
**Parsing recommandé** :
```bash
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)
```
---
## 🔍 3. DONNÉES SUPPLÉMENTAIRES INTÉRESSANTES
### 3.1 BIOS (dmidecode -t 0)
**Pourquoi c'est utile** : Version BIOS/UEFI peut affecter performances, compatibilité
```bash
sudo dmidecode -t 0
# Output:
# Vendor: LENOVO
# Version: 9SKT91AUS
# Release Date: 03/30/2012
```
**✅ Données à extraire** :
- `bios_vendor` : LENOVO
- `bios_version` : 9SKT91AUS
- `bios_date` : 03/30/2012
---
### 3.2 PCI Version (lspci -v)
**Pourquoi c'est utile** : PCIe gen (2.0, 3.0, 4.0, 5.0) affecte bande passante GPU/NVMe
```bash
sudo lspci -vv -s 00:02.0 | grep 'LnkCap:'
# Output:
# LnkCap: Port #0, Speed 2.5GT/s, Width x16
# → PCIe 2.0 x16
```
**✅ Données à extraire** :
- `pcie_version` : 2.0, 3.0, 4.0, 5.0
- `pcie_lanes` : x1, x4, x8, x16
**❌ COMPLEXITÉ** : Parsing difficile, pas prioritaire pour MVP
---
### 3.3 USB Version (lsusb)
**Pourquoi c'est utile** : USB 2.0 / 3.0 / 3.1 / 4.0 pour périphériques
```bash
lsusb
# Output:
# Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
# Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```
**✅ Données à extraire** :
- `usb_controllers` : ["USB 2.0", "USB 3.0"]
**❌ PAS PRIORITAIRE** pour benchmarking hardware
---
### 3.4 Température CPU (sensors)
**Pourquoi c'est utile** : Throttling thermique affecte performances
```bash
sensors
# Output:
# coretemp-isa-0000
# Core 0: +45.0°C
# Core 1: +43.0°C
```
**✅ Données à extraire** :
- `cpu_temp_celsius` : 45.0 (moyenne des cores)
**❌ PAS PRIORITAIRE** pour MVP (nécessite lm-sensors installé)
---
### 3.5 Consommation électrique (TDP)
**Pourquoi c'est utile** : Efficacité énergétique (perf/watt)
**Problème** : Pas accessible via commandes Linux standard
- Nécessite lookup dans base de données (ark.intel.com, AMD specs)
- Ou lecture via RAPL (Running Average Power Limit) si supporté
**❌ NON IMPLÉMENTABLE** facilement sans DB externe
---
## 📋 4. RÉSUMÉ - Priorités d'implémentation
### ✅ NIVEAU 1 - ESSENTIEL (déjà partiellement fait)
| Catégorie | Données | Outil | Parsing |
|-----------|---------|-------|---------|
| CPU Bench | events_per_sec, latency | sysbench | ✅ Simple (grep/awk) |
| Memory Bench | throughput_mib_s | sysbench | ✅ Simple (grep/awk) |
| Disk Bench | read/write MB/s, IOPS | fio JSON | ✅ Moyen (jq) |
| Network Bench | upload/download Mbps | iperf3 JSON | ✅ Moyen (jq) |
| CPU Info | vendor, model, cores, threads | lscpu | ⚠️ Locale issue |
| RAM Info | total_mb | free | ✅ Simple |
| OS Info | name, version, kernel | os-release | ✅ Simple |
**Actions requises** :
1. ✅ Forcer `LC_ALL=C` pour lscpu
2. ✅ Utiliser JSON pour fio/iperf3
3. ✅ Parser correctement les résultats
---
### ✅ NIVEAU 2 - IMPORTANT (enrichissement)
| Catégorie | Données | Outil | Complexité |
|-----------|---------|-------|-----------|
| CPU Detail | freq, caches, flags | lscpu | Moyen |
| RAM Detail | slots, type, speed, ECC | dmidecode | Élevée (sudo required) |
| GPU | vendor, model, vram | lspci, nvidia-smi | Moyen |
| Storage | model, size, type (SSD/HDD) | lsblk, smartctl | Moyen (sudo) |
| Network | interfaces, MAC, speed | ip, ethtool | Moyen |
| Motherboard | manufacturer, model | dmidecode | Simple (sudo) |
**Actions requises** :
1. ✅ Implémenter détection dmidecode (avec fallback si sudo manquant)
2. ✅ Parser GPU (lspci minimum, nvidia-smi si dispo)
3. ✅ Collecter storage avec smartctl (optionnel)
---
### ⚠️ NIVEAU 3 - NICE TO HAVE (futur)
| Données | Outil | Priorité | Raison |
|---------|-------|----------|--------|
| BIOS version/date | dmidecode -t 0 | Basse | Utile mais pas critique |
| PCIe version | lspci -vv | Basse | Parsing complexe |
| USB controllers | lsusb | Très basse | Peu utile pour bench |
| CPU temp | sensors | Moyenne | Nécessite lm-sensors |
| TDP | DB externe | Très basse | Non faisable simplement |
**Actions** : À voir après MVP fonctionnel
---
## 🎯 5. RECOMMANDATIONS POUR LE SCRIPT
### Stratégie de collecte
1. **Hardware basique (sans sudo)** :
- ✅ lscpu (avec LC_ALL=C)
- ✅ free
- ✅ lspci
- ✅ lsblk
- ✅ ip link
- ✅ /etc/os-release
2. **Hardware avancé (avec sudo optionnel)** :
- ⚠️ dmidecode (RAM layout, motherboard, BIOS)
- ⚠️ smartctl (modèle SSD/HDD)
- ⚠️ ethtool (vitesse réseau)
3. **Gestion des cas** :
- Si `sudo` non disponible → Collecter infos basiques uniquement
- Si outil manquant → Retourner `null` pour ce champ
- Si erreur → Log warning, continuer
### JSON final recommandé
```json
{
"hardware": {
"cpu": {
"vendor": "GenuineIntel",
"model": "Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz",
"cores": 4,
"threads": 4,
"base_freq_ghz": 3.1,
"max_freq_ghz": 3.4,
"cache_l1_kb": 256,
"cache_l2_kb": 1024,
"cache_l3_kb": 6144,
"flags": ["avx", "sse4_1", "sse4_2"]
},
"ram": {
"total_mb": 7771,
"slots_total": 4,
"slots_used": 4,
"ecc": false,
"layout": [
{"slot": "DIMM0", "size_mb": 2048, "type": "DDR3", "speed_mhz": 1333, "manufacturer": "Samsung"}
]
},
"gpu": {
"vendor": "Intel Corporation",
"model": "2nd Gen Core Processor Family IGP",
"vram_mb": null
},
"motherboard": {
"manufacturer": "LENOVO",
"model": "Unknown",
"bios_version": "9SKT91AUS",
"bios_date": "03/30/2012"
},
"storage": [
{"device": "sda", "model": "KINGSTON SA400S37480G", "size_gb": 480, "type": "ssd", "interface": "sata"}
],
"network": [
{"name": "eno1", "type": "ethernet", "speed_mbps": 1000, "mac": "44:37:e6:6b:53:86"}
],
"os": {
"name": "debian",
"version": "13 (trixie)",
"kernel_version": "6.12.57+deb13-amd64",
"architecture": "x86_64"
}
},
"results": {
"cpu": {
"events_per_sec": 3409.87,
"duration_s": 10.0005,
"score": 34.1
},
"memory": {
"throughput_mib_s": 13806.03,
"score": 69.0
},
"disk": {
"read_mb_s": 711,
"write_mb_s": 711,
"iops_read": 177777,
"iops_write": 177777,
"latency_ms": 0.002,
"score": 85.0
},
"network": {
"upload_mbps": 327,
"download_mbps": 323,
"ping_ms": 53.1,
"score": 32.7
},
"global_score": 55.2
}
}
```
---
## ✅ CONCLUSION
**Données ESSENTIELLES déjà disponibles** : ✅
- Benchmarks (CPU, RAM, Disk, Network) → Parsing fonctionne
- Infos basiques (CPU, RAM, OS) → Parsing à corriger (locale)
**Données IMPORTANTES à ajouter** : ⚠️
- CPU détaillé (fréquences, caches, flags)
- RAM détaillée (dmidecode)
- GPU (lspci minimum)
- Storage (lsblk + smartctl)
- Network (ip link + ethtool)
**Données OPTIONNELLES** : 💡
- BIOS
- PCI version
- Température CPU
**Prochaine étape** : Modifier le script bench.sh pour collecter toutes les données NIVEAU 1 et NIVEAU 2

192
CHANGELOG.md Normal file
View File

@@ -0,0 +1,192 @@
# Changelog - script_test.sh
## Version 1.0.1 - Améliorations demandées
### Nouvelles fonctionnalités
#### 1. Wake-on-LAN pour cartes Ethernet
- **Fichier** : [script_test.sh:546-555](script_test.sh#L546-L555)
- Détection automatique du support Wake-on-LAN via `ethtool`
- Ajout du champ `wake_on_lan` (true/false/null) dans les informations réseau
- Vérifie si la carte supporte le "magic packet" (flag 'g')
```json
{
"name": "eth0",
"type": "ethernet",
"wake_on_lan": true
}
```
#### 2. Statistiques RAM détaillées
- **Fichier** : [script_test.sh:298-303](script_test.sh#L298-L303) et [script_test.sh:367-385](script_test.sh#L367-L385)
- Ajout de la RAM utilisée (`used_mb`)
- Ajout de la RAM libre (`free_mb`)
- Ajout de la RAM partagée (`shared_mb`) - inclut tmpfs, vidéo partagée, etc.
- Distinction entre RAM physique totale et RAM disponible dans l'OS
```json
{
"total_mb": 16384,
"used_mb": 8192,
"free_mb": 7500,
"shared_mb": 692
}
```
#### 3. Test réseau iperf3 vers 10.0.1.97
- **Fichier** : [script_test.sh:675-726](script_test.sh#L675-L726)
- Test de connectivité préalable avec `ping`
- Test upload (client → serveur) pendant 10 secondes
- Test download (serveur → client avec `-R`) pendant 10 secondes
- Mesure du ping moyen (5 paquets)
- Calcul du score réseau basé sur la moyenne upload/download
**Prérequis** : Le serveur 10.0.1.97 doit avoir `iperf3 -s` en cours d'exécution.
```bash
# Sur le serveur 10.0.1.97
iperf3 -s
```
```json
{
"upload_mbps": 940.50,
"download_mbps": 950.20,
"ping_ms": 0.5,
"score": 94.54
}
```
#### 4. Données SMART de vieillissement des disques
- **Fichier** : [script_test.sh:492-602](script_test.sh#L492-L602)
- Extraction complète des données SMART pour chaque disque via `smartctl`
- **Indicateurs de santé globale** :
- `health_status` : PASSED/FAILED (test auto-diagnostic SMART)
- `temperature_celsius` : Température actuelle du disque
- **Indicateurs de vieillissement** :
- `power_on_hours` : Heures de fonctionnement totales
- `power_cycle_count` : Nombre de démarrages/arrêts
- `reallocated_sectors` : Secteurs défectueux réalloués (⚠️ signe de défaillance)
- `pending_sectors` : Secteurs en attente de réallocation (⚠️ attention)
- `udma_crc_errors` : Erreurs de transmission (câble/interface)
- **Pour SSD uniquement** :
- `wear_leveling_count` : Compteur d'usure des cellules
- `total_lbas_written` : Volume total de données écrites
**Interprétation** :
-`health_status: "PASSED"` + `reallocated_sectors: 0` = Disque sain
- ⚠️ `reallocated_sectors > 0` = Début de défaillance, surveiller
- 🔴 `pending_sectors > 0` = Défaillance imminente, sauvegarder immédiatement
- 🔴 `health_status: "FAILED"` = Disque défaillant, remplacer
```json
{
"device": "sda",
"model": "Samsung SSD 970 EVO Plus 500GB",
"type": "ssd",
"smart": {
"health_status": "PASSED",
"power_on_hours": 12543,
"power_cycle_count": 1876,
"temperature_celsius": 42,
"reallocated_sectors": 0,
"pending_sectors": 0,
"udma_crc_errors": 0,
"wear_leveling_count": 97,
"total_lbas_written": 45678901234
}
}
```
#### 5. Correction du calcul global_score
- **Fichier** : [script_test.sh:732-760](script_test.sh#L732-L760)
- Le score global n'inclut **que** CPU, RAM et Disk (pas réseau, pas GPU)
- Nouvelle pondération :
- **CPU** : 40%
- **RAM** : 30%
- **Disk** : 30%
- Normalisation automatique si certains benchmarks sont manquants
- Score sur 100
### Corrections
- **PATH Fix** : Ajout de `/usr/sbin` et `/sbin` au PATH ([script_test.sh:30](script_test.sh#L30))
- Résout le problème de détection de `dmidecode`, `smartctl`, `ethtool`
### Format JSON mis à jour
```json
{
"hardware": {
"ram": {
"total_mb": 16384,
"used_mb": 8192,
"free_mb": 7500,
"shared_mb": 692,
"slots_total": 4,
"slots_used": 2,
"ecc": false,
"layout": [...]
},
"network": [
{
"name": "eth0",
"type": "ethernet",
"mac": "00:11:22:33:44:55",
"ip_address": "10.0.1.100",
"speed_mbps": 1000,
"wake_on_lan": true
}
]
},
"benchmarks": {
"cpu": {
"events_per_sec": 5234.89,
"duration_s": 10.0,
"score": 52.35
},
"memory": {
"throughput_mib_s": 15234.5,
"score": 76.17
},
"disk": {
"read_mb_s": 450.0,
"write_mb_s": 420.0,
"iops_read": 112000,
"iops_write": 105000,
"latency_ms": 0.08,
"score": 43.50
},
"network": {
"upload_mbps": 940.5,
"download_mbps": 950.2,
"ping_ms": 0.5,
"score": 94.54
},
"gpu": null,
"global_score": 57.00
}
}
```
### Notes d'utilisation
1. **Serveur iperf3** : Assurez-vous que `iperf3 -s` tourne sur 10.0.1.97 avant de lancer le script
2. **Permissions** : Le script nécessite `sudo` pour dmidecode, smartctl, ethtool
3. **Durée** : Le script prend environ 3-4 minutes (10s iperf3 upload + 10s download + 30s disk)
### Commande de test
```bash
# Lancer le serveur iperf3 sur 10.0.1.97
ssh user@10.0.1.97 'iperf3 -s -D'
# Lancer le script de test
sudo bash script_test.sh
# Voir le résultat
cat result.json | jq .
```

316
DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,316 @@
# Guide de Déploiement - Linux BenchTools
## Modifications Apportées
### 1. Backend API
-**Schémas Pydantic mis à jour** ([backend/app/schemas/hardware.py](backend/app/schemas/hardware.py))
- `RAMInfo` : Ajout de `used_mb`, `free_mb`, `shared_mb`
- `StorageDevice` : `capacity_gb` changé de `int` à `float`
-**API endpoint mis à jour** ([backend/app/api/benchmark.py](backend/app/api/benchmark.py))
- Sauvegarde des nouveaux champs RAM
-**Modèles de base de données** ([backend/app/models/](backend/app/models/))
- `HardwareSnapshot` : Nouveaux champs RAM
- `DiskSMART` : Nouveau modèle pour données SMART (créé mais pas encore utilisé)
### 2. Docker
-**Service iperf3 ajouté** ([docker-compose.yml](docker-compose.yml))
- Port 5201 TCP/UDP exposé
### 3. Script Client
-**Script bench.sh complètement réécrit** ([scripts/bench.sh](scripts/bench.sh))
- Génère le payload JSON directement
- Collecte toutes les données hardware
- Exécute les benchmarks
- Envoie au serveur API
## Déploiement
### Étape 1: Arrêter les Services
```bash
cd /home/gilles/Documents/vscode/serv_benchmark
docker-compose down
```
### Étape 2: Appliquer la Migration Base de Données
Si la base de données existe déjà :
```bash
cd backend
python3 apply_migration.py
```
**OU** si vous préférez partir de zéro (⚠️ PERTE DE DONNÉES) :
```bash
rm -f backend/data/data.db
```
### Étape 3: Démarrer les Services
```bash
docker-compose up -d
```
Vérifier les logs :
```bash
docker-compose logs -f backend
docker-compose logs iperf3
```
### Étape 4: Tester le Serveur iperf3
Depuis un client :
```bash
iperf3 -c 10.0.1.97 -p 5201
```
Vous devriez voir le test de bande passante s'exécuter.
### Étape 5: Exécuter le Script Bench
Depuis un client Linux :
```bash
# Option 1: Télécharger et exécuter
curl -sSL https://gitea.maison43.duckdns.org/gilles/serv_benchmark/raw/branch/main/scripts/bench.sh | sudo bash
# Option 2: Exécuter localement
cd /chemin/vers/serv_benchmark
sudo bash scripts/bench.sh
```
**Note** : Le script `bench.sh` a les paramètres serveur codés en dur :
- `SERVER_URL="10.0.1.97:8007"`
- `API_TOKEN="29855796dacf5cfe75ff9b02d6adf3dd0f9c52db5b53e7abfb4c0df7ece1be0a"`
- `IPERF_SERVER="10.0.1.97"`
Pour les modifier, éditez le fichier ou passez-les en variables d'environnement.
## Vérification
### 1. Vérifier la Base de Données
```bash
sqlite3 backend/data/data.db
# Lister les tables
.tables
# Vérifier les colonnes de hardware_snapshots
PRAGMA table_info(hardware_snapshots);
# Voir les derniers benchmarks
SELECT id, device_id, global_score, run_at FROM benchmarks ORDER BY run_at DESC LIMIT 5;
# Voir les données RAM détaillées
SELECT
d.hostname,
h.ram_total_mb,
h.ram_used_mb,
h.ram_free_mb,
h.ram_shared_mb
FROM hardware_snapshots h
JOIN devices d ON h.device_id = d.id
ORDER BY h.captured_at DESC
LIMIT 5;
```
### 2. Vérifier l'API
```bash
# Tester l'endpoint
curl http://10.0.1.97:8007/docs
# Voir les devices
curl http://10.0.1.97:8007/api/devices
```
### 3. Consulter le Frontend
Ouvrir dans un navigateur :
```
http://10.0.1.97:8087
```
## Troubleshooting
### Erreur: HTTP 422 "Input should be a valid integer"
**Solution** : Cette erreur a été corrigée en changeant `capacity_gb` de `int` à `float` dans le schéma.
Si vous rencontrez encore cette erreur :
1. Vérifiez que le backend a bien été redémarré après les modifications
2. Vérifiez les logs : `docker-compose logs backend`
### Port 5201 déjà utilisé
```bash
# Trouver le processus
sudo lsof -i :5201
# Arrêter le service Docker iperf3
docker-compose stop iperf3
```
### Script bench.sh ne trouve pas les dépendances
```bash
# Installer manuellement
sudo apt-get update
sudo apt-get install -y curl jq sysbench fio iperf3 dmidecode smartmontools ethtool
```
### Base de données verrouillée (SQLite locked)
```bash
# Arrêter tous les services
docker-compose down
# Supprimer les locks
rm -f backend/data/data.db-shm backend/data/data.db-wal
# Redémarrer
docker-compose up -d
```
## Structure des Données Envoyées
Le script `bench.sh` envoie un payload JSON avec cette structure :
```json
{
"device_identifier": "hostname",
"bench_script_version": "1.1.0",
"hardware": {
"cpu": {
"vendor": "GenuineIntel",
"model": "Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz",
"cores": 4,
"threads": 4,
...
},
"ram": {
"total_mb": 7771,
"used_mb": 6123, // NOUVEAU
"free_mb": 923, // NOUVEAU
"shared_mb": 760, // NOUVEAU
"slots_total": 4,
"slots_used": 4,
"ecc": false,
"layout": [...]
},
"storage": {
"devices": [
{
"name": "/dev/sda",
"type": "SSD",
"capacity_gb": 447.1, // FLOAT maintenant
...
}
],
"partitions": []
},
"network": {
"interfaces": [...]
},
"motherboard": {...},
"os": {...}
},
"results": {
"cpu": {
"events_per_sec": 1206.65,
"duration_s": 10.0,
"score": 12.06
},
"memory": {...},
"disk": {...},
"network": {...},
"gpu": null,
"global_score": 10.85
}
}
```
## Prochaines Étapes
### À Implémenter
1. **Données SMART des disques**
- Le modèle `DiskSMART` existe mais n'est pas encore utilisé
- Modifier `bench.sh` pour collecter les données SMART
- Adapter l'API pour sauvegarder dans `disk_smart_data`
2. **Wake-on-LAN**
- Ajouter le champ dans le schéma `NetworkInterface`
- Collecter dans `bench.sh`
- Afficher dans le frontend
3. **Frontend**
- Afficher les nouvelles données RAM
- Graphiques d'utilisation
- Dashboard SMART pour la santé des disques
4. **Amélioration du script bench.sh**
- Supporter les arguments en ligne de commande
- Mode verbose/debug
- Retry automatique en cas d'échec
## Logs Utiles
### Backend
```bash
docker-compose logs -f backend
```
### iperf3
```bash
docker-compose logs -f iperf3
```
### Tous les services
```bash
docker-compose logs -f
```
## Performance
Le script `bench.sh` prend environ **3-5 minutes** pour :
- Collecter toutes les données hardware (30s)
- Benchmark CPU (10s)
- Benchmark RAM (10s)
- Benchmark Disk (2-3 minutes avec fio)
- Benchmark Network (optionnel, 20s)
## Sécurité
⚠️ **Le token API est codé en dur dans le script** :
```bash
API_TOKEN="29855796dacf5cfe75ff9b02d6adf3dd0f9c52db5b53e7abfb4c0df7ece1be0a"
```
**Recommandations** :
- Changer ce token en production
- Utiliser des variables d'environnement
- Limiter l'accès au serveur API par IP
- Utiliser HTTPS en production
## Support
Pour toute question ou problème :
1. Consulter les logs Docker
2. Vérifier le fichier [IMPLEMENTATION_STATUS.md](IMPLEMENTATION_STATUS.md)
3. Tester manuellement avec `curl` :
```bash
# Test manuel de l'API
curl -X POST http://10.0.1.97:8007/api/benchmark \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d @result.json
```

248
FIXES_APPLIED.md Normal file
View File

@@ -0,0 +1,248 @@
# Corrections Appliquées - Erreur HTTP 422
## Problème Initial
Lors de l'exécution de `bench.sh`, erreur HTTP 422 :
```
{"detail":[{
"type":"int_from_float",
"loc":["body","hardware","storage","devices",0,"capacity_gb"],
"msg":"Input should be a valid integer, got a number with a fractional part",
"input":447.1
}]}
```
## Cause
Le schéma Pydantic `StorageDevice` définissait `capacity_gb` comme `int`, mais le script `bench.sh` envoyait un `float` (447.1).
## Solutions Appliquées
### 1. ✅ Schéma StorageDevice ([backend/app/schemas/hardware.py](backend/app/schemas/hardware.py:59))
**Avant** :
```python
class StorageDevice(BaseModel):
capacity_gb: Optional[int] = None
```
**Après** :
```python
class StorageDevice(BaseModel):
capacity_gb: Optional[float] = None # Changed from int to float
```
### 2. ✅ Schéma RAMInfo ([backend/app/schemas/hardware.py](backend/app/schemas/hardware.py:35-44))
Ajout des nouveaux champs pour les statistiques RAM :
```python
class RAMInfo(BaseModel):
total_mb: int
used_mb: Optional[int] = None # NEW
free_mb: Optional[int] = None # NEW
shared_mb: Optional[int] = None # NEW
slots_total: Optional[int] = None
slots_used: Optional[int] = None
ecc: Optional[bool] = None
layout: Optional[List[RAMSlot]] = None
```
### 3. ✅ API Backend ([backend/app/api/benchmark.py](backend/app/api/benchmark.py:72-80))
Sauvegarde des nouveaux champs RAM :
```python
# RAM
ram_total_mb=hw.ram.total_mb if hw.ram else None,
ram_used_mb=hw.ram.used_mb if hw.ram else None, # NEW
ram_free_mb=hw.ram.free_mb if hw.ram else None, # NEW
ram_shared_mb=hw.ram.shared_mb if hw.ram else None, # NEW
ram_slots_total=hw.ram.slots_total if hw.ram else None,
# ...
```
### 4. ✅ Modèle de Base de Données ([backend/app/models/hardware_snapshot.py](backend/app/models/hardware_snapshot.py:35-43))
Ajout des colonnes dans la table `hardware_snapshots` :
```python
# RAM
ram_total_mb = Column(Integer, nullable=True)
ram_used_mb = Column(Integer, nullable=True) # NEW
ram_free_mb = Column(Integer, nullable=True) # NEW
ram_shared_mb = Column(Integer, nullable=True) # NEW
ram_slots_total = Column(Integer, nullable=True)
# ...
```
### 5. ✅ Migration SQL ([backend/migrations/001_add_ram_stats_and_smart.sql](backend/migrations/001_add_ram_stats_and_smart.sql))
Script SQL pour mettre à jour une base existante :
```sql
ALTER TABLE hardware_snapshots ADD COLUMN ram_used_mb INTEGER;
ALTER TABLE hardware_snapshots ADD COLUMN ram_free_mb INTEGER;
ALTER TABLE hardware_snapshots ADD COLUMN ram_shared_mb INTEGER;
```
### 6. ✅ Script d'Application de Migration ([backend/apply_migration.py](backend/apply_migration.py))
Script Python pour appliquer automatiquement la migration :
```bash
python3 backend/apply_migration.py
```
## Test de Validation
```bash
# Test des schémas Pydantic
python3 -c "
from app.schemas.hardware import RAMInfo, StorageDevice
# Test RAMInfo avec nouveaux champs
ram = RAMInfo(total_mb=8000, used_mb=6000, free_mb=1500, shared_mb=500)
print('✅ RAMInfo OK')
# Test StorageDevice avec float
storage = StorageDevice(name='/dev/sda', capacity_gb=447.1)
print(f'✅ StorageDevice OK - capacity_gb={storage.capacity_gb}')
"
```
Résultat :
```
✅ RAMInfo OK
✅ StorageDevice OK - capacity_gb=447.1
```
## Prochaines Étapes pour le Déploiement
### Option 1 : Nouvelle Base de Données (Recommandé pour les tests)
```bash
# Arrêter les services
docker-compose down
# Supprimer l'ancienne base
rm -f backend/data/data.db
# Redémarrer (la base sera recréée automatiquement avec les nouveaux champs)
docker-compose up -d
```
### Option 2 : Migration de la Base Existante (Production)
```bash
# Arrêter les services
docker-compose down
# Appliquer la migration
python3 backend/apply_migration.py
# Redémarrer
docker-compose up -d
```
## Vérification
### 1. Tester avec le script bench.sh
```bash
sudo bash scripts/bench.sh
```
Vous devriez voir :
```
[8/8] Construction du payload JSON et envoi au serveur
✓ Envoi du payload vers: http://10.0.1.97:8007/api/benchmark
✓ Payload envoyé avec succès (HTTP 200)
```
### 2. Vérifier la base de données
```bash
sqlite3 backend/data/data.db
# Vérifier les nouvelles colonnes
PRAGMA table_info(hardware_snapshots);
# Voir les données
SELECT
d.hostname,
h.ram_total_mb,
h.ram_used_mb,
h.ram_free_mb,
h.ram_shared_mb
FROM hardware_snapshots h
JOIN devices d ON h.device_id = d.id
ORDER BY h.captured_at DESC
LIMIT 1;
```
### 3. Vérifier les logs backend
```bash
docker-compose logs backend | grep -i error
```
Pas d'erreurs = succès ! ✅
## Résumé des Fichiers Modifiés
| Fichier | Modification | Status |
|---------|-------------|--------|
| [backend/app/schemas/hardware.py](backend/app/schemas/hardware.py) | `capacity_gb: int``float`, ajout champs RAM | ✅ |
| [backend/app/api/benchmark.py](backend/app/api/benchmark.py) | Sauvegarde nouveaux champs RAM | ✅ |
| [backend/app/models/hardware_snapshot.py](backend/app/models/hardware_snapshot.py) | Ajout colonnes RAM | ✅ |
| [backend/migrations/001_add_ram_stats_and_smart.sql](backend/migrations/001_add_ram_stats_and_smart.sql) | Migration SQL | ✅ |
| [backend/apply_migration.py](backend/apply_migration.py) | Script d'application | ✅ |
## Données Maintenant Collectées
### Avant
```json
{
"ram": {
"total_mb": 8000
},
"storage": {
"devices": [{
"capacity_gb": 500 // int uniquement
}]
}
}
```
### Après
```json
{
"ram": {
"total_mb": 7771,
"used_mb": 6123, // ✨ NOUVEAU
"free_mb": 923, // ✨ NOUVEAU
"shared_mb": 760 // ✨ NOUVEAU
},
"storage": {
"devices": [{
"capacity_gb": 447.1 // ✨ Supporte maintenant les décimales
}]
}
}
```
## Notes
- ✅ Les modifications sont **rétrocompatibles** : les anciens benchmarks sans `used_mb`/`free_mb`/`shared_mb` continueront de fonctionner
- ✅ Le script `bench.sh` est maintenant **totalement autonome** et ne dépend plus de `script_test.sh`
- ✅ Le payload JSON est **conforme** au format attendu par l'API
- ✅ La validation Pydantic fonctionne correctement
## Support
En cas de problème, consulter :
1. [DEPLOYMENT_GUIDE.md](DEPLOYMENT_GUIDE.md) - Guide complet de déploiement
2. [IMPLEMENTATION_STATUS.md](IMPLEMENTATION_STATUS.md) - État d'implémentation
3. Logs Docker : `docker-compose logs -f backend`

257
FRONTEND_UPDATES.md Normal file
View File

@@ -0,0 +1,257 @@
# Mises à jour Frontend - Linux BenchTools
Date: 2025-12-07
## Vue d'ensemble
Le frontend a été mis à jour pour afficher les nouvelles données collectées par le script bench.sh v1.1.0, notamment :
- Statistiques RAM détaillées (utilisée, libre, partagée)
- Informations réseau complètes (Wake-on-LAN, vitesse)
- Résultats des tests iperf3 (upload, download, ping)
## Fichiers modifiés
### 1. `frontend/device_detail.html`
**Ajout** d'une nouvelle section "Informations Réseau Détaillées" après la section "Dernier Benchmark" (lignes 67-75) :
```html
<!-- Network Details -->
<div class="card">
<div class="card-header">🌐 Informations Réseau Détaillées</div>
<div class="card-body">
<div id="networkDetails">
<div class="loading">Chargement...</div>
</div>
</div>
</div>
```
### 2. `frontend/js/device_detail.js`
#### Modifications apportées :
**a) Appel de la nouvelle fonction dans `loadDeviceDetail()` (ligne 39)**
```javascript
renderNetworkDetails();
```
**b) Affichage RAM détaillé dans `renderHardwareSummary()` (lignes 89-105)**
Ajout du calcul et de l'affichage des statistiques RAM :
- Pourcentage d'utilisation
- RAM utilisée en GB
- RAM libre en GB
- RAM partagée en GB (si disponible, ex: mémoire vidéo partagée)
```javascript
// RAM usage info
const ramTotalGB = Math.round((snapshot.ram_total_mb || 0) / 1024);
const ramUsedMB = snapshot.ram_used_mb || 0;
const ramFreeMB = snapshot.ram_free_mb || 0;
const ramSharedMB = snapshot.ram_shared_mb || 0;
let ramValue = `${ramTotalGB} GB`;
if (ramUsedMB > 0 || ramFreeMB > 0) {
const usagePercent = ramTotalGB > 0 ? Math.round((ramUsedMB / (snapshot.ram_total_mb || 1)) * 100) : 0;
ramValue = `${ramTotalGB} GB (${usagePercent}% utilisé)<br><small>Utilisée: ${Math.round(ramUsedMB / 1024)}GB • Libre: ${Math.round(ramFreeMB / 1024)}GB`;
if (ramSharedMB > 0) {
ramValue += ` • Partagée: ${Math.round(ramSharedMB / 1024)}GB`;
}
ramValue += `<br>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>`;
}
```
**c) Nouvelle fonction `renderNetworkDetails()` (lignes 163-281)**
Cette fonction affiche :
1. **Interfaces réseau** :
- Type (ethernet/wifi) avec icône appropriée (🔌/📡)
- Badge Wake-on-LAN (WoL ✓ ou WoL ✗)
- Adresse IP
- Adresse MAC
- Vitesse en Mbps
- Driver (si disponible)
2. **Résultats benchmark réseau (iperf3)** :
- Upload en Mbps avec flèche ↑
- Download en Mbps avec flèche ↓
- Ping moyen en ms
- Score réseau
```javascript
// Render network details
function renderNetworkDetails() {
const container = document.getElementById('networkDetails');
const snapshot = currentDevice.last_hardware_snapshot;
const bench = currentDevice.last_benchmark;
// Parse network interfaces JSON
const interfaces = JSON.parse(snapshot.network_interfaces_json);
// Display each interface with WoL badge
interfaces.forEach(iface => {
const typeIcon = iface.type === 'ethernet' ? '🔌' : '📡';
const wolBadge = iface.wake_on_lan === true
? '<span class="badge badge-success">WoL ✓</span>'
: '<span class="badge badge-muted">WoL ✗</span>';
// ... affichage des détails
});
// Display iperf3 results if available
if (bench && bench.network_results_json) {
const netResults = JSON.parse(bench.network_results_json);
// Affichage upload_mbps, download_mbps, ping_ms, score
}
}
```
## Backend - Modifications associées
### 1. Modèle Benchmark (`backend/app/models/benchmark.py`)
**Ajout** du champ `network_results_json` (ligne 33) :
```python
network_results_json = Column(Text, nullable=True) # Network benchmark details (iperf3)
```
### 2. API Benchmark (`backend/app/api/benchmark.py`)
**Ajout** de l'extraction et de la sauvegarde des résultats réseau (lignes 134-142, 158) :
```python
# Extract network results for easier frontend access
network_results = None
if results.network:
network_results = {
"upload_mbps": results.network.upload_mbps,
"download_mbps": results.network.download_mbps,
"ping_ms": results.network.ping_ms,
"score": results.network.score
}
benchmark = Benchmark(
# ...
network_results_json=json.dumps(network_results) if network_results else None
)
```
### 3. Migration SQL
**Fichier** : `backend/migrations/002_add_network_results.sql`
```sql
ALTER TABLE benchmarks ADD COLUMN network_results_json TEXT;
```
**Script d'application** : `backend/apply_migration_002.py`
## Déploiement
Pour appliquer les mises à jour :
### 1. Appliquer la migration 002
```bash
cd backend
python3 apply_migration_002.py
```
### 2. Redémarrer le backend
```bash
docker compose restart backend
```
### 3. Frontend
Aucune action requise - les fichiers HTML/JS sont servis directement par nginx.
Il suffit de rafraîchir la page dans le navigateur (F5 ou Ctrl+R).
## Données affichées
### Section "Résumé Hardware"
- **RAM** : Affiche maintenant le pourcentage d'utilisation et le détail utilisée/libre/partagée
**Avant** :
```
💾 RAM: 16 GB
2 / 4 slots
```
**Après** :
```
💾 RAM: 16 GB (45% utilisé)
Utilisée: 7GB • Libre: 9GB • Partagée: 0GB
2 / 4 slots
```
### Section "Informations Réseau Détaillées" (nouvelle)
**Interfaces** :
```
🔌 enp0s31f6 (ethernet) [WoL ✓]
IP: 10.0.1.100
MAC: a0:b1:c2:d3:e4:f5
Vitesse: 1000 Mbps
```
**Benchmark iperf3** :
```
📈 Résultats Benchmark Réseau (iperf3)
↑ 945.23 ↓ 938.17 52.3 47.2
Upload Mbps Download Mbps Ping ms Score
```
## Compatibilité
- ✅ Compatible avec les anciens benchmarks (affichage gracieux si données manquantes)
- ✅ Gestion des cas où `network_results_json` est null
- ✅ Gestion des cas où `ram_used_mb`, `ram_free_mb`, `ram_shared_mb` sont null
- ✅ Affichage "N/A" ou messages appropriés si données non disponibles
## Tests à effectuer
1. **Nouveau benchmark** :
- Exécuter `sudo bash scripts/bench.sh` depuis un client
- Vérifier l'affichage des nouvelles données sur la page device detail
2. **Anciens benchmarks** :
- Vérifier que les devices avec anciens benchmarks s'affichent correctement
- Vérifier l'absence d'erreurs JavaScript dans la console
3. **Wake-on-LAN** :
- Vérifier le badge "WoL ✓" pour les interfaces ethernet supportant WoL
- Vérifier le badge "WoL ✗" pour les interfaces ne supportant pas WoL
4. **iperf3** :
- Vérifier l'affichage des débits upload/download
- Vérifier l'affichage du ping moyen
- Vérifier le calcul du score réseau
## Notes techniques
### Parsing JSON
Le frontend parse plusieurs champs JSON :
- `network_interfaces_json` : Array des interfaces réseau
- `network_results_json` : Objet avec upload_mbps, download_mbps, ping_ms, score
### Gestion des erreurs
Tous les parsings JSON sont dans des blocs try/catch pour éviter les crashes si le JSON est invalide.
### Classes CSS utilisées
- `.badge-success` : Badge vert pour WoL activé
- `.badge-muted` : Badge gris pour WoL désactivé
- `.hardware-item` : Conteneur pour chaque interface réseau
- Couleurs CSS variables : `--color-success`, `--color-info`, `--color-warning`, `--color-primary`
## Améliorations futures possibles
1. **Graphiques** : Ajouter des graphiques pour visualiser l'historique des performances réseau
2. **SMART** : Ajouter une section pour afficher les données SMART des disques (déjà collectées par le script)
3. **Alertes** : Notifier si Wake-on-LAN n'est pas activé sur les serveurs
4. **Comparaison** : Permettre de comparer les résultats iperf3 entre plusieurs benchmarks
5. **Export** : Permettre d'exporter les données réseau en CSV/JSON
## Support
Pour toute question ou problème concernant ces mises à jour, vérifiez :
1. Les logs du backend : `docker compose logs backend`
2. La console JavaScript du navigateur (F12)
3. Les migrations ont été appliquées : `python3 backend/apply_migration_002.py`

201
IMPLEMENTATION_STATUS.md Normal file
View File

@@ -0,0 +1,201 @@
# État d'Implémentation - Linux BenchTools
## ✅ Complété
### 1. Script de Collecte Local (`script_test.sh`)
- ✅ Collecte complète des informations hardware
- ✅ Données SMART des disques (santé + vieillissement)
- ✅ Statistiques RAM détaillées (used, free, shared)
- ✅ Wake-on-LAN pour cartes ethernet
- ✅ Benchmarks (CPU, RAM, Disk, Network via iperf3)
- ✅ Génération de `result.json` complet
- ✅ PATH fix pour `/usr/sbin`
### 2. Docker Infrastructure
- ✅ Service backend (FastAPI)
- ✅ Service frontend (Nginx)
- ✅ Service iperf3 (port 5201)
### 3. Modèles de Base de Données
- ✅ Modèle `HardwareSnapshot` mis à jour (RAM used/free/shared)
- ✅ Nouveau modèle `DiskSMART` pour données de vieillissement
- ✅ Relations entre modèles configurées
### 4. Script Client (`scripts/bench.sh`)
- ✅ Wrapper pour `script_test.sh`
- ✅ Parsing d'arguments (--server, --token, --iperf-server)
- ✅ Vérification et installation des dépendances
- ✅ Envoi POST des résultats au serveur
## 🚧 En Cours / À Faire
### 5. Schémas Pydantic (Backend)
**Problème**: Les schémas actuels ne correspondent pas à la structure de `result.json`
#### Format actuel attendu par l'API:
```json
{
"device_identifier": "hostname",
"bench_script_version": "1.0.0",
"hardware": {
"cpu": { "vendor": "...", "model": "..." },
"ram": { "total_mb": 8000, "layout": [...] },
"storage": { "devices": [...], "partitions": [...] },
...
},
"results": {
"cpu": { "score": 50 },
"memory": { "score": 60 },
...
}
}
```
#### Format généré par `result.json`:
```json
{
"metadata": {
"script_version": "1.0.0",
"timestamp": "2025-12-07T19:47:34Z"
},
"system": {
"hostname": "lenovo-bureau",
"os": { "name": "debian", ... }
},
"hardware": {
"cpu": { "vendor": "...", "model": "...", "flags": [...] },
"ram": {
"total_mb": 7771,
"used_mb": 6123, // NOUVEAU
"free_mb": 923, // NOUVEAU
"shared_mb": 760, // NOUVEAU
"layout": [...]
},
"storage": [ // Format différent: array au lieu d'objet
{
"device": "sda",
"model": "...",
"smart": { // NOUVEAU
"health_status": "PASSED",
"power_on_hours": 7101,
...
}
}
],
"network": [ // Format différent: array au lieu d'objet
{
"name": "eno1",
"wake_on_lan": true // NOUVEAU
}
]
},
"benchmarks": { // "results" dans l'API
"cpu": { "events_per_sec": 1206, "score": 12.06 },
"global_score": 10.85
}
}
```
### Actions Requises:
#### Option A: Modifier les Schémas Pydantic (Recommandé)
- [ ] Mettre à jour `RAMInfo` pour ajouter `used_mb`, `free_mb`, `shared_mb`
- [ ] Créer `SMARTData` schema pour les données SMART
- [ ] Mettre à jour `StorageDevice` pour inclure `smart: SMARTData`
- [ ] Mettre à jour `NetworkInterface` pour ajouter `wake_on_lan`, `ip_address`
- [ ] Créer un mapper qui convertit `result.json` → format API attendu
- `metadata.script_version``bench_script_version`
- `system.hostname``device_identifier`
- `hardware.storage[]``hardware.storage.devices[]`
- `hardware.network[]``hardware.network.interfaces[]`
- `benchmarks``results`
#### Option B: Adapter le script client
- [ ] Modifier `script_test.sh` pour générer le format attendu par l'API
- ⚠️ Moins flexible, car le format local serait différent du format serveur
### 6. API Backend (`backend/app/api/benchmark.py`)
- [ ] Adapter le parsing pour les nouvelles données
- [ ] Sauvegarder les données SMART dans la table `disk_smart_data`
- [ ] Gérer les nouveaux champs RAM (used/free/shared)
- [ ] Gérer wake_on_lan pour les interfaces réseau
### 7. Migration Alembic
- [ ] Créer migration pour:
- `hardware_snapshots.ram_used_mb`
- `hardware_snapshots.ram_free_mb`
- `hardware_snapshots.ram_shared_mb`
- Nouvelle table `disk_smart_data`
### 8. Frontend
- [ ] Afficher les données SMART (santé des disques)
- [ ] Afficher l'utilisation RAM détaillée
- [ ] Afficher Wake-on-LAN status
- [ ] Graphiques d'évolution SMART (power_on_hours, température)
### 9. Tests
- [ ] Tester le flux complet local → serveur
- [ ] Vérifier que toutes les données sont bien sauvegardées
- [ ] Tester avec plusieurs machines
## 🎯 Prochaines Étapes Recommandées
1. **Créer un mapper `result.json` → API format**
- Fichier: `backend/app/utils/result_mapper.py`
- Fonction: `map_result_json_to_api_payload(result_json) -> BenchmarkPayload`
2. **Mettre à jour les schémas Pydantic**
- Ajouter les nouveaux champs dans `schemas/hardware.py`
3. **Adapter l'API `benchmark.py`**
- Utiliser le mapper
- Sauvegarder les données SMART
4. **Créer la migration Alembic**
```bash
cd backend
alembic revision --autogenerate -m "Add RAM stats and SMART data"
alembic upgrade head
```
5. **Tester le flux complet**
```bash
# 1. Démarrer le serveur
docker-compose up -d
# 2. Exécuter le benchmark
sudo bash scripts/bench.sh --server http://10.0.1.97:8007 --token YOUR_TOKEN
# 3. Vérifier dans la base de données
sqlite3 backend/data/data.db "SELECT * FROM disk_smart_data LIMIT 5;"
```
## 📊 Structure de `result.json` Complète
Voir le fichier [result.json](result.json) pour un exemple réel complet.
### Nouvelles Données vs Ancien Format
| Donnée | Ancien | Nouveau | Location |
|--------|--------|---------|----------|
| RAM utilisée | ❌ | ✅ `ram.used_mb` | `hardware.ram.used_mb` |
| RAM libre | ❌ | ✅ `ram.free_mb` | `hardware.ram.free_mb` |
| RAM partagée | ❌ | ✅ `ram.shared_mb` | `hardware.ram.shared_mb` |
| SMART santé | ❌ | ✅ `smart.health_status` | `hardware.storage[].smart.health_status` |
| SMART heures | ❌ | ✅ `smart.power_on_hours` | `hardware.storage[].smart.power_on_hours` |
| SMART secteurs | ❌ | ✅ `smart.reallocated_sectors` | `hardware.storage[].smart.reallocated_sectors` |
| Wake-on-LAN | ❌ | ✅ `wake_on_lan` | `hardware.network[].wake_on_lan` |
| Network ping | ❌ | ✅ `ping_ms` | `benchmarks.network.ping_ms` |
| IP address | ❌ | ✅ `ip_address` | `hardware.network[].ip_address` |
## 📝 Notes de Migration
### Base de Données
- Nouvelle table: `disk_smart_data`
- Champs ajoutés à `hardware_snapshots`: `ram_used_mb`, `ram_free_mb`, `ram_shared_mb`
- Relations: `HardwareSnapshot` ← (1:N) → `DiskSMART`
### Compatibilité Ascendante
- Les anciens benchmarks sans ces données afficheront `null`
- Le système reste compatible avec les scripts clients plus anciens

247
SMART_GUIDE.md Normal file
View File

@@ -0,0 +1,247 @@
# Guide des Données SMART - Santé et Vieillissement des Disques
## Introduction
Le script [script_test.sh](script_test.sh) collecte automatiquement les données SMART (Self-Monitoring, Analysis and Reporting Technology) pour tous les disques physiques détectés. Ces données permettent de surveiller l'état de santé et le vieillissement des disques durs (HDD) et SSD.
## Données Collectées
### Indicateurs Communs (HDD et SSD)
#### 1. État de Santé Global
```json
"health_status": "PASSED" // ou "FAILED" ou null
```
- **PASSED** : Le disque passe tous les auto-tests SMART ✅
- **FAILED** : Le disque a échoué aux tests SMART 🔴 (remplacer immédiatement)
- **null** : SMART non supporté ou données non disponibles
#### 2. Heures de Fonctionnement
```json
"power_on_hours": 12543
```
- Nombre total d'heures pendant lesquelles le disque a été alimenté
- **Interprétation** :
- < 10,000h : Jeune
- 10,000-30,000h : Utilisé normalement
- \> 30,000h : Vieillissant (> 3.4 ans de fonctionnement continu)
- \> 50,000h : Très vieux (> 5.7 ans)
#### 3. Cycles d'Alimentation
```json
"power_cycle_count": 1876
```
- Nombre de fois où le disque a été allumé/éteint
- Les arrêts/démarrages fréquents peuvent réduire la durée de vie
#### 4. Température
```json
"temperature_celsius": 42
```
- Température actuelle du disque en degrés Celsius
- **Plages recommandées** :
- HDD : 25-45°C optimal, < 50°C acceptable
- SSD : 30-50°C optimal, < 70°C acceptable
- ⚠️ > 60°C (HDD) ou > 80°C (SSD) : Surchauffe dangereuse
### Indicateurs Critiques de Défaillance
#### 5. Secteurs Réalloués
```json
"reallocated_sectors": 0
```
- Nombre de secteurs défectueux qui ont été remplacés par des secteurs de réserve
- **CRITIQUE** : Ce compteur ne devrait JAMAIS augmenter sur un disque sain
- **Interprétation** :
- **0** : Disque sain ✅
- **1-10** : Début de défaillance ⚠️ (surveiller de près)
- **> 10** : Défaillance avancée 🔴 (planifier le remplacement)
- **> 100** : Défaillance critique 🔴 (remplacer immédiatement)
#### 6. Secteurs en Attente de Réallocation
```json
"pending_sectors": 0
```
- Secteurs instables en attente de réallocation
- **TRÈS CRITIQUE** : Indique une défaillance imminente
- **Interprétation** :
- **0** : Normal ✅
- **> 0** : Défaillance imminente 🔴 (sauvegarder et remplacer MAINTENANT)
#### 7. Erreurs CRC UDMA
```json
"udma_crc_errors": 0
```
- Erreurs de transmission de données (câble SATA/USB ou interface défectueux)
- **Interprétation** :
- **0** : Connexion saine ✅
- **1-5** : Problème de câble/connexion ⚠️ (vérifier le câble)
- **> 10** : Câble défectueux ou problème d'interface 🔴
### Indicateurs Spécifiques aux SSD
#### 8. Wear Leveling Count
```json
"wear_leveling_count": 97
```
- Représente le pourcentage de durée de vie restante des cellules NAND
- Généralement affiché comme une valeur de 0 à 100
- **Interprétation** :
- **100-80** : SSD neuf/très bon état ✅
- **79-50** : Usure normale ✅
- **49-20** : Usure avancée ⚠️
- **< 20** : Fin de vie proche 🔴 (planifier remplacement)
#### 9. Total LBAs Written
```json
"total_lbas_written": 45678901234
```
- Volume total de données écrites sur le SSD (en blocs logiques)
- Permet de calculer l'usure totale du SSD
- **Calcul approximatif de données écrites** :
- `Total_GB_Written = (total_lbas_written × 512) / 1,073,741,824`
- Exemple : 45,678,901,234 LBAs ≈ 21.8 TB écrits
## Exemple de Sortie Complète
### Disque SSD Sain
```json
{
"device": "nvme0n1",
"model": "Samsung SSD 970 EVO Plus 500GB",
"size_gb": "465.8",
"type": "ssd",
"interface": "nvme",
"serial": "S4EWNX0R123456",
"smart": {
"health_status": "PASSED",
"power_on_hours": 8543,
"power_cycle_count": 1234,
"temperature_celsius": 38,
"reallocated_sectors": 0,
"pending_sectors": 0,
"udma_crc_errors": 0,
"wear_leveling_count": 95,
"total_lbas_written": 12345678901
}
}
```
**Diagnostic** : Disque en excellent état ✅
### Disque HDD avec Début de Défaillance
```json
{
"device": "sda",
"model": "WD Blue 1TB",
"size_gb": "931.5",
"type": "hdd",
"interface": "sata",
"serial": "WD-WCC1234567890",
"smart": {
"health_status": "PASSED",
"power_on_hours": 42567,
"power_cycle_count": 3456,
"temperature_celsius": 41,
"reallocated_sectors": 8,
"pending_sectors": 0,
"udma_crc_errors": 1,
"wear_leveling_count": null,
"total_lbas_written": null
}
}
```
**Diagnostic** :
- ⚠️ 8 secteurs réalloués : Début de défaillance
- ⚠️ 42,567 heures (4.8 ans) : Disque vieillissant
- **Action** : Surveiller de près, planifier un remplacement préventif
### Disque Défaillant (À Remplacer)
```json
{
"device": "sdb",
"model": "Seagate Barracuda 2TB",
"size_gb": "1863.0",
"type": "hdd",
"interface": "sata",
"serial": "ST2000DM001-XXXXXXX",
"smart": {
"health_status": "FAILED",
"power_on_hours": 67890,
"power_cycle_count": 5678,
"temperature_celsius": 52,
"reallocated_sectors": 156,
"pending_sectors": 3,
"udma_crc_errors": 45,
"wear_leveling_count": null,
"total_lbas_written": null
}
}
```
**Diagnostic** :
- 🔴 `health_status: "FAILED"` : Test SMART échoué
- 🔴 156 secteurs réalloués : Défaillance massive
- 🔴 3 secteurs en attente : Défaillance active en cours
- 🔴 52°C : Surchauffe
- **Action** : SAUVEGARDER IMMÉDIATEMENT et remplacer le disque
## Commandes de Diagnostic Manuel
### Voir les données SMART brutes
```bash
sudo smartctl -A /dev/sda
```
### Tester la santé d'un disque
```bash
sudo smartctl -H /dev/sda
```
### Voir toutes les informations SMART
```bash
sudo smartctl -a /dev/sda
```
### Lancer un test court (5 minutes)
```bash
sudo smartctl -t short /dev/sda
# Attendre 5 minutes, puis vérifier les résultats
sudo smartctl -l selftest /dev/sda
```
### Lancer un test long (plusieurs heures)
```bash
sudo smartctl -t long /dev/sda
# Attendre la fin du test (peut prendre 2-12h selon le disque)
sudo smartctl -l selftest /dev/sda
```
## Recommandations
### Surveillance Régulière
- Exécuter le script de benchmark **mensuellement** pour suivre l'évolution
- Comparer les valeurs SMART d'un mois à l'autre
- Toute augmentation de `reallocated_sectors` ou `pending_sectors` est un signal d'alarme
### Planification de Remplacement
- **Remplacer immédiatement** si :
- `health_status: "FAILED"`
- `pending_sectors > 0`
- `reallocated_sectors > 100`
- **Planifier un remplacement** si :
- `reallocated_sectors` augmente régulièrement
- `power_on_hours > 50,000` sur un HDD
- `wear_leveling_count < 20` sur un SSD
- Température constamment > 55°C (HDD) ou > 75°C (SSD)
### Sauvegardes
- **Sauvegarde 3-2-1** :
- **3** copies de vos données
- **2** supports différents
- **1** copie hors site
## Références
- [Wikipedia SMART](https://en.wikipedia.org/wiki/S.M.A.R.T.)
- [Backblaze Hard Drive Stats](https://www.backblaze.com/blog/backblaze-drive-stats-for-2024/) - Statistiques de défaillance réelles
- [smartmontools Documentation](https://www.smartmontools.org/)

129
TEST_BENCH.md Normal file
View File

@@ -0,0 +1,129 @@
# Test du script bench.sh avec corrections
## Corrections appliquées
1.**Migration 002 appliquée** - Ajout de la colonne `network_results_json` à la table `benchmarks`
2.**Payload RAM corrigé** - Ajout de `used_mb`, `free_mb`, `shared_mb` dans le payload JSON
3.**Mode debug implémenté** - Variable `DEBUG_PAYLOAD=1` pour afficher le payload complet
## Comment tester
### Test normal (sans debug)
```bash
sudo bash /home/gilles/Documents/vscode/serv_benchmark/scripts/bench.sh
```
### Test avec debug (affiche le payload)
```bash
DEBUG_PAYLOAD=1 sudo bash /home/gilles/Documents/vscode/serv_benchmark/scripts/bench.sh
```
Le mode debug va :
1. Afficher le payload JSON complet formaté
2. Sauvegarder le fichier dans `/tmp/bench_payload_YYYYMMDD_HHMMSS.json`
3. Demander confirmation avant d'envoyer
## Problèmes résolus
### 1. Erreur HTTP 500
**Cause** : Colonne `network_results_json` manquante dans la base de données
**Solution** : Migration SQL appliquée directement via Docker
```sql
ALTER TABLE benchmarks ADD COLUMN network_results_json TEXT;
```
### 2. Données RAM manquantes dans le payload
**Cause** : Le script collectait les données (used_mb, free_mb, shared_mb) mais ne les incluait pas dans le payload JSON
**Solution** : Ajout des champs dans la construction du payload (lignes 869-871 du script)
**Avant** :
```javascript
ram: {
total_mb: .total_mb,
slots_total: .slots_total,
// ... manque used_mb, free_mb, shared_mb
}
```
**Après** :
```javascript
ram: {
total_mb: .total_mb,
used_mb: .used_mb, // ✅ AJOUTÉ
free_mb: .free_mb, // ✅ AJOUTÉ
shared_mb: .shared_mb, // ✅ AJOUTÉ
slots_total: .slots_total,
// ...
}
```
### 3. Debug du payload
**Implémentation** : Mode debug avec variable d'environnement
```bash
# Activer le debug
DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh
# Le script affichera :
# ════════════════════════════════════════════════════════
# DEBUG: Payload JSON complet
# ════════════════════════════════════════════════════════
# {
# "device_identifier": "lenovo-bureau",
# "bench_script_version": "1.1.0",
# "hardware": {
# "cpu": { ... },
# "ram": {
# "total_mb": 7771,
# "used_mb": 4530, // ✅ Maintenant présent
# "free_mb": 1291, // ✅ Maintenant présent
# "shared_mb": 1950, // ✅ Maintenant présent
# ...
# },
# ...
# },
# "results": {
# "network": {
# "upload_mbps": 427.86,
# "download_mbps": 398.94,
# "ping_ms": 44.16,
# ...
# }
# }
# }
# ════════════════════════════════════════════════════════
# ✓ Payload sauvegardé dans: /tmp/bench_payload_20251207_233745.json
#
# Appuyez sur Entrée pour continuer l'envoi ou Ctrl+C pour annuler...
```
## Vérification frontend
Après un nouveau benchmark réussi, vérifier dans l'interface web :
1. **Section RAM** : Doit afficher le pourcentage d'utilisation et les détails
```
💾 RAM: 8 GB (58% utilisé)
Utilisée: 4GB • Libre: 1GB • Partagée: 2GB
4 / 4 slots
```
2. **Section Réseau** : Doit afficher les interfaces avec WoL et les résultats iperf3
```
🔌 eno1 (ethernet) [WoL ✓]
IP: 10.0.1.169
MAC: xx:xx:xx:xx:xx:xx
Vitesse: 1000 Mbps
📈 Résultats Benchmark Réseau (iperf3)
↑ 427.86 ↓ 398.94 44.16 41.34
Upload Mbps Download Mbps Ping ms Score
```
## Prochaines étapes
1. Lancer un nouveau benchmark : `sudo bash scripts/bench.sh`
2. Vérifier qu'il n'y a plus d'erreur HTTP 500
3. Consulter la page device detail dans l'interface web
4. Vérifier l'affichage des nouvelles données RAM et réseau

267
USAGE_DEBUG.md Normal file
View File

@@ -0,0 +1,267 @@
# Mode DEBUG - Guide d'utilisation
## Configuration du mode DEBUG
Le mode DEBUG permet d'afficher le payload JSON complet avant l'envoi au serveur et de le sauvegarder dans un fichier.
### Option 1 : Activer directement dans le script (permanent)
Éditez le fichier `scripts/bench.sh` à la ligne 37 :
```bash
# Avant (debug désactivé)
DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-0}"
# Après (debug activé en permanence)
DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-1}"
```
Puis exécutez normalement :
```bash
sudo bash scripts/bench.sh
```
### Option 2 : Activer via variable d'environnement (temporaire)
Sans modifier le script, utilisez :
```bash
DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh
```
**Note** : Cette méthode nécessite `DEBUG_PAYLOAD=1` à chaque exécution.
## Ce que fait le mode DEBUG
Lorsque `DEBUG_PAYLOAD=1` :
1. **Affiche le payload complet** formaté avec `jq` :
```
════════════════════════════════════════════════════════
DEBUG: Payload JSON complet
════════════════════════════════════════════════════════
{
"device_identifier": "lenovo-bureau",
"bench_script_version": "1.1.0",
"hardware": {
"cpu": {
"vendor": "GenuineIntel",
"model": "Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz",
"cores": 4,
"threads": 4,
...
},
"ram": {
"total_mb": 7771,
"used_mb": 4530,
"free_mb": 1291,
"shared_mb": 1950,
...
},
"network": {
"interfaces": [
{
"name": "eno1",
"type": "ethernet",
"mac": "xx:xx:xx:xx:xx:xx",
"ip": "10.0.1.169",
"speed_mbps": 1000,
"wake_on_lan": true
}
]
}
},
"results": {
"cpu": { "score": 12.17, ... },
"memory": { "score": 0.0, ... },
"disk": { "score": 67.65, ... },
"network": {
"upload_mbps": 427.86,
"download_mbps": 398.94,
"ping_ms": 44.16,
"score": 41.34
},
"global_score": 25.15
}
}
════════════════════════════════════════════════════════
```
2. **Sauvegarde dans un fichier** avec timestamp :
```
✓ Payload sauvegardé dans: /tmp/bench_payload_20251207_234512.json
```
3. **Attend confirmation** avant d'envoyer :
```
Appuyez sur Entrée pour continuer l'envoi ou Ctrl+C pour annuler...
```
## Utilisation pratique
### Vérifier les données avant envoi
```bash
# Activer le debug
DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh
# Le script s'arrête avant l'envoi
# Vous pouvez alors :
# - Vérifier le payload affiché
# - Consulter le fichier JSON sauvegardé
# - Appuyer sur Entrée pour continuer
# - Ou Ctrl+C pour annuler
```
### Sauvegarder le payload sans envoyer
```bash
# Lancer avec debug
DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh
# Quand le script demande confirmation :
# Appuyez sur Ctrl+C pour annuler l'envoi
# Le payload est déjà sauvegardé dans /tmp/bench_payload_*.json
```
### Analyser les payloads sauvegardés
```bash
# Lister tous les payloads sauvegardés
ls -lh /tmp/bench_payload_*.json
# Afficher le dernier payload
jq '.' /tmp/bench_payload_*.json | tail -n 1
# Comparer deux payloads
diff <(jq -S '.' /tmp/bench_payload_20251207_234512.json) \
<(jq -S '.' /tmp/bench_payload_20251207_235612.json)
```
## Vérifications importantes
Avec le mode DEBUG activé, vous pouvez vérifier que :
### ✅ Données RAM complètes
```json
"ram": {
"total_mb": 7771,
"used_mb": 4530, // Présent ✓
"free_mb": 1291, // Présent ✓
"shared_mb": 1950 // Présent ✓
}
```
### ✅ Données réseau Wake-on-LAN
```json
"network": {
"interfaces": [
{
"wake_on_lan": true // Présent ✓
}
]
}
```
### ✅ Résultats iperf3
```json
"results": {
"network": {
"upload_mbps": 427.86, // Présent ✓
"download_mbps": 398.94, // Présent ✓
"ping_ms": 44.16, // Présent ✓
"score": 41.34
}
}
```
## Désactiver le mode DEBUG
### Méthode 1 : Dans le script (permanent)
Éditez `scripts/bench.sh` ligne 37 :
```bash
DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-0}" # Remettre à 0
```
### Méthode 2 : Sans variable d'environnement
Simplement exécuter :
```bash
sudo bash scripts/bench.sh # Sans DEBUG_PAYLOAD=1
```
## Nettoyage des fichiers debug
Les fichiers de debug s'accumulent dans `/tmp/`. Pour les nettoyer :
```bash
# Supprimer tous les payloads de debug
rm /tmp/bench_payload_*.json
# Ou supprimer les payloads de plus de 7 jours
find /tmp -name "bench_payload_*.json" -mtime +7 -delete
```
## Cas d'usage
### 1. Développement / Tests
Activez le debug en permanence dans le script pendant le développement.
### 2. Debugging d'erreurs
Si vous avez une erreur HTTP 422 ou 500, activez le debug pour voir exactement ce qui est envoyé.
### 3. Validation après modifications
Après modification du script, vérifiez que toutes les données attendues sont présentes.
### 4. Documentation
Sauvegardez un payload exemple pour la documentation de l'API.
## Exemple complet
```bash
# Terminal 1 : Lancer avec debug
gilles@serveur:~$ DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh
# ... Le script collecte les données ...
# Affichage du payload complet
════════════════════════════════════════════════════════
DEBUG: Payload JSON complet
════════════════════════════════════════════════════════
{
"device_identifier": "lenovo-bureau",
...
}
════════════════════════════════════════════════════════
✓ Payload sauvegardé dans: /tmp/bench_payload_20251207_234512.json
Appuyez sur Entrée pour continuer l'envoi ou Ctrl+C pour annuler...
# [Vous appuyez sur Entrée]
✓ Envoi du payload vers: http://10.0.1.97:8007/api/benchmark
✓ Payload envoyé avec succès (HTTP 200)
# Terminal 2 : Analyser le payload sauvegardé
gilles@serveur:~$ jq '.hardware.ram' /tmp/bench_payload_20251207_234512.json
{
"total_mb": 7771,
"used_mb": 4530,
"free_mb": 1291,
"shared_mb": 1950,
...
}
```
## Résumé
| Action | Commande |
|--------|----------|
| Activer temporairement | `DEBUG_PAYLOAD=1 sudo bash scripts/bench.sh` |
| Activer en permanence | Éditer ligne 37 du script : `DEBUG_PAYLOAD="${DEBUG_PAYLOAD:-1}"` |
| Désactiver | `sudo bash scripts/bench.sh` (sans DEBUG_PAYLOAD=1) |
| Voir les payloads sauvegardés | `ls /tmp/bench_payload_*.json` |
| Analyser un payload | `jq '.' /tmp/bench_payload_YYYYMMDD_HHMMSS.json` |
| Nettoyer | `rm /tmp/bench_payload_*.json` |

2457
analyse_chatgpt.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,9 @@ async def submit_benchmark(
# RAM # RAM
ram_total_mb=hw.ram.total_mb if hw.ram else None, ram_total_mb=hw.ram.total_mb if hw.ram else None,
ram_used_mb=hw.ram.used_mb if hw.ram else None, # NEW
ram_free_mb=hw.ram.free_mb if hw.ram else None, # NEW
ram_shared_mb=hw.ram.shared_mb if hw.ram else None, # NEW
ram_slots_total=hw.ram.slots_total if hw.ram else None, ram_slots_total=hw.ram.slots_total if hw.ram else None,
ram_slots_used=hw.ram.slots_used if hw.ram else None, ram_slots_used=hw.ram.slots_used if hw.ram else None,
ram_ecc=hw.ram.ecc if hw.ram else None, ram_ecc=hw.ram.ecc if hw.ram else None,
@@ -128,6 +131,16 @@ async def submit_benchmark(
if results.global_score is not None: if results.global_score is not None:
global_score = results.global_score global_score = results.global_score
# Extract network results for easier frontend access
network_results = None
if results.network:
network_results = {
"upload_mbps": results.network.upload_mbps if hasattr(results.network, 'upload_mbps') else None,
"download_mbps": results.network.download_mbps if hasattr(results.network, 'download_mbps') else None,
"ping_ms": results.network.ping_ms if hasattr(results.network, 'ping_ms') else None,
"score": results.network.score
}
benchmark = Benchmark( benchmark = Benchmark(
device_id=device.id, device_id=device.id,
hardware_snapshot_id=snapshot.id, hardware_snapshot_id=snapshot.id,
@@ -141,7 +154,8 @@ async def submit_benchmark(
network_score=results.network.score if results.network else None, network_score=results.network.score if results.network else None,
gpu_score=results.gpu.score if results.gpu else None, gpu_score=results.gpu.score if results.gpu else None,
details_json=json.dumps(results.dict()) details_json=json.dumps(results.dict()),
network_results_json=json.dumps(network_results) if network_results else None
) )
db.add(benchmark) db.add(benchmark)

View File

@@ -10,5 +10,6 @@ Base = declarative_base()
from app.models.device import Device # noqa from app.models.device import Device # noqa
from app.models.hardware_snapshot import HardwareSnapshot # noqa from app.models.hardware_snapshot import HardwareSnapshot # noqa
from app.models.benchmark import Benchmark # noqa from app.models.benchmark import Benchmark # noqa
from app.models.disk_smart import DiskSMART # noqa
from app.models.manufacturer_link import ManufacturerLink # noqa from app.models.manufacturer_link import ManufacturerLink # noqa
from app.models.document import Document # noqa from app.models.document import Document # noqa

View File

@@ -30,6 +30,7 @@ class Benchmark(Base):
# Details # Details
details_json = Column(Text, nullable=False) # JSON object with all raw results details_json = Column(Text, nullable=False) # JSON object with all raw results
network_results_json = Column(Text, nullable=True) # Network benchmark details (iperf3)
notes = Column(Text, nullable=True) notes = Column(Text, nullable=True)
# Relationships # Relationships

View File

@@ -0,0 +1,48 @@
"""
Linux BenchTools - Disk SMART Data Model
"""
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from app.db.base import Base
class DiskSMART(Base):
"""
SMART health and aging data for storage devices
"""
__tablename__ = "disk_smart_data"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
hardware_snapshot_id = Column(Integer, ForeignKey("hardware_snapshots.id"), nullable=False, index=True)
captured_at = Column(DateTime, nullable=False, default=datetime.utcnow)
# Disk identification
device_name = Column(String(50), nullable=False) # e.g., "sda", "nvme0n1"
model = Column(String(255), nullable=True)
serial_number = Column(String(100), nullable=True)
size_gb = Column(Float, nullable=True)
disk_type = Column(String(20), nullable=True) # "ssd" or "hdd"
interface = Column(String(50), nullable=True) # "sata", "nvme", "usb"
# SMART Health Status
health_status = Column(String(20), nullable=True) # "PASSED", "FAILED", or null
temperature_celsius = Column(Integer, nullable=True)
# Aging indicators
power_on_hours = Column(Integer, nullable=True)
power_cycle_count = Column(Integer, nullable=True)
reallocated_sectors = Column(Integer, nullable=True) # Critical: bad sectors
pending_sectors = Column(Integer, nullable=True) # Very critical: imminent failure
udma_crc_errors = Column(Integer, nullable=True) # Cable/interface issues
# SSD-specific
wear_leveling_count = Column(Integer, nullable=True) # 0-100 (higher is better)
total_lbas_written = Column(Float, nullable=True) # Total data written
# Relationship
hardware_snapshot = relationship("HardwareSnapshot", back_populates="disk_smart_data")
def __repr__(self):
return f"<DiskSMART(id={self.id}, device='{self.device_name}', health='{self.health_status}')>"

View File

@@ -34,6 +34,9 @@ class HardwareSnapshot(Base):
# RAM # RAM
ram_total_mb = Column(Integer, nullable=True) ram_total_mb = Column(Integer, nullable=True)
ram_used_mb = Column(Integer, nullable=True) # NEW: RAM utilisée
ram_free_mb = Column(Integer, nullable=True) # NEW: RAM libre
ram_shared_mb = Column(Integer, nullable=True) # NEW: RAM partagée (tmpfs/vidéo)
ram_slots_total = Column(Integer, nullable=True) ram_slots_total = Column(Integer, nullable=True)
ram_slots_used = Column(Integer, nullable=True) ram_slots_used = Column(Integer, nullable=True)
ram_ecc = Column(Boolean, nullable=True) ram_ecc = Column(Boolean, nullable=True)
@@ -74,6 +77,7 @@ class HardwareSnapshot(Base):
# Relationships # Relationships
device = relationship("Device", back_populates="hardware_snapshots") device = relationship("Device", back_populates="hardware_snapshots")
benchmarks = relationship("Benchmark", back_populates="hardware_snapshot") benchmarks = relationship("Benchmark", back_populates="hardware_snapshot")
disk_smart_data = relationship("DiskSMART", back_populates="hardware_snapshot", cascade="all, delete-orphan")
def __repr__(self): def __repr__(self):
return f"<HardwareSnapshot(id={self.id}, device_id={self.device_id}, captured_at='{self.captured_at}')>" return f"<HardwareSnapshot(id={self.id}, device_id={self.device_id}, captured_at='{self.captured_at}')>"

View File

@@ -35,6 +35,9 @@ class RAMSlot(BaseModel):
class RAMInfo(BaseModel): class RAMInfo(BaseModel):
"""RAM information schema""" """RAM information schema"""
total_mb: int total_mb: int
used_mb: Optional[int] = None # NEW
free_mb: Optional[int] = None # NEW
shared_mb: Optional[int] = None # NEW
slots_total: Optional[int] = None slots_total: Optional[int] = None
slots_used: Optional[int] = None slots_used: Optional[int] = None
ecc: Optional[bool] = None ecc: Optional[bool] = None
@@ -56,7 +59,7 @@ class StorageDevice(BaseModel):
name: str name: str
type: Optional[str] = None type: Optional[str] = None
interface: Optional[str] = None interface: Optional[str] = None
capacity_gb: Optional[int] = None capacity_gb: Optional[float] = None # Changed from int to float
vendor: Optional[str] = None vendor: Optional[str] = None
model: Optional[str] = None model: Optional[str] = None
smart_health: Optional[str] = None smart_health: Optional[str] = None

75
backend/apply_migration.py Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Apply SQL migration to existing database
Usage: python apply_migration.py
"""
import sqlite3
import os
# Database path
DB_PATH = os.path.join(os.path.dirname(__file__), "data", "data.db")
MIGRATION_PATH = os.path.join(os.path.dirname(__file__), "migrations", "001_add_ram_stats_and_smart.sql")
def apply_migration():
"""Apply the SQL migration"""
if not os.path.exists(DB_PATH):
print(f"❌ Database not found at {DB_PATH}")
print(" The database will be created automatically on first run.")
return
if not os.path.exists(MIGRATION_PATH):
print(f"❌ Migration file not found at {MIGRATION_PATH}")
return
print(f"📂 Database: {DB_PATH}")
print(f"📄 Migration: {MIGRATION_PATH}")
print()
# Read migration SQL
with open(MIGRATION_PATH, 'r') as f:
migration_sql = f.read()
# Connect to database
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
try:
# Check if columns already exist
cursor.execute("PRAGMA table_info(hardware_snapshots)")
columns = [row[1] for row in cursor.fetchall()]
if 'ram_used_mb' in columns:
print("⚠️ Migration already applied (ram_used_mb column exists)")
# Check if disk_smart_data table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='disk_smart_data'")
if cursor.fetchone():
print("⚠️ disk_smart_data table already exists")
print("✅ Database is up to date")
return
# Apply migration
print("🔄 Applying migration...")
cursor.executescript(migration_sql)
conn.commit()
print("✅ Migration applied successfully!")
print()
print("New columns added to hardware_snapshots:")
print(" - ram_used_mb")
print(" - ram_free_mb")
print(" - ram_shared_mb")
print()
print("New table created:")
print(" - disk_smart_data")
except sqlite3.Error as e:
print(f"❌ Error applying migration: {e}")
conn.rollback()
finally:
conn.close()
if __name__ == "__main__":
apply_migration()

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
Apply SQL migration 002 to existing database
Migration 002: Add network_results_json column to benchmarks table
Usage: python apply_migration_002.py
"""
import sqlite3
import os
# Database path
DB_PATH = os.path.join(os.path.dirname(__file__), "data", "data.db")
MIGRATION_PATH = os.path.join(os.path.dirname(__file__), "migrations", "002_add_network_results.sql")
def apply_migration():
"""Apply the SQL migration 002"""
if not os.path.exists(DB_PATH):
print(f"❌ Database not found at {DB_PATH}")
print(" The database will be created automatically on first run.")
return
if not os.path.exists(MIGRATION_PATH):
print(f"❌ Migration file not found at {MIGRATION_PATH}")
return
print(f"📂 Database: {DB_PATH}")
print(f"📄 Migration: {MIGRATION_PATH}")
print()
# Read migration SQL
with open(MIGRATION_PATH, 'r') as f:
migration_sql = f.read()
# Connect to database
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
try:
# Check if column already exists
cursor.execute("PRAGMA table_info(benchmarks)")
columns = [row[1] for row in cursor.fetchall()]
if 'network_results_json' in columns:
print("⚠️ Migration 002 already applied (network_results_json column exists)")
print("✅ Database is up to date")
return
# Apply migration
print("🔄 Applying migration 002...")
cursor.executescript(migration_sql)
conn.commit()
print("✅ Migration 002 applied successfully!")
print()
print("New column added to benchmarks:")
print(" - network_results_json")
except sqlite3.Error as e:
print(f"❌ Error applying migration: {e}")
conn.rollback()
finally:
conn.close()
if __name__ == "__main__":
apply_migration()

View File

@@ -0,0 +1,43 @@
-- Migration 001: Add RAM statistics and SMART data table
-- Date: 2025-12-07
-- Description: Adds used_mb, free_mb, shared_mb to hardware_snapshots and creates disk_smart_data table
-- Add new RAM columns to hardware_snapshots
ALTER TABLE hardware_snapshots ADD COLUMN ram_used_mb INTEGER;
ALTER TABLE hardware_snapshots ADD COLUMN ram_free_mb INTEGER;
ALTER TABLE hardware_snapshots ADD COLUMN ram_shared_mb INTEGER;
-- Create disk_smart_data table
CREATE TABLE IF NOT EXISTS disk_smart_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hardware_snapshot_id INTEGER NOT NULL,
captured_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Disk identification
device_name VARCHAR(50) NOT NULL,
model VARCHAR(255),
serial_number VARCHAR(100),
size_gb REAL,
disk_type VARCHAR(20), -- 'ssd' or 'hdd'
interface VARCHAR(50), -- 'sata', 'nvme', 'usb'
-- SMART Health Status
health_status VARCHAR(20), -- 'PASSED', 'FAILED', or NULL
temperature_celsius INTEGER,
-- Aging indicators
power_on_hours INTEGER,
power_cycle_count INTEGER,
reallocated_sectors INTEGER, -- Critical: bad sectors
pending_sectors INTEGER, -- Very critical: imminent failure
udma_crc_errors INTEGER, -- Cable/interface issues
-- SSD-specific
wear_leveling_count INTEGER, -- 0-100 (higher is better)
total_lbas_written REAL, -- Total data written
FOREIGN KEY (hardware_snapshot_id) REFERENCES hardware_snapshots(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_disk_smart_hardware_snapshot ON disk_smart_data(hardware_snapshot_id);
CREATE INDEX IF NOT EXISTS idx_disk_smart_device ON disk_smart_data(device_name);

View File

@@ -0,0 +1,4 @@
-- Migration 002: Add network_results_json column to benchmarks table
-- Date: 2025-12-07
ALTER TABLE benchmarks ADD COLUMN network_results_json TEXT;

View File

@@ -28,6 +28,17 @@ services:
networks: networks:
- benchtools - benchtools
iperf3:
image: networkstatic/iperf3
container_name: linux_benchtools_iperf3
command: ["-s"]
ports:
- "5201:5201/tcp"
- "5201:5201/udp"
restart: unless-stopped
networks:
- benchtools
networks: networks:
benchtools: benchtools:
driver: bridge driver: bridge

View File

@@ -64,6 +64,16 @@
</div> </div>
</div> </div>
<!-- Network Details -->
<div class="card">
<div class="card-header">🌐 Informations Réseau Détaillées</div>
<div class="card-body">
<div id="networkDetails">
<div class="loading">Chargement...</div>
</div>
</div>
</div>
<!-- Tabs --> <!-- Tabs -->
<div class="tabs-container"> <div class="tabs-container">
<div class="tabs"> <div class="tabs">

View File

@@ -24,25 +24,29 @@
</header> </header>
<!-- Main Content --> <!-- Main Content -->
<main class="container"> <main class="container" style="max-width: 100%; padding: 0 1rem;">
<!-- Search Bar --> <!-- Two-Panel Layout -->
<div class="search-bar"> <div style="display: flex; gap: 1rem; height: calc(100vh - 200px); margin-top: 1rem;">
<span class="search-icon">🔍</span> <!-- Left Panel: Device List (1/5 width) -->
<input <div style="flex: 0 0 20%; display: flex; flex-direction: column; background: var(--card-bg); border-radius: 8px; overflow: hidden;">
type="text" <div style="padding: 1rem; border-bottom: 1px solid var(--border-color); font-weight: 600; font-size: 1.1rem;">
id="searchInput" 📋 Devices
class="search-input" </div>
placeholder="Rechercher par hostname, description ou tags..." <div id="deviceList" style="flex: 1; overflow-y: auto; padding: 0.5rem;">
> <div class="loading">Chargement...</div>
</div> </div>
</div>
<!-- Devices Grid --> <!-- Right Panel: Device Details (4/5 width) -->
<div id="devicesContainer"> <div style="flex: 1; background: var(--card-bg); border-radius: 8px; overflow-y: auto;">
<div class="loading">Chargement des devices</div> <div id="deviceDetailsContainer" style="padding: 2rem;">
<div style="text-align: center; color: var(--text-secondary); padding: 4rem 2rem;">
<div style="font-size: 3rem; margin-bottom: 1rem;">📊</div>
<p style="font-size: 1.1rem;">Sélectionnez un device dans la liste pour afficher ses détails</p>
</div>
</div>
</div>
</div> </div>
<!-- Pagination -->
<div id="paginationContainer"></div>
</main> </main>
<!-- Footer --> <!-- Footer -->

View File

@@ -36,6 +36,7 @@ async function loadDeviceDetail() {
renderDeviceHeader(); renderDeviceHeader();
renderHardwareSummary(); renderHardwareSummary();
renderLastBenchmark(); renderLastBenchmark();
renderNetworkDetails();
await loadBenchmarkHistory(); await loadBenchmarkHistory();
await loadDocuments(); await loadDocuments();
await loadLinks(); await loadLinks();
@@ -86,9 +87,27 @@ function renderHardwareSummary() {
return; return;
} }
// RAM usage info
const ramTotalGB = Math.round((snapshot.ram_total_mb || 0) / 1024);
const ramUsedMB = snapshot.ram_used_mb || 0;
const ramFreeMB = snapshot.ram_free_mb || 0;
const ramSharedMB = snapshot.ram_shared_mb || 0;
let ramValue = `${ramTotalGB} GB`;
if (ramUsedMB > 0 || ramFreeMB > 0) {
const usagePercent = ramTotalGB > 0 ? Math.round((ramUsedMB / (snapshot.ram_total_mb || 1)) * 100) : 0;
ramValue = `${ramTotalGB} GB (${usagePercent}% utilisé)<br><small>Utilisée: ${Math.round(ramUsedMB / 1024)}GB • Libre: ${Math.round(ramFreeMB / 1024)}GB`;
if (ramSharedMB > 0) {
ramValue += ` • Partagée: ${Math.round(ramSharedMB / 1024)}GB`;
}
ramValue += `<br>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>`;
} else {
ramValue += `<br><small>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>`;
}
const hardwareItems = [ const hardwareItems = [
{ label: 'CPU', icon: '🔲', value: `${snapshot.cpu_model || 'N/A'}<br><small>${snapshot.cpu_cores || 0}C / ${snapshot.cpu_threads || 0}T @ ${snapshot.cpu_max_freq_ghz || snapshot.cpu_base_freq_ghz || '?'} GHz</small>` }, { label: 'CPU', icon: '🔲', value: `${snapshot.cpu_model || 'N/A'}<br><small>${snapshot.cpu_cores || 0}C / ${snapshot.cpu_threads || 0}T @ ${snapshot.cpu_max_freq_ghz || snapshot.cpu_base_freq_ghz || '?'} GHz</small>` },
{ label: 'RAM', icon: '💾', value: `${Math.round((snapshot.ram_total_mb || 0) / 1024)} GB<br><small>${snapshot.ram_slots_used || '?'} / ${snapshot.ram_slots_total || '?'} slots</small>` }, { label: 'RAM', icon: '💾', value: ramValue },
{ label: 'GPU', icon: '🎮', value: snapshot.gpu_model || snapshot.gpu_summary || 'N/A' }, { label: 'GPU', icon: '🎮', value: snapshot.gpu_model || snapshot.gpu_summary || 'N/A' },
{ label: 'Stockage', icon: '💿', value: snapshot.storage_summary || 'N/A' }, { label: 'Stockage', icon: '💿', value: snapshot.storage_summary || 'N/A' },
{ label: 'Réseau', icon: '🌐', value: snapshot.network_interfaces_json ? `${JSON.parse(snapshot.network_interfaces_json).length} interface(s)` : 'N/A' }, { label: 'Réseau', icon: '🌐', value: snapshot.network_interfaces_json ? `${JSON.parse(snapshot.network_interfaces_json).length} interface(s)` : 'N/A' },
@@ -141,6 +160,126 @@ function renderLastBenchmark() {
`; `;
} }
// Render network details
function renderNetworkDetails() {
const container = document.getElementById('networkDetails');
const snapshot = currentDevice.last_hardware_snapshot;
const bench = currentDevice.last_benchmark;
if (!snapshot || !snapshot.network_interfaces_json) {
container.innerHTML = '<p style="color: var(--text-muted);">Aucune information réseau disponible</p>';
return;
}
try {
const interfaces = JSON.parse(snapshot.network_interfaces_json);
if (!interfaces || interfaces.length === 0) {
container.innerHTML = '<p style="color: var(--text-muted);">Aucune interface réseau détectée</p>';
return;
}
let html = '<div style="display: grid; gap: 1rem;">';
// Interface details
interfaces.forEach(iface => {
const typeIcon = iface.type === 'ethernet' ? '🔌' : (iface.type === 'wifi' ? '📡' : '🌐');
const wol = iface.wake_on_lan;
const wolBadge = wol === true
? '<span class="badge badge-success" style="margin-left: 0.5rem;">WoL ✓</span>'
: (wol === false ? '<span class="badge badge-muted" style="margin-left: 0.5rem;">WoL ✗</span>' : '');
html += `
<div class="hardware-item" style="border: 1px solid var(--border-color); border-radius: 8px; padding: 1rem;">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 0.75rem;">
<div>
<div style="font-weight: 600; color: var(--color-primary);">${typeIcon} ${escapeHtml(iface.name)}</div>
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem;">${escapeHtml(iface.type || 'unknown')}</div>
</div>
<div>${wolBadge}</div>
</div>
<div style="display: grid; gap: 0.5rem; font-size: 0.9rem;">
${iface.ip ? `<div><strong>IP:</strong> ${escapeHtml(iface.ip)}</div>` : ''}
${iface.mac ? `<div><strong>MAC:</strong> <code>${escapeHtml(iface.mac)}</code></div>` : ''}
${iface.speed_mbps ? `<div><strong>Vitesse:</strong> ${iface.speed_mbps} Mbps</div>` : ''}
${iface.driver ? `<div><strong>Driver:</strong> ${escapeHtml(iface.driver)}</div>` : ''}
</div>
</div>
`;
});
// Network benchmark results (iperf3)
if (bench && bench.network_score !== null && bench.network_score !== undefined) {
let netBenchHtml = '<div style="border: 2px solid var(--color-info); border-radius: 8px; padding: 1rem; margin-top: 1rem;">';
netBenchHtml += '<div style="font-weight: 600; color: var(--color-info); margin-bottom: 0.75rem;">📈 Résultats Benchmark Réseau (iperf3)</div>';
netBenchHtml += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">';
// Try to parse network_results_json if available
let uploadMbps = null;
let downloadMbps = null;
let pingMs = null;
if (bench.network_results_json) {
try {
const netResults = typeof bench.network_results_json === 'string'
? JSON.parse(bench.network_results_json)
: bench.network_results_json;
uploadMbps = netResults.upload_mbps;
downloadMbps = netResults.download_mbps;
pingMs = netResults.ping_ms;
} catch (e) {
console.warn('Failed to parse network_results_json:', e);
}
}
if (uploadMbps !== null && uploadMbps !== undefined) {
netBenchHtml += `
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-success);">↑ ${uploadMbps.toFixed(2)}</div>
<div style="color: var(--text-secondary); font-size: 0.85rem;">Upload Mbps</div>
</div>
`;
}
if (downloadMbps !== null && downloadMbps !== undefined) {
netBenchHtml += `
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-info);">↓ ${downloadMbps.toFixed(2)}</div>
<div style="color: var(--text-secondary); font-size: 0.85rem;">Download Mbps</div>
</div>
`;
}
if (pingMs !== null && pingMs !== undefined) {
netBenchHtml += `
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-warning);">${typeof pingMs === 'number' ? pingMs.toFixed(2) : pingMs}</div>
<div style="color: var(--text-secondary); font-size: 0.85rem;">Ping ms</div>
</div>
`;
}
netBenchHtml += `
<div style="text-align: center;">
<div style="font-size: 1.5rem; font-weight: 600; color: var(--color-primary);">${bench.network_score.toFixed(2)}</div>
<div style="color: var(--text-secondary); font-size: 0.85rem;">Score</div>
</div>
`;
netBenchHtml += '</div></div>';
html += netBenchHtml;
}
html += '</div>';
container.innerHTML = html;
} catch (error) {
console.error('Failed to parse network interfaces:', error);
container.innerHTML = '<p style="color: var(--text-danger);">Erreur lors du parsing des données réseau</p>';
}
}
// Load benchmark history // Load benchmark history
async function loadBenchmarkHistory() { async function loadBenchmarkHistory() {
const container = document.getElementById('benchmarkHistory'); const container = document.getElementById('benchmarkHistory');

View File

@@ -1,194 +1,327 @@
// Linux BenchTools - Devices List Logic // Linux BenchTools - Devices Two-Panel Layout
const { formatRelativeTime, createScoreBadge, getScoreBadgeText, escapeHtml, showError, showEmptyState, formatTags, debounce } = window.BenchUtils; const { formatRelativeTime, createScoreBadge, getScoreBadgeText, escapeHtml, showError, showEmptyState, formatTags } = window.BenchUtils;
const api = window.BenchAPI; const api = window.BenchAPI;
let currentPage = 1;
const pageSize = 20;
let searchQuery = '';
let allDevices = []; let allDevices = [];
let selectedDeviceId = null;
// Load devices // Load devices
async function loadDevices() { async function loadDevices() {
const container = document.getElementById('devicesContainer'); const listContainer = document.getElementById('deviceList');
try { try {
const data = await api.getDevices({ page_size: 1000 }); // Get all for client-side filtering const data = await api.getDevices({ page_size: 1000 }); // Get all devices
allDevices = data.items || []; allDevices = data.items || [];
if (allDevices.length === 0) { if (allDevices.length === 0) {
showEmptyState(container, 'Aucun device trouvé. Exécutez un benchmark sur une machine pour commencer.', '📊'); listContainer.innerHTML = '<div style="padding: 1rem; text-align: center; color: var(--text-secondary);">📊<br>Aucun device</div>';
return; return;
} }
renderDevices(); // Sort by global_score descending
allDevices.sort((a, b) => {
const scoreA = a.last_benchmark?.global_score ?? -1;
const scoreB = b.last_benchmark?.global_score ?? -1;
return scoreB - scoreA;
});
renderDeviceList();
// Auto-select first device if none selected
if (!selectedDeviceId && allDevices.length > 0) {
selectDevice(allDevices[0].id);
}
} catch (error) { } catch (error) {
console.error('Failed to load devices:', error); console.error('Failed to load devices:', error);
showError(container, 'Impossible de charger les devices. Vérifiez que le backend est accessible.'); listContainer.innerHTML = '<div style="padding: 1rem; color: var(--color-danger);">❌ Erreur de chargement</div>';
} }
} }
// Filter devices based on search query // Render device list (left panel)
function filterDevices() { function renderDeviceList() {
if (!searchQuery) { const listContainer = document.getElementById('deviceList');
return allDevices;
}
const query = searchQuery.toLowerCase(); listContainer.innerHTML = allDevices.map(device => {
const globalScore = device.last_benchmark?.global_score;
const isSelected = device.id === selectedDeviceId;
return allDevices.filter(device => { const scoreText = globalScore !== null && globalScore !== undefined
const hostname = (device.hostname || '').toLowerCase(); ? Math.round(globalScore)
const description = (device.description || '').toLowerCase(); : 'N/A';
const tags = (device.tags || '').toLowerCase();
const location = (device.location || '').toLowerCase();
return hostname.includes(query) || const scoreClass = globalScore !== null && globalScore !== undefined
description.includes(query) || ? window.BenchUtils.getScoreBadgeClass(globalScore)
tags.includes(query) || : 'badge';
location.includes(query);
}); return `
<div
class="device-list-item ${isSelected ? 'selected' : ''}"
onclick="selectDevice(${device.id})"
style="
padding: 0.75rem;
margin-bottom: 0.5rem;
background: ${isSelected ? 'var(--color-primary-alpha)' : 'var(--bg-secondary)'};
border: 1px solid ${isSelected ? 'var(--color-primary)' : 'var(--border-color)'};
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
"
onmouseover="if (!this.classList.contains('selected')) this.style.background='var(--bg-hover)'"
onmouseout="if (!this.classList.contains('selected')) this.style.background='var(--bg-secondary)'"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.25rem;">
<div style="font-weight: 600; font-size: 0.95rem; color: var(--text-primary);">
${escapeHtml(device.hostname)}
</div>
<span class="${scoreClass}" style="font-size: 0.75rem; padding: 0.2rem 0.5rem;">
${scoreText}
</span>
</div>
${device.last_benchmark?.run_at ? `
<div style="font-size: 0.75rem; color: var(--text-secondary);">
⏱️ ${formatRelativeTime(device.last_benchmark.run_at)}
</div>
` : '<div style="font-size: 0.75rem; color: var(--color-warning);">⚠️ Pas de benchmark</div>'}
</div>
`;
}).join('');
} }
// Render devices // Select device and display details
function renderDevices() { async function selectDevice(deviceId) {
const container = document.getElementById('devicesContainer'); selectedDeviceId = deviceId;
const filteredDevices = filterDevices(); renderDeviceList(); // Update selection in list
if (filteredDevices.length === 0) { const detailsContainer = document.getElementById('deviceDetailsContainer');
showEmptyState(container, 'Aucun device ne correspond à votre recherche.', '🔍'); detailsContainer.innerHTML = '<div class="loading">Chargement des détails...</div>';
return;
try {
const device = await api.getDevice(deviceId);
renderDeviceDetails(device);
} catch (error) {
console.error('Failed to load device details:', error);
showError(detailsContainer, 'Impossible de charger les détails du device.');
} }
// Sort by global_score descending
const sortedDevices = filteredDevices.sort((a, b) => {
const scoreA = a.last_benchmark?.global_score ?? -1;
const scoreB = b.last_benchmark?.global_score ?? -1;
return scoreB - scoreA;
});
// Pagination
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedDevices = sortedDevices.slice(startIndex, endIndex);
// Render device cards
container.innerHTML = paginatedDevices.map(device => createDeviceCard(device)).join('');
// Render pagination
renderPagination(filteredDevices.length);
} }
// Create device card HTML // Render device details (right panel)
function createDeviceCard(device) { function renderDeviceDetails(device) {
const detailsContainer = document.getElementById('deviceDetailsContainer');
const snapshot = device.last_hardware_snapshot;
const bench = device.last_benchmark; const bench = device.last_benchmark;
// Hardware summary
const cpuModel = snapshot?.cpu_model || 'N/A';
const cpuCores = snapshot?.cpu_cores || '?';
const cpuThreads = snapshot?.cpu_threads || '?';
const ramTotalGB = Math.round((snapshot?.ram_total_mb || 0) / 1024);
const ramUsedMB = snapshot?.ram_used_mb || 0;
const ramFreeMB = snapshot?.ram_free_mb || 0;
const ramSharedMB = snapshot?.ram_shared_mb || 0;
const gpuSummary = snapshot?.gpu_summary || 'N/A';
const storage = snapshot?.storage_summary || 'N/A';
const osName = snapshot?.os_name || 'N/A';
const kernelVersion = snapshot?.kernel_version || 'N/A';
// RAM usage calculation
let ramUsageHtml = `${ramTotalGB} GB`;
if (ramUsedMB > 0 || ramFreeMB > 0) {
const usagePercent = ramTotalGB > 0 ? Math.round((ramUsedMB / (snapshot.ram_total_mb || 1)) * 100) : 0;
ramUsageHtml = `
${ramTotalGB} GB (${usagePercent}% utilisé)<br>
<small style="color: var(--text-secondary);">
Utilisée: ${Math.round(ramUsedMB / 1024)}GB •
Libre: ${Math.round(ramFreeMB / 1024)}GB${ramSharedMB > 0 ? ` • Partagée: ${Math.round(ramSharedMB / 1024)}GB` : ''}
</small>
`;
}
// Benchmark scores
const globalScore = bench?.global_score; const globalScore = bench?.global_score;
const cpuScore = bench?.cpu_score; const cpuScore = bench?.cpu_score;
const memScore = bench?.memory_score; const memScore = bench?.memory_score;
const diskScore = bench?.disk_score; const diskScore = bench?.disk_score;
const netScore = bench?.network_score; const netScore = bench?.network_score;
const gpuScore = bench?.gpu_score; const gpuScore = bench?.gpu_score;
const runAt = bench?.run_at;
const globalScoreHtml = globalScore !== null && globalScore !== undefined const globalScoreHtml = globalScore !== null && globalScore !== undefined
? `<span class="${window.BenchUtils.getScoreBadgeClass(globalScore)}">${getScoreBadgeText(globalScore)}</span>` ? `<span class="${window.BenchUtils.getScoreBadgeClass(globalScore)}" style="font-size: 1.5rem; padding: 0.5rem 1rem;">${getScoreBadgeText(globalScore)}</span>`
: '<span class="badge">N/A</span>'; : '<span class="badge">N/A</span>';
return ` // Network details
<div class="device-card" onclick="window.location.href='device_detail.html?id=${device.id}'"> let networkHtml = '';
<div class="device-card-header"> if (snapshot?.network_interfaces_json) {
<div> try {
<div class="device-card-title">${escapeHtml(device.hostname)}</div> const interfaces = JSON.parse(snapshot.network_interfaces_json);
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem;"> networkHtml = interfaces.map(iface => {
${escapeHtml(device.description || 'Aucune description')} const typeIcon = iface.type === 'ethernet' ? '🔌' : '📡';
const wolBadge = iface.wake_on_lan === true
? '<span class="badge badge-success" style="margin-left: 0.5rem;">WoL ✓</span>'
: '<span class="badge badge-muted" style="margin-left: 0.5rem;">WoL ✗</span>';
return `
<div style="padding: 0.75rem; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 0.5rem;">
<div style="font-weight: 600; margin-bottom: 0.5rem;">
${typeIcon} ${escapeHtml(iface.name)} (${iface.type})${wolBadge}
</div>
<div style="font-size: 0.9rem; color: var(--text-secondary);">
IP: ${iface.ip || 'N/A'} • MAC: ${iface.mac || 'N/A'}<br>
Vitesse: ${iface.speed_mbps ? iface.speed_mbps + ' Mbps' : 'N/A'}
${iface.driver ? ` • Driver: ${iface.driver}` : ''}
</div>
</div> </div>
`;
}).join('');
} catch (e) {
networkHtml = '<p style="color: var(--text-secondary);">Erreur de parsing JSON</p>';
}
}
// Network benchmark results (iperf3)
let netBenchHtml = '';
if (bench?.network_results_json) {
try {
const netResults = JSON.parse(bench.network_results_json);
netBenchHtml = `
<div style="background: var(--bg-secondary); padding: 1rem; border-radius: 6px; margin-top: 1rem;">
<div style="font-weight: 600; margin-bottom: 0.75rem;">📈 Résultats Benchmark Réseau (iperf3)</div>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; text-align: center;">
<div>
<div style="color: var(--color-success); font-size: 1.5rem; font-weight: 600;">
${netResults.upload_mbps?.toFixed(2) || 'N/A'}
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary);">Upload Mbps</div>
</div>
<div>
<div style="color: var(--color-info); font-size: 1.5rem; font-weight: 600;">
${netResults.download_mbps?.toFixed(2) || 'N/A'}
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary);">Download Mbps</div>
</div>
<div>
<div style="color: var(--color-warning); font-size: 1.5rem; font-weight: 600;">
${netResults.ping_ms?.toFixed(2) || 'N/A'}
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary);">Ping ms</div>
</div>
<div>
<div style="color: var(--color-primary); font-size: 1.5rem; font-weight: 600;">
${netResults.score?.toFixed(2) || 'N/A'}
</div>
<div style="font-size: 0.85rem; color: var(--text-secondary);">Score</div>
</div>
</div>
</div>
`;
} catch (e) {
console.error('Error parsing network results:', e);
}
}
detailsContainer.innerHTML = `
<!-- Device Header -->
<div style="border-bottom: 2px solid var(--border-color); padding-bottom: 1.5rem; margin-bottom: 1.5rem;">
<div style="display: flex; justify-content: space-between; align-items: start;">
<div>
<h2 style="margin: 0 0 0.5rem 0; font-size: 2rem;">${escapeHtml(device.hostname)}</h2>
<p style="color: var(--text-secondary); margin: 0;">
${escapeHtml(device.description || 'Aucune description')}
</p>
${device.location ? `<p style="color: var(--text-secondary); margin: 0.25rem 0 0 0;">📍 ${escapeHtml(device.location)}</p>` : ''}
${device.tags ? `<div class="tags" style="margin-top: 0.5rem;">${formatTags(device.tags)}</div>` : ''}
</div> </div>
<div> <div>
${globalScoreHtml} ${globalScoreHtml}
</div> </div>
</div> </div>
</div>
<div class="device-card-meta"> <!-- Benchmark Scores -->
${device.location ? `<span>📍 ${escapeHtml(device.location)}</span>` : ''} ${bench ? `
${bench?.run_at ? `<span>⏱️ ${formatRelativeTime(runAt)}</span>` : ''} <div style="margin-bottom: 2rem;">
<h3 style="margin: 0 0 1rem 0; font-size: 1.3rem;">📊 Scores de Benchmark</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem;">
${createScoreCard(cpuScore, 'CPU', '🔧')}
${createScoreCard(memScore, 'Mémoire', '💾')}
${createScoreCard(diskScore, 'Disque', '💿')}
${createScoreCard(netScore, 'Réseau', '🌐')}
${createScoreCard(gpuScore, 'GPU', '🎮')}
</div> </div>
<div style="margin-top: 0.75rem; color: var(--text-secondary); font-size: 0.9rem;">
⏱️ Dernier benchmark: ${bench.run_at ? formatRelativeTime(bench.run_at) : 'N/A'}
</div>
</div>
` : '<div style="padding: 2rem; background: var(--bg-secondary); border-radius: 6px; text-align: center; color: var(--color-warning); margin-bottom: 2rem;">⚠️ Aucun benchmark disponible</div>'}
${device.tags ? `<div class="tags" style="margin-bottom: 1rem;">${formatTags(device.tags)}</div>` : ''} <!-- Hardware Summary -->
<div style="margin-bottom: 2rem;">
<h3 style="margin: 0 0 1rem 0; font-size: 1.3rem;">🖥️ Résumé Matériel</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
${createInfoCard('🔧 CPU', `${escapeHtml(cpuModel)}<br><small style="color: var(--text-secondary);">${cpuCores} cores / ${cpuThreads} threads</small>`)}
${createInfoCard('💾 RAM', ramUsageHtml)}
${createInfoCard('🎮 GPU', escapeHtml(gpuSummary))}
${createInfoCard('💿 Storage', escapeHtml(storage))}
${createInfoCard('🐧 OS', `${escapeHtml(osName)}<br><small style="color: var(--text-secondary);">Kernel: ${escapeHtml(kernelVersion)}</small>`)}
${createInfoCard('⏰ Créé le', new Date(device.created_at).toLocaleDateString('fr-FR'))}
</div>
</div>
<div class="device-card-scores"> <!-- Network Details -->
${createScoreBadge(cpuScore, 'CPU')} ${networkHtml || netBenchHtml ? `
${createScoreBadge(memScore, 'MEM')} <div style="margin-bottom: 2rem;">
${createScoreBadge(diskScore, 'DISK')} <h3 style="margin: 0 0 1rem 0; font-size: 1.3rem;">🌐 Détails Réseau</h3>
${createScoreBadge(netScore, 'NET')} ${networkHtml}
${createScoreBadge(gpuScore, 'GPU')} ${netBenchHtml}
</div>
` : ''}
<!-- Actions -->
<div style="margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<a href="device_detail.html?id=${device.id}" class="btn btn-primary" style="text-decoration: none; display: inline-block;">
📄 Voir la page complète
</a>
</div>
`;
}
// Create score card for display
function createScoreCard(score, label, icon) {
const scoreValue = score !== null && score !== undefined ? Math.round(score) : 'N/A';
const badgeClass = score !== null && score !== undefined
? window.BenchUtils.getScoreBadgeClass(score)
: 'badge';
return `
<div style="background: var(--bg-secondary); padding: 1rem; border-radius: 6px; text-align: center;">
<div style="font-size: 1.5rem; margin-bottom: 0.25rem;">${icon}</div>
<div style="font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 0.5rem;">${label}</div>
<div class="${badgeClass}" style="font-size: 1.25rem; padding: 0.25rem 0.75rem; display: inline-block;">
${scoreValue}
</div> </div>
</div> </div>
`; `;
} }
// Render pagination // Create info card
function renderPagination(totalItems) { function createInfoCard(label, value) {
const container = document.getElementById('paginationContainer'); return `
<div style="background: var(--bg-secondary); padding: 1rem; border-radius: 6px;">
if (totalItems <= pageSize) { <div style="font-weight: 600; margin-bottom: 0.5rem; color: var(--text-primary);">${label}</div>
container.innerHTML = ''; <div style="color: var(--text-secondary);">${value}</div>
return;
}
const totalPages = Math.ceil(totalItems / pageSize);
container.innerHTML = `
<div class="pagination">
<button
class="pagination-btn"
onclick="changePage(${currentPage - 1})"
${currentPage === 1 ? 'disabled' : ''}
>
← Précédent
</button>
<span class="pagination-info">
Page ${currentPage} sur ${totalPages}
</span>
<button
class="pagination-btn"
onclick="changePage(${currentPage + 1})"
${currentPage === totalPages ? 'disabled' : ''}
>
Suivant →
</button>
</div> </div>
`; `;
} }
// Change page
function changePage(page) {
currentPage = page;
renderDevices();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// Handle search
const handleSearch = debounce((value) => {
searchQuery = value;
currentPage = 1;
renderDevices();
}, 300);
// Initialize devices page // Initialize devices page
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
loadDevices(); loadDevices();
// Setup search
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', (e) => handleSearch(e.target.value));
// Refresh every 30 seconds // Refresh every 30 seconds
setInterval(loadDevices, 30000); setInterval(loadDevices, 30000);
}); });
// Make changePage available globally // Make selectDevice available globally
window.changePage = changePage; window.selectDevice = selectDevice;

145
result.json Executable file
View File

@@ -0,0 +1,145 @@
{
"metadata": {
"script_version": "1.0.0",
"timestamp": "2025-12-07T19:47:34Z"
},
"system": {
"hostname": "lenovo-bureau",
"os": {
"name": "debian",
"version": "13 (trixie)",
"kernel_version": "6.12.57+deb13-amd64",
"architecture": "x86_64"
}
},
"hardware": {
"cpu": {
"vendor": "GenuineIntel",
"model": "Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz",
"cores": 4,
"threads": 4,
"base_freq_ghz": null,
"max_freq_ghz": 3.40,
"cache_l1_kb": 256,
"cache_l2_kb": 1,
"cache_l3_kb": 6,
"flags": [
"pclmulqdq",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"avx"
]
},
"ram": {
"total_mb": 7771,
"used_mb": 6123,
"free_mb": 923,
"shared_mb": 760,
"slots_total": 4,
"slots_used": 4,
"ecc": false,
"layout": [
{
"slot": "A1DIMM0",
"size_mb": 2048,
"type": "DDR3",
"speed_mhz": 1333,
"manufacturer": "Samsung"
},
{
"slot": "A1DIMM1",
"size_mb": 2048,
"type": "DDR3",
"speed_mhz": 1333,
"manufacturer": "Samsung"
},
{
"slot": "A1DIMM2",
"size_mb": 2048,
"type": "DDR3",
"speed_mhz": 1333,
"manufacturer": "Kingston"
},
{
"slot": "A1DIMM3",
"size_mb": 2048,
"type": "DDR3",
"speed_mhz": 1333,
"manufacturer": "Kingston"
}
]
},
"gpu": {
"vendor": "Intel",
"model": "Intel Corporation 2nd Generation Core Processor Family Integrated Graphics Controller (rev 09)",
"vram_mb": null
},
"motherboard": {
"manufacturer": "LENOVO",
"model": "Unknown",
"bios_vendor": "LENOVO",
"bios_version": "9HKT43AUS",
"bios_date": "07/11/2011"
},
"storage": [
{
"device": "sda",
"model": "KINGSTON SA400S37480G",
"size_gb": "447.1",
"type": "ssd",
"interface": "sata",
"serial": "50026B77833E25E3",
"smart": {
"health_status": "PASSED",
"power_on_hours": 7101,
"power_cycle_count": 780,
"temperature_celsius": 24,
"reallocated_sectors": null,
"pending_sectors": null,
"udma_crc_errors": null,
"wear_leveling_count": null,
"total_lbas_written": null
}
}
],
"network": [
{
"name": "eno1",
"type": "ethernet",
"mac": "44:37:e6:6b:53:86",
"ip_address": "10.0.1.169",
"speed_mbps": 1000,
"wake_on_lan": true
}
]
},
"benchmarks": {
"cpu": {
"events_per_sec": 1206.65,
"duration_s": 10.0028,
"score": 12.06
},
"memory": {
"throughput_mib_s": 1024.00,
"score": 10.24
},
"disk": {
"read_mb_s": 7.92,
"write_mb_s": 7.90,
"iops_read": 2029,
"iops_write": 2024,
"latency_ms": 0.385,
"score": 7.91
},
"network": {
"upload_mbps": 530.59,
"download_mbps": 399.57,
"ping_ms": 53.954,
"score": 46.50
},
"gpu": null,
"global_score": 10.85
}
}

1442
result_bench.md Normal file

File diff suppressed because it is too large Load Diff

968
script_test.sh Executable file
View File

@@ -0,0 +1,968 @@
#!/usr/bin/env bash
#
# Linux BenchTools - Script de Test Complet
# Version: 1.0.0
#
# Ce script collecte les informations hardware et exécute les benchmarks
# Génère un fichier result.json dans le dossier courant
#
set -e
# Couleurs pour l'affichage
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Variables globales
SCRIPT_VERSION="1.0.0"
OUTPUT_FILE="result.json"
TOTAL_STEPS=8
CURRENT_STEP=0
# Forcer locale en anglais pour parsing
export LC_ALL=C
# Ajouter /usr/sbin au PATH pour accéder à dmidecode, smartctl, ethtool
export PATH="/usr/sbin:/sbin:$PATH"
# Fonctions d'affichage
log_step() {
CURRENT_STEP=$((CURRENT_STEP + 1))
echo -e "${BLUE}[${CURRENT_STEP}/${TOTAL_STEPS}]${NC} ${GREEN}$1${NC}"
}
log_info() {
echo -e " ${GREEN}${NC} $1"
}
log_warn() {
echo -e " ${YELLOW}${NC} $1"
}
log_error() {
echo -e " ${RED}${NC} $1"
}
safe_bc() {
local expr="$1"
# Renvoie 0 si bc plante, au lieu de laisser une chaîne vide
local out
out=$(echo "$expr" | bc 2>/dev/null) || out="0"
echo "$out"
}
# Vérifier les permissions sudo
check_sudo() {
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo -e "${YELLOW} Linux BenchTools - Script de Test${NC}"
echo -e "${YELLOW} Version ${SCRIPT_VERSION}${NC}"
echo -e "${YELLOW}════════════════════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Ce script nécessite les permissions sudo pour :${NC}"
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. Veuillez relancer avec sudo."
exit 1
fi
# Garder sudo actif pendant toute la durée du script
while true; do sudo -n true; sleep 50; kill -0 "$$" || exit; done 2>/dev/null &
log_info "Permissions sudo OK"
echo ""
}
# Vérifier et installer les dépendances
check_dependencies() {
local missing_essential=()
local missing_bench=()
local missing_optional=()
echo -e "${BLUE}Vérification des dépendances...${NC}"
# Outils essentiels
for tool in curl jq lscpu free lsblk ip bc; do
if ! command -v $tool &> /dev/null; then
missing_essential+=($tool)
fi
done
# Outils de benchmark
for tool in sysbench fio iperf3; do
if ! command -v $tool &> /dev/null; then
missing_bench+=($tool)
fi
done
# Outils optionnels (mais recommandés)
for tool in dmidecode smartctl ethtool lspci; do
if ! command -v $tool &> /dev/null; then
missing_optional+=($tool)
fi
done
# Installer les paquets manquants
local to_install=()
# Mapper les commandes vers les paquets Debian/Ubuntu
declare -A package_map=(
[curl]="curl"
[jq]="jq"
[lscpu]="util-linux"
[free]="procps"
[lsblk]="util-linux"
[ip]="iproute2"
[bc]="bc"
[sysbench]="sysbench"
[fio]="fio"
[iperf3]="iperf3"
[dmidecode]="dmidecode"
[smartctl]="smartmontools"
[ethtool]="ethtool"
[lspci]="pciutils"
)
# Collecter tous les paquets à installer
for tool in "${missing_essential[@]}" "${missing_bench[@]}" "${missing_optional[@]}"; do
local package="${package_map[$tool]}"
if [[ -n "$package" ]] && [[ ! " ${to_install[@]} " =~ " ${package} " ]]; then
to_install+=($package)
fi
done
# Installer si nécessaire
if [[ ${#to_install[@]} -gt 0 ]]; then
echo ""
log_warn "Paquets manquants détectés : ${to_install[*]}"
echo -e "${BLUE}Installation automatique des dépendances...${NC}"
echo ""
# Mettre à jour la liste des paquets
echo "► Mise à jour de la liste des paquets..."
if ! sudo apt-get update -qq 2>&1 | grep -v "Policy will reject signature"; then
log_warn "Avertissement lors de apt-get update (continuer quand même)"
fi
echo " Liste mise à jour"
# Installer les paquets (avec sortie pour voir les erreurs)
echo "► Installation de : ${to_install[*]}"
local install_output
install_output=$(sudo apt-get install -y "${to_install[@]}" 2>&1)
local install_code=$?
# Afficher la sortie (sans les warnings de signature)
echo "$install_output" | grep -v "Policy will reject signature" || true
if [[ $install_code -eq 0 ]]; then
log_info "Installation terminée avec succès"
# Forcer le shell à recharger le PATH pour voir les nouvelles commandes
echo "► Rechargement du PATH..."
hash -r
# Vérifier que les commandes sont maintenant disponibles
echo "► Vérification post-installation..."
for tool in dmidecode smartctl ethtool; do
if command -v $tool &> /dev/null; then
echo "$tool : $(which $tool)"
else
echo "$tool : NON TROUVÉ après installation"
fi
done
else
log_error "Erreur lors de l'installation (code: $install_code)"
echo "Veuillez installer manuellement : sudo apt-get install ${to_install[*]}"
exit 1
fi
echo ""
else
log_info "Toutes les dépendances sont déjà installées"
fi
}
#
# ÉTAPE 1 : Collecte informations système de base
#
collect_system_info() {
log_step "Collecte des informations système de base"
local hostname=$(hostname)
local os_name=$(grep '^ID=' /etc/os-release | cut -d= -f2 | tr -d '"')
local os_version=$(grep '^VERSION=' /etc/os-release | cut -d= -f2 | tr -d '"')
local kernel=$(uname -r)
local arch=$(uname -m)
SYSTEM_INFO=$(jq -n \
--arg hostname "$hostname" \
--arg os_name "$os_name" \
--arg os_version "$os_version" \
--arg kernel "$kernel" \
--arg arch "$arch" \
'{
hostname: $hostname,
os: {
name: $os_name,
version: $os_version,
kernel_version: $kernel,
architecture: $arch
}
}')
log_info "Hostname: $hostname"
log_info "OS: $os_name $os_version"
log_info "Kernel: $kernel"
}
#
# ÉTAPE 2 : Collecte informations CPU
#
collect_cpu_info() {
log_step "Collecte des informations CPU"
local vendor=$(lscpu | grep 'Vendor ID' | awk '{print $3}')
local model=$(lscpu | sed -n 's/^Model name:[ \t]*//p' | xargs)
local cores=$(lscpu | awk '/^CPU\(s\):/ {print $2}')
local threads=$(nproc)
# Fréquences
local cpu_mhz=$(lscpu | grep 'CPU MHz:' | awk '{print $3}')
local cpu_max_mhz=$(lscpu | grep 'CPU max MHz:' | awk '{print $4}')
local cpu_min_mhz=$(lscpu | grep 'CPU min MHz:' | awk '{print $4}')
# Convertir en GHz
local base_freq_ghz="null"
local max_freq_ghz="null"
if [[ -n "$cpu_mhz" ]]; then
base_freq_ghz=$(echo "scale=2; $cpu_mhz / 1000" | bc)
fi
if [[ -n "$cpu_max_mhz" ]]; then
max_freq_ghz=$(echo "scale=2; $cpu_max_mhz / 1000" | bc)
fi
# Caches
local cache_l1d=$(lscpu | grep 'L1d cache:' | awk '{print $3}' | sed 's/K//' | sed 's/M/*1024/' | bc 2>/dev/null || echo "null")
local cache_l1i=$(lscpu | grep 'L1i cache:' | awk '{print $3}' | sed 's/K//' | sed 's/M/*1024/' | bc 2>/dev/null || echo "null")
local cache_l2=$(lscpu | grep 'L2 cache:' | awk '{print $3}' | sed 's/K//' | sed 's/M/*1024/' | bc 2>/dev/null || echo "null")
local cache_l3=$(lscpu | grep 'L3 cache:' | awk '{print $3}' | sed 's/K//' | sed 's/M/*1024/' | bc 2>/dev/null || echo "null")
# Calculer L1 total
local cache_l1_kb="null"
if [[ "$cache_l1d" != "null" && "$cache_l1i" != "null" ]]; then
cache_l1_kb=$(echo "$cache_l1d + $cache_l1i" | bc)
fi
# Flags CPU (limiter aux plus importants)
local flags=$(lscpu | grep 'Flags:' | sed 's/Flags:[ \t]*//')
local important_flags="avx,avx2,sse4_1,sse4_2,aes,vt-x,vmx,svm"
local flags_array="[]"
if [[ -n "$flags" ]]; then
flags_array=$(echo "$flags" | tr ' ' '\n' | grep -E "^(avx|avx2|sse4_1|sse4_2|aes|pclmulqdq|sha|rdrand|rdseed|popcnt)$" | jq -R . | jq -s .)
fi
CPU_INFO=$(jq -n \
--arg vendor "$vendor" \
--arg model "$model" \
--argjson cores "${cores:-0}" \
--argjson threads "${threads:-0}" \
--argjson base_freq "$base_freq_ghz" \
--argjson max_freq "$max_freq_ghz" \
--argjson l1 "${cache_l1_kb:-null}" \
--argjson l2 "${cache_l2:-null}" \
--argjson l3 "${cache_l3:-null}" \
--argjson flags "$flags_array" \
'{
vendor: $vendor,
model: $model,
cores: $cores,
threads: $threads,
base_freq_ghz: $base_freq,
max_freq_ghz: $max_freq,
cache_l1_kb: $l1,
cache_l2_kb: $l2,
cache_l3_kb: $l3,
flags: $flags
}')
log_info "CPU: $model"
log_info "Cores: $cores, Threads: $threads"
log_info "Freq: ${base_freq_ghz}GHz - ${max_freq_ghz}GHz"
}
#
# ÉTAPE 3 : Collecte informations RAM
#
collect_ram_info() {
log_step "Collecte des informations RAM"
# Statistiques RAM de l'OS (total, used, free, shared)
local mem_line=$(free -m | grep '^Mem:')
local total_mb=$(echo "$mem_line" | awk '{print $2}')
local used_mb=$(echo "$mem_line" | awk '{print $3}')
local free_mb=$(echo "$mem_line" | awk '{print $4}')
local shared_mb=$(echo "$mem_line" | awk '{print $5}') # RAM partagée (inclut tmpfs, vidéo partagée, etc.)
# Infos détaillées avec dmidecode (nécessite sudo)
local slots_total="null"
local slots_used="null"
local ecc="null"
local layout="[]"
# dmidecode nécessite sudo pour fonctionner
echo " [DEBUG] Vérification dmidecode..."
if command -v dmidecode &> /dev/null; then
echo " [DEBUG] dmidecode trouvé: $(which dmidecode)"
# Nombre de slots
echo " [DEBUG] Exécution: sudo dmidecode -t 16..."
slots_total=$(sudo dmidecode -t 16 2>/dev/null | grep 'Number Of Devices' | awk '{print $4}' | head -1)
echo " [DEBUG] Slots total: $slots_total"
# ECC
if sudo dmidecode -t 16 2>/dev/null | grep 'Error Correction Type' | grep -q 'None'; then
ecc="false"
else
ecc="true"
fi
# Layout des barrettes (parsing complexe)
local 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:/ && /[0-9]+ MT/ { speed=$2 }
/Manufacturer:/ {
manu=$2;
if (size > 0) {
print slot "," size "," type "," speed "," manu;
}
slot=""; size=0; type=""; speed=""; manu="";
}
')
# Convertir en JSON
if [[ -n "$dimm_data" ]]; then
layout=$(echo "$dimm_data" | while IFS=',' read -r slot size type speed manu; do
jq -n \
--arg slot "$slot" \
--argjson size "$size" \
--arg type "$type" \
--argjson speed "$speed" \
--arg manu "$manu" \
'{slot: $slot, size_mb: $size, type: $type, speed_mhz: $speed, manufacturer: $manu}'
done | jq -s .)
slots_used=$(echo "$layout" | jq 'length')
fi
else
echo " [DEBUG] dmidecode NON trouvé (command -v failed)"
log_warn "dmidecode non disponible - infos RAM limitées"
fi
echo " [DEBUG] Fin collect_ram_info"
RAM_INFO=$(jq -n \
--argjson total "$total_mb" \
--argjson used "$used_mb" \
--argjson free "$free_mb" \
--argjson shared "$shared_mb" \
--argjson slots_total "${slots_total:-null}" \
--argjson slots_used "${slots_used:-null}" \
--argjson ecc "${ecc:-null}" \
--argjson layout "$layout" \
'{
total_mb: $total,
used_mb: $used,
free_mb: $free,
shared_mb: $shared,
slots_total: $slots_total,
slots_used: $slots_used,
ecc: $ecc,
layout: $layout
}')
log_info "RAM Total: ${total_mb}MB (Utilisée: ${used_mb}MB, Libre: ${free_mb}MB)"
[[ "$shared_mb" -gt 0 ]] && log_info "RAM Partagée: ${shared_mb}MB (tmpfs/vidéo)"
[[ "$slots_used" != "null" ]] && log_info "Slots: ${slots_used}/${slots_total}"
}
#
# ÉTAPE 4 : Collecte informations GPU, Motherboard, BIOS
#
collect_hardware_info() {
log_step "Collecte GPU, Carte mère, BIOS"
# GPU
local gpu_vendor="null"
local gpu_model="null"
local gpu_vram="null"
if command -v lspci &> /dev/null; then
local gpu_info=$(lspci | grep -i 'vga\|3d' | head -1)
if [[ -n "$gpu_info" ]]; then
gpu_model=$(echo "$gpu_info" | sed 's/.*: //' | xargs)
if echo "$gpu_info" | grep -qi 'nvidia'; then
gpu_vendor="NVIDIA"
if command -v nvidia-smi &> /dev/null; then
gpu_vram=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -1)
fi
elif echo "$gpu_info" | grep -qi 'intel'; then
gpu_vendor="Intel"
elif echo "$gpu_info" | grep -qi 'amd\|radeon'; then
gpu_vendor="AMD"
fi
log_info "GPU: $gpu_model"
fi
fi
GPU_INFO=$(jq -n \
--arg vendor "${gpu_vendor}" \
--arg model "${gpu_model}" \
--argjson vram "${gpu_vram:-null}" \
'{vendor: $vendor, model: $model, vram_mb: $vram}')
# Motherboard & BIOS
local mb_manufacturer="null"
local mb_model="null"
local bios_vendor="null"
local bios_version="null"
local bios_date="null"
if command -v dmidecode &> /dev/null; then
mb_manufacturer=$(sudo dmidecode -t 2 | grep 'Manufacturer:' | sed 's/.*: *//' | xargs)
mb_model=$(sudo dmidecode -t 2 | grep 'Product Name:' | sed 's/.*: *//' | xargs)
[[ -z "$mb_model" || "$mb_model" == "To be filled by O.E.M." ]] && mb_model="Unknown"
bios_vendor=$(sudo dmidecode -t 0 | grep 'Vendor:' | sed 's/.*: *//' | xargs)
bios_version=$(sudo dmidecode -t 0 | grep 'Version:' | sed 's/.*: *//' | xargs)
bios_date=$(sudo dmidecode -t 0 | grep 'Release Date:' | sed 's/.*: *//' | xargs)
log_info "Motherboard: $mb_manufacturer $mb_model"
log_info "BIOS: $bios_version ($bios_date)"
fi
MOTHERBOARD_INFO=$(jq -n \
--arg manu "${mb_manufacturer}" \
--arg model "${mb_model}" \
--arg bios_vendor "${bios_vendor}" \
--arg bios_ver "${bios_version}" \
--arg bios_date "${bios_date}" \
'{
manufacturer: $manu,
model: $model,
bios_vendor: $bios_vendor,
bios_version: $bios_ver,
bios_date: $bios_date
}')
}
#
# ÉTAPE 5 : Collecte informations Storage
#
collect_storage_info() {
log_step "Collecte des informations de stockage"
local storage_array="[]"
# Lister uniquement les disques physiques (pas loop, pas ram)
local physical_disks=$(lsblk -d -n -o NAME,TYPE | grep 'disk' | awk '{print $1}')
for disk in $physical_disks; do
# Ignorer les disques de taille 0 (lecteurs vides)
local size_bytes=$(lsblk -d -n -b -o SIZE /dev/$disk)
[[ "$size_bytes" -eq 0 ]] && continue
local size=$(lsblk -d -n -o SIZE /dev/$disk | sed 's/,/./')
local rota=$(lsblk -d -n -o ROTA /dev/$disk)
local tran=$(lsblk -d -n -o TRAN /dev/$disk)
# Type : SSD ou HDD
local disk_type="unknown"
[[ "$rota" == "0" ]] && disk_type="ssd" || disk_type="hdd"
# Interface
local interface="unknown"
[[ -n "$tran" ]] && interface="$tran"
# Modèle et données SMART via smartctl
local model="Unknown"
local serial="Unknown"
local smart_health="null"
local power_on_hours="null"
local power_cycle_count="null"
local temperature="null"
local reallocated_sectors="null"
local pending_sectors="null"
local udma_crc_errors="null"
local wear_leveling="null"
local total_lbas_written="null"
if command -v smartctl &> /dev/null; then
# Informations de base
local smart_info=$(sudo smartctl -i /dev/$disk 2>/dev/null || true)
if [[ -n "$smart_info" ]]; then
model=$(echo "$smart_info" | grep 'Device Model:' | sed 's/.*: *//' | xargs)
[[ -z "$model" ]] && model=$(echo "$smart_info" | grep 'Model Number:' | sed 's/.*: *//' | xargs)
serial=$(echo "$smart_info" | grep 'Serial Number:' | sed 's/.*: *//' | xargs)
fi
# Données SMART (santé et vieillissement)
local smart_all=$(sudo smartctl -A -H /dev/$disk 2>/dev/null || true)
if [[ -n "$smart_all" ]]; then
# État de santé global
if echo "$smart_all" | grep -q 'SMART overall-health self-assessment test result: PASSED'; then
smart_health="PASSED"
elif echo "$smart_all" | grep -q 'SMART overall-health self-assessment test result: FAILED'; then
smart_health="FAILED"
fi
# Heures de fonctionnement (ID 9)
power_on_hours=$(echo "$smart_all" | awk '/Power_On_Hours|Power On Hours/ {print $10}' | head -1)
[[ -z "$power_on_hours" ]] && power_on_hours="null"
# Nombre de cycles d'alimentation (ID 12)
power_cycle_count=$(echo "$smart_all" | awk '/Power_Cycle_Count|Start_Stop_Count/ {print $10}' | head -1)
[[ -z "$power_cycle_count" ]] && power_cycle_count="null"
# Température (ID 194)
temperature=$(echo "$smart_all" | awk '/Temperature_Celsius|Airflow_Temperature/ {print $10}' | head -1)
[[ -z "$temperature" ]] && temperature="null"
# Secteurs réalloués (ID 5) - Important pour la santé
reallocated_sectors=$(echo "$smart_all" | awk '/Reallocated_Sector_Ct/ {print $10}' | head -1)
[[ -z "$reallocated_sectors" ]] && reallocated_sectors="null"
# Secteurs en attente de réallocation (ID 197)
pending_sectors=$(echo "$smart_all" | awk '/Current_Pending_Sector/ {print $10}' | head -1)
[[ -z "$pending_sectors" ]] && pending_sectors="null"
# Erreurs CRC UDMA (ID 199) - Problèmes de câble/interface
udma_crc_errors=$(echo "$smart_all" | awk '/UDMA_CRC_Error_Count/ {print $10}' | head -1)
[[ -z "$udma_crc_errors" ]] && udma_crc_errors="null"
# Pour SSD: Wear Leveling Count (ID 177)
if [[ "$disk_type" == "ssd" ]]; then
wear_leveling=$(echo "$smart_all" | awk '/Wear_Leveling_Count/ {print $4}' | head -1)
[[ -z "$wear_leveling" ]] && wear_leveling="null"
# Total LBAs Written (indicateur d'usure SSD, ID 241)
total_lbas_written=$(echo "$smart_all" | awk '/Total_LBAs_Written/ {print $10}' | head -1)
[[ -z "$total_lbas_written" ]] && total_lbas_written="null"
fi
fi
fi
# Convertir size en GB
local size_gb=$(echo "$size" | sed 's/[^0-9.]//g')
# Créer l'objet SMART
local smart_json=$(jq -n \
--arg health "${smart_health}" \
--argjson power_hours "${power_on_hours:-null}" \
--argjson power_cycles "${power_cycle_count:-null}" \
--argjson temp "${temperature:-null}" \
--argjson realloc "${reallocated_sectors:-null}" \
--argjson pending "${pending_sectors:-null}" \
--argjson crc_errors "${udma_crc_errors:-null}" \
--argjson wear "${wear_leveling:-null}" \
--argjson lbas_written "${total_lbas_written:-null}" \
'{
health_status: $health,
power_on_hours: $power_hours,
power_cycle_count: $power_cycles,
temperature_celsius: $temp,
reallocated_sectors: $realloc,
pending_sectors: $pending,
udma_crc_errors: $crc_errors,
wear_leveling_count: $wear,
total_lbas_written: $lbas_written
}')
local disk_json=$(jq -n \
--arg device "$disk" \
--arg model "$model" \
--arg size "$size_gb" \
--arg type "$disk_type" \
--arg interface "$interface" \
--arg serial "$serial" \
--argjson smart "$smart_json" \
'{device: $device, model: $model, size_gb: $size, type: $type, interface: $interface, serial: $serial, smart: $smart}')
storage_array=$(echo "$storage_array" | jq --argjson disk "$disk_json" '. + [$disk]')
# Log avec infos de santé
local health_info=""
[[ "$smart_health" != "null" ]] && health_info=" - Santé: $smart_health"
[[ "$power_on_hours" != "null" ]] && health_info="$health_info, ${power_on_hours}h"
log_info "Disque: /dev/$disk - $model ($size, $disk_type)$health_info"
done
STORAGE_INFO="$storage_array"
}
#
# ÉTAPE 6 : Collecte informations réseau
#
collect_network_info() {
log_step "Collecte des informations réseau"
local network_array="[]"
# Interfaces actives (ignore loopback, docker, bridges)
for iface in $(ip -br link show | grep -E 'UP|UNKNOWN' | awk '{print $1}'); do
[[ "$iface" =~ ^(lo|docker|br-|veth) ]] && continue
# MAC
local mac=$(ip link show $iface | grep 'link/ether' | awk '{print $2}')
[[ -z "$mac" ]] && continue
# Type
local type="unknown"
if [[ "$iface" =~ ^(eth|eno|enp) ]]; then
type="ethernet"
elif [[ "$iface" =~ ^(wlan|wlp) ]]; then
type="wifi"
fi
# IP address
local ip_addr=$(ip -4 addr show $iface | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
# Vitesse et Wake-on-LAN (nécessite ethtool et ethernet)
local speed="null"
local wol_supported="null"
if command -v ethtool &> /dev/null && [[ "$type" == "ethernet" ]]; then
speed=$(sudo ethtool $iface 2>/dev/null | grep 'Speed:' | awk '{print $2}' | sed 's/Mb\/s//' | sed 's/Unknown!/null/')
[[ -z "$speed" || "$speed" == "Unknown" ]] && speed="null"
# Wake-on-LAN support
local wol_info=$(sudo ethtool $iface 2>/dev/null | grep 'Supports Wake-on:')
if [[ -n "$wol_info" ]]; then
# Si le support contient 'g' (magic packet), WoL est supporté
if echo "$wol_info" | grep -q 'g'; then
wol_supported="true"
else
wol_supported="false"
fi
fi
fi
local net_json=$(jq -n \
--arg name "$iface" \
--arg type "$type" \
--arg mac "$mac" \
--arg ip "${ip_addr:-null}" \
--argjson speed "${speed:-null}" \
--argjson wol "${wol_supported:-null}" \
'{name: $name, type: $type, mac: $mac, ip_address: $ip, speed_mbps: $speed, wake_on_lan: $wol}')
network_array=$(echo "$network_array" | jq --argjson net "$net_json" '. + [$net]')
log_info "Interface: $iface ($type) - IP: ${ip_addr:-N/A}"
done
NETWORK_INFO="$network_array"
}
#
# ÉTAPE 7 : Exécution des benchmarks
#
run_benchmarks() {
log_step "Exécution des benchmarks (peut prendre plusieurs minutes)"
# CPU Benchmark
local cpu_bench="null"
if command -v sysbench &> /dev/null; then
log_info "Benchmark CPU en cours..."
local cpu_result=$(sysbench cpu --cpu-max-prime=20000 --threads=$(nproc) run 2>&1)
local events_per_sec=$(echo "$cpu_result" | grep 'events per second' | awk '{print $4}')
local total_time=$(echo "$cpu_result" | grep 'total time:' | awk '{print $3}' | sed 's/s//')
local cpu_score=$(echo "scale=2; $events_per_sec / 100" | bc)
cpu_bench=$(jq -n \
--argjson eps "${events_per_sec:-0}" \
--argjson time "${total_time:-0}" \
--argjson score "${cpu_score:-0}" \
'{events_per_sec: $eps, duration_s: $time, score: $score}')
log_info "CPU: ${events_per_sec} events/sec (score: ${cpu_score})"
else
log_warn "sysbench non disponible - CPU bench ignoré"
fi
# Memory Benchmark
local mem_bench="null"
if command -v sysbench &> /dev/null; then
log_info "Benchmark Mémoire en cours..."
local mem_result=$(sysbench memory --memory-block-size=1M --memory-total-size=1G run 2>&1)
local throughput=$(echo "$mem_result" | grep 'MiB/sec' | grep -oP '\d+\.\d+' | head -1)
local mem_score=$(echo "scale=2; $throughput / 100" | bc)
mem_bench=$(jq -n \
--argjson tp "${throughput:-0}" \
--argjson score "${mem_score:-0}" \
'{throughput_mib_s: $tp, score: $score}')
log_info "Mémoire: ${throughput} MiB/s (score: ${mem_score})"
fi
# Disk Benchmark (sur le disque principal uniquement, pas /tmp)
local disk_bench="null"
if command -v fio &> /dev/null; then
log_info "Benchmark Disque en cours (cela peut prendre 2-3 minutes)..."
# Utiliser le répertoire home pour éviter /tmp (qui peut être tmpfs)
local test_dir="$HOME/fio-bench-test"
mkdir -p "$test_dir"
local fio_result=$(fio --name=bench --ioengine=libaio --rw=randrw --bs=4k \
--size=500M --numjobs=1 --direct=1 --runtime=30 --time_based \
--filename="$test_dir/testfile" --output-format=json 2>/dev/null || echo '{}')
# Nettoyer
rm -rf "$test_dir"
if [[ "$fio_result" != "{}" ]]; then
local read_bw=$(echo "$fio_result" | jq '.jobs[0].read.bw // 0')
local write_bw=$(echo "$fio_result" | jq '.jobs[0].write.bw // 0')
local read_iops=$(echo "$fio_result" | jq '.jobs[0].read.iops // 0' | cut -d. -f1)
local write_iops=$(echo "$fio_result" | jq '.jobs[0].write.iops // 0' | cut -d. -f1)
local lat_ns=$(echo "$fio_result" | jq '.jobs[0].read.clat_ns.mean // 0')
# Convertir en MB/s et ms
local read_mb=$(echo "scale=2; $read_bw / 1024" | bc)
local write_mb=$(echo "scale=2; $write_bw / 1024" | bc)
local lat_ms=$(echo "scale=3; $lat_ns / 1000000" | bc)
# Score : moyenne read/write MB/s
local disk_score=$(echo "scale=2; ($read_mb + $write_mb) / 2" | bc)
disk_bench=$(jq -n \
--argjson read "$read_mb" \
--argjson write "$write_mb" \
--argjson riops "$read_iops" \
--argjson wiops "$write_iops" \
--argjson lat "$lat_ms" \
--argjson score "$disk_score" \
'{read_mb_s: $read, write_mb_s: $write, iops_read: $riops, iops_write: $wiops, latency_ms: $lat, score: $score}')
log_info "Disque: R=${read_mb}MB/s W=${write_mb}MB/s (score: ${disk_score})"
fi
else
log_warn "fio non disponible - Disk bench ignoré"
fi
# Network Benchmark vers le serveur 10.0.1.97
local net_bench="null"
if command -v iperf3 &> /dev/null; then
log_info "Benchmark Réseau en cours (vers 10.0.1.97)..."
# Test de connectivité d'abord
if ping -c 1 -W 2 10.0.1.97 &> /dev/null; then
# Test upload (client → serveur)
local upload_result=$(iperf3 -c 10.0.1.97 -t 10 -J 2>/dev/null || echo '{}')
local upload_mbps="0"
local download_mbps="0"
local ping_ms="null"
if [[ "$upload_result" != "{}" ]]; then
# Extraire le débit d'upload en bits/sec et convertir en Mbps
local upload_bps=$(echo "$upload_result" | jq '.end.sum_sent.bits_per_second // 0')
upload_mbps=$(echo "scale=2; $upload_bps / 1000000" | bc)
# Test download (serveur → client avec -R)
local download_result=$(iperf3 -c 10.0.1.97 -t 10 -R -J 2>/dev/null || echo '{}')
if [[ "$download_result" != "{}" ]]; then
local download_bps=$(echo "$download_result" | jq '.end.sum_received.bits_per_second // 0')
download_mbps=$(echo "scale=2; $download_bps / 1000000" | bc)
fi
# Mesurer le ping
local ping_output=$(ping -c 5 10.0.1.97 2>/dev/null | grep 'avg' || echo "")
if [[ -n "$ping_output" ]]; then
ping_ms=$(echo "$ping_output" | awk -F'/' '{print $5}')
fi
# Score réseau : moyenne upload/download divisée par 10 (pour avoir un score sur 100)
local net_score=$(echo "scale=2; ($upload_mbps + $download_mbps) / 20" | bc)
net_bench=$(jq -n \
--argjson upload "$upload_mbps" \
--argjson download "$download_mbps" \
--argjson ping "${ping_ms:-null}" \
--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 "Serveur iperf3 sur 10.0.1.97 non accessible (assurez-vous que 'iperf3 -s' tourne)"
fi
else
log_warn "Serveur 10.0.1.97 non joignable - Network bench ignoré"
fi
else
log_warn "iperf3 non disponible - Network bench ignoré"
fi
# GPU Benchmark
local gpu_bench="null"
log_warn "GPU bench non implémenté - ignoré"
# Calculer score global (seulement CPU, RAM, Disk)
# Pondération : CPU=60%, RAM=20%, Disk=20%
local scores=""
local total_weight=0
if [[ "$cpu_bench" != "null" ]]; then
local cpu_score
cpu_score=$(echo "$cpu_bench" | jq '.score // 0')
scores="$scores + $cpu_score * 0.6"
total_weight=$(safe_bc "$total_weight + 0.6")
fi
if [[ "$mem_bench" != "null" ]]; then
local mem_score
mem_score=$(echo "$mem_bench" | jq '.score // 0')
scores="$scores + $mem_score * 0.2"
total_weight=$(safe_bc "$total_weight + 0.2")
fi
if [[ "$disk_bench" != "null" ]]; then
local disk_score
disk_score=$(echo "$disk_bench" | jq '.score // 0')
scores="$scores + $disk_score * 0.2"
total_weight=$(safe_bc "$total_weight + 0.2")
fi
# Nettoyer le préfixe " + " éventuellement
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
# Toujours fournir un JSON valide à jq
if [[ -z "$global_score" ]]; then
global_score=0
fi
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"
}
#
# ÉTAPE 8 : Génération du fichier JSON
#
generate_json() {
log_step "Génération du fichier result.json"
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Assembler le JSON final
jq -n \
--arg version "$SCRIPT_VERSION" \
--arg timestamp "$timestamp" \
--argjson system "$SYSTEM_INFO" \
--argjson cpu "$CPU_INFO" \
--argjson ram "$RAM_INFO" \
--argjson gpu "$GPU_INFO" \
--argjson mb "$MOTHERBOARD_INFO" \
--argjson storage "$STORAGE_INFO" \
--argjson network "$NETWORK_INFO" \
--argjson benchmarks "$BENCHMARK_RESULTS" \
'{
metadata: {
script_version: $version,
timestamp: $timestamp
},
system: $system,
hardware: {
cpu: $cpu,
ram: $ram,
gpu: $gpu,
motherboard: $mb,
storage: $storage,
network: $network
},
benchmarks: $benchmarks
}' > "$OUTPUT_FILE"
log_info "Fichier généré: $OUTPUT_FILE"
# Afficher un résumé
echo ""
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Test terminé avec succès !${NC}"
echo -e "${GREEN}════════════════════════════════════════════════════════${NC}"
echo ""
echo "Fichier de résultat : $(pwd)/$OUTPUT_FILE"
echo "Taille du fichier : $(du -h $OUTPUT_FILE | awk '{print $1}')"
echo ""
echo "Aperçu du résultat :"
echo "-------------------"
jq '{
hostname: .system.hostname,
os: .system.os.name,
cpu: .hardware.cpu.model,
ram_mb: .hardware.ram.total_mb,
storage_count: (.hardware.storage | length),
network_count: (.hardware.network | length),
benchmark_score: .benchmarks.global_score
}' "$OUTPUT_FILE"
echo ""
}
#
# MAIN
#
main() {
# Vérifications préliminaires
check_sudo
check_dependencies
echo ""
echo -e "${BLUE}Début de la collecte et des benchmarks...${NC}"
echo ""
# Exécuter toutes les étapes
collect_system_info
collect_cpu_info
collect_ram_info
collect_hardware_info
collect_storage_info
collect_network_info
run_benchmarks
generate_json
echo -e "${GREEN}✓ Terminé !${NC}"
}
# Lancer le script
main "$@"

File diff suppressed because it is too large Load Diff

332
simple_bench.md Normal file
View File

@@ -0,0 +1,332 @@
# Tests de Benchmarks Individuels
Ce fichier contient les commandes pour tester chaque benchmark individuellement et voir exactement ce qu'ils retournent.
## Prérequis
Vérifier les outils installés :
```bash
which sysbench fio iperf3 dmidecode lscpu free jq lsblk
```
Installer les outils manquants (Debian/Ubuntu) :
```bash
sudo apt-get update
sudo apt-get install sysbench fio iperf3 dmidecode lshw util-linux coreutils jq
```
---
## 1. Test CPU avec sysbench
### Mode court (10000 primes)
```bash
sysbench cpu --cpu-max-prime=10000 --threads=$(nproc) run
```
### Mode normal (20000 primes)
```bash
sysbench cpu --cpu-max-prime=20000 --threads=$(nproc) run
```
### Récupérer uniquement les events/sec
```bash
sysbench cpu --cpu-max-prime=10000 --threads=$(nproc) run 2>&1 | grep 'events per second'
```
**Ce qu'on cherche** : La ligne `events per second: XXXX.XX`
---
## 2. Test Mémoire avec sysbench
### Mode court (512M)
```bash
sysbench memory --memory-block-size=1M --memory-total-size=512M run
```
### Mode normal (1G)
```bash
sysbench memory --memory-block-size=1M --memory-total-size=1G run
```
### Récupérer le throughput
```bash
sysbench memory --memory-block-size=1M --memory-total-size=512M run 2>&1 | grep 'MiB/sec'
```
**Ce qu'on cherche** : Le throughput en MiB/sec
---
## 3. Test Disque avec fio
### Test lecture séquentielle
```bash
fio --name=seqread --ioengine=libaio --rw=read --bs=1M --size=1G --numjobs=1 --direct=1 --filename=/tmp/fio-test-file
```
### Test écriture séquentielle
```bash
fio --name=seqwrite --ioengine=libaio --rw=write --bs=1M --size=1G --numjobs=1 --direct=1 --filename=/tmp/fio-test-file
```
### Test lecture/écriture aléatoire (IOPS)
```bash
fio --name=randrw --ioengine=libaio --rw=randrw --bs=4k --size=100M --numjobs=1 --direct=1 --filename=/tmp/fio-test-file
```
### Récupérer les résultats en JSON
```bash
fio --name=test --ioengine=libaio --rw=randrw --bs=4k --size=100M --numjobs=1 --direct=1 --filename=/tmp/fio-test-file --output-format=json
```
**Ce qu'on cherche** :
- `READ: bw=XXXMiB/s` (throughput lecture)
- `WRITE: bw=XXXMiB/s` (throughput écriture)
- `IOPS=XXXX` (operations/sec)
### Nettoyage
```bash
rm -f /tmp/fio-test-file
```
---
## 4. Test Réseau avec iperf3
### Sur le serveur (10.0.1.97) - Lancer le serveur iperf3
```bash
iperf3 -s
```
### Sur le client - Test upload
```bash
iperf3 -c 10.0.1.97 -t 5
```
### Sur le client - Test download
```bash
iperf3 -c 10.0.1.97 -t 5 -R
```
### Récupérer les résultats en JSON
```bash
iperf3 -c 10.0.1.97 -t 5 -J
```
**Ce qu'on cherche** :
- `bits_per_second` (vitesse en bits/sec)
- Convertir en Mbps : `bits_per_second / 1000000`
---
## 5. Collecte d'infos Hardware
### CPU avec lscpu
```bash
lscpu
```
Extraction des infos importantes :
```bash
echo "Vendor: $(lscpu | grep 'Vendor ID' | awk '{print $3}')"
echo "Model: $(lscpu | grep 'Model name' | sed 's/Model name: *//')"
echo "Cores: $(lscpu | grep '^CPU(s):' | awk '{print $2}')"
echo "Threads: $(nproc)"
echo "Base Freq: $(lscpu | grep 'CPU MHz' | awk '{print $3}')"
echo "Max Freq: $(lscpu | grep 'CPU max MHz' | awk '{print $4}')"
```
### RAM avec free
```bash
free -m
```
Total RAM :
```bash
free -m | grep '^Mem:' | awk '{print $2}'
```
### RAM détaillée avec dmidecode (nécessite sudo)
```bash
sudo dmidecode -t memory
```
Infos RAM structurées :
```bash
sudo dmidecode -t memory | grep -E 'Size|Type:|Speed:|Manufacturer'
```
### GPU avec lspci
```bash
lspci | grep -i vga
lspci | grep -i 3d
```
### GPU NVIDIA avec nvidia-smi (si carte NVIDIA)
```bash
nvidia-smi --query-gpu=gpu_name,memory.total --format=csv,noheader
```
### Carte mère avec dmidecode
```bash
sudo dmidecode -t baseboard | grep -E 'Manufacturer|Product Name'
```
### OS
```bash
cat /etc/os-release
uname -r # kernel version
uname -m # architecture
```
### Disques
```bash
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
```
Avec détails :
```bash
lsblk -J # JSON format
```
### Réseau
```bash
ip link show
```
Interfaces actives :
```bash
ip -br link show | grep UP
```
---
## 6. Test du Script bench.sh (si tu veux)
### Dry-run (voir ce qui serait collecté sans envoyer)
Tu peux commenter la ligne de curl dans le script et juste faire un `echo "$payload"` pour voir le JSON généré.
Ou créer un script de test :
```bash
# Dans le script bench.sh, remplacer la ligne 455-459 par :
echo "=== PAYLOAD JSON ==="
echo "$payload" | jq .
echo "=== END PAYLOAD ==="
```
---
## Format JSON attendu par le backend
Le backend attend un JSON comme ceci :
```json
{
"device_identifier": "hostname",
"bench_script_version": "1.0.0",
"hardware": {
"cpu": {
"vendor": "GenuineIntel",
"model": "Intel(R) Core(TM) i7-9700K CPU @ 3.60GHz",
"microarchitecture": "Coffee Lake",
"cores": 8,
"threads": 8,
"base_freq_ghz": 3.6,
"max_freq_ghz": 4.9,
"cache_l1_kb": 512,
"cache_l2_kb": 2048,
"cache_l3_kb": 12288,
"flags": ["avx", "avx2", "sse4_1", "sse4_2"],
"tdp_w": 95
},
"ram": {
"total_mb": 16384,
"slots_total": 4,
"slots_used": 2,
"ecc": false,
"layout": [
{"slot": "DIMM1", "size_mb": 8192, "type": "DDR4", "speed_mhz": 3200, "manufacturer": "Corsair"},
{"slot": "DIMM2", "size_mb": 8192, "type": "DDR4", "speed_mhz": 3200, "manufacturer": "Corsair"}
]
},
"gpu": {
"vendor": "NVIDIA",
"model": "GeForce RTX 3070",
"vram_mb": 8192,
"driver_version": "525.105.17",
"cuda_version": "12.0"
},
"motherboard": {
"manufacturer": "ASUS",
"model": "ROG STRIX Z390-E GAMING",
"bios_version": "2417",
"bios_date": "08/14/2020"
},
"storage": [
{"device": "nvme0n1", "model": "Samsung SSD 970 EVO Plus", "size_gb": 500, "type": "nvme"},
{"device": "sda", "model": "WD Blue 1TB", "size_gb": 1000, "type": "sata"}
],
"network": [
{"name": "eth0", "speed_mbps": 1000, "type": "ethernet"},
{"name": "wlan0", "speed_mbps": 866, "type": "wifi"}
],
"os": {
"name": "ubuntu",
"version": "22.04",
"kernel_version": "5.15.0-91-generic",
"architecture": "x86_64"
}
},
"results": {
"cpu": {
"events_per_sec": 5234.89,
"duration_s": 10.0,
"score": 52.35
},
"memory": {
"throughput_mib_s": 15234.5,
"score": 76.17
},
"disk": {
"read_mb_s": 3500.0,
"write_mb_s": 3000.0,
"iops_read": 450000,
"iops_write": 400000,
"latency_ms": 0.05,
"score": 85.0
},
"network": {
"upload_mbps": 940.5,
"download_mbps": 950.2,
"ping_ms": 0.5,
"jitter_ms": 0.1,
"packet_loss_percent": 0.0,
"score": 95.0
},
"gpu": null,
"global_score": 77.13
}
}
```
---
## Notes
- Tous les champs optionnels peuvent être `null` ou omis
- Le backend acceptera un JSON partiel tant que les champs obligatoires sont présents :
- `device_identifier` (string)
- `bench_script_version` (string)
- `hardware` (objet, peut être minimaliste)
- `results.global_score` (float 0-100)
---
## Prochaine étape
Une fois que tu auras testé les benchmarks individuellement, copie-colle les résultats ici et on pourra :
1. Adapter le parsing dans le script bench.sh
2. S'assurer que les regex/awk extraient les bonnes valeurs
3. Tester le JSON généré

27
test_path_fix.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Test script pour vérifier que le PATH fix fonctionne
export PATH="/usr/sbin:/sbin:$PATH"
echo "=== Vérification du PATH ==="
echo "PATH actuel: $PATH"
echo ""
echo "=== Vérification des outils ==="
for tool in dmidecode smartctl ethtool sysbench fio iperf3 lscpu free lsblk jq ip; do
if command -v $tool &> /dev/null; then
echo "$tool : $(command -v $tool)"
else
echo "$tool : NON TROUVÉ"
fi
done
echo ""
echo "=== Test dmidecode (nécessite sudo) ==="
if command -v dmidecode &> /dev/null; then
echo "dmidecode est accessible à: $(command -v dmidecode)"
echo "Pour tester: sudo dmidecode -t memory | head -20"
else
echo "dmidecode NON accessible"
fi

150
test_smart.sh Executable file
View File

@@ -0,0 +1,150 @@
#!/usr/bin/env bash
#
# Test rapide des données SMART pour tous les disques
#
set -e
export PATH="/usr/sbin:/sbin:$PATH"
# Couleurs
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e "${BLUE} Test SMART - Santé des Disques${NC}"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo ""
# Vérifier smartctl
if ! command -v smartctl &> /dev/null; then
echo -e "${RED}✗ smartctl non installé${NC}"
echo "Installer avec: sudo apt-get install smartmontools"
exit 1
fi
# Lister les disques physiques
physical_disks=$(lsblk -d -n -o NAME,TYPE | grep 'disk' | awk '{print $1}')
for disk in $physical_disks; do
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Disque: /dev/$disk${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Ignorer les disques vides
size_bytes=$(lsblk -d -n -b -o SIZE /dev/$disk)
if [[ "$size_bytes" -eq 0 ]]; then
echo -e "${YELLOW}⚠ Disque vide, ignoré${NC}"
echo ""
continue
fi
# Infos de base
model=$(sudo smartctl -i /dev/$disk 2>/dev/null | grep 'Device Model:' | sed 's/.*: *//' || echo "Unknown")
[[ -z "$model" ]] && model=$(sudo smartctl -i /dev/$disk 2>/dev/null | grep 'Model Number:' | sed 's/.*: *//' || echo "Unknown")
echo "Modèle: $model"
# Test de santé
health=$(sudo smartctl -H /dev/$disk 2>/dev/null | grep 'SMART overall-health' | awk '{print $NF}')
if [[ "$health" == "PASSED" ]]; then
echo -e "Santé: ${GREEN}✓ PASSED${NC}"
elif [[ "$health" == "FAILED" ]]; then
echo -e "Santé: ${RED}✗ FAILED - REMPLACER IMMÉDIATEMENT${NC}"
else
echo "Santé: Inconnu (SMART peut-être non supporté)"
fi
# Données SMART importantes
smart_data=$(sudo smartctl -A /dev/$disk 2>/dev/null)
if [[ -n "$smart_data" ]]; then
# Heures de fonctionnement
power_hours=$(echo "$smart_data" | awk '/Power_On_Hours|Power On Hours/ {print $10}' | head -1)
if [[ -n "$power_hours" ]]; then
days=$((power_hours / 24))
years=$(echo "scale=1; $power_hours / 8760" | bc)
echo "Temps de fonctionnement: $power_hours heures ($days jours / $years ans)"
fi
# Cycles d'alimentation
power_cycles=$(echo "$smart_data" | awk '/Power_Cycle_Count|Start_Stop_Count/ {print $10}' | head -1)
[[ -n "$power_cycles" ]] && echo "Cycles d'alimentation: $power_cycles"
# Température
temp=$(echo "$smart_data" | awk '/Temperature_Celsius|Airflow_Temperature/ {print $10}' | head -1)
if [[ -n "$temp" ]]; then
if [[ $temp -lt 50 ]]; then
echo -e "Température: ${GREEN}${temp}°C${NC}"
elif [[ $temp -lt 60 ]]; then
echo -e "Température: ${YELLOW}${temp}°C (surveiller)${NC}"
else
echo -e "Température: ${RED}${temp}°C (SURCHAUFFE)${NC}"
fi
fi
# Secteurs réalloués (CRITIQUE)
realloc=$(echo "$smart_data" | awk '/Reallocated_Sector_Ct/ {print $10}' | head -1)
if [[ -n "$realloc" ]]; then
if [[ $realloc -eq 0 ]]; then
echo -e "Secteurs réalloués: ${GREEN}$realloc${NC}"
elif [[ $realloc -lt 10 ]]; then
echo -e "Secteurs réalloués: ${YELLOW}$realloc (début de défaillance)${NC}"
else
echo -e "Secteurs réalloués: ${RED}$realloc (DÉFAILLANCE - planifier remplacement)${NC}"
fi
fi
# Secteurs en attente (TRÈS CRITIQUE)
pending=$(echo "$smart_data" | awk '/Current_Pending_Sector/ {print $10}' | head -1)
if [[ -n "$pending" ]]; then
if [[ $pending -eq 0 ]]; then
echo -e "Secteurs en attente: ${GREEN}$pending${NC}"
else
echo -e "Secteurs en attente: ${RED}$pending (DÉFAILLANCE IMMINENTE - sauvegarder maintenant)${NC}"
fi
fi
# Erreurs CRC
crc=$(echo "$smart_data" | awk '/UDMA_CRC_Error_Count/ {print $10}' | head -1)
if [[ -n "$crc" ]]; then
if [[ $crc -eq 0 ]]; then
echo -e "Erreurs CRC: ${GREEN}$crc${NC}"
else
echo -e "Erreurs CRC: ${YELLOW}$crc (vérifier le câble SATA)${NC}"
fi
fi
# Pour SSD: Wear Leveling
rota=$(lsblk -d -n -o ROTA /dev/$disk)
if [[ "$rota" == "0" ]]; then
wear=$(echo "$smart_data" | awk '/Wear_Leveling_Count/ {print $4}' | head -1)
if [[ -n "$wear" ]]; then
if [[ $wear -gt 50 ]]; then
echo -e "Usure SSD: ${GREEN}$wear%${NC}"
elif [[ $wear -gt 20 ]]; then
echo -e "Usure SSD: ${YELLOW}$wear% (surveiller)${NC}"
else
echo -e "Usure SSD: ${RED}$wear% (fin de vie proche)${NC}"
fi
fi
fi
else
echo -e "${YELLOW}⚠ Données SMART non disponibles pour ce disque${NC}"
fi
echo ""
done
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}✓ Test SMART terminé${NC}"
echo ""
echo "Pour plus de détails sur un disque:"
echo " sudo smartctl -a /dev/sdX"
echo ""
echo "Documentation complète: ./SMART_GUIDE.md"
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"