script
This commit is contained in:
767
ANALYSE_DONNEES.md
Normal file
767
ANALYSE_DONNEES.md
Normal 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
192
CHANGELOG.md
Normal 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
316
DEPLOYMENT_GUIDE.md
Normal 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
248
FIXES_APPLIED.md
Normal 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
257
FRONTEND_UPDATES.md
Normal 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
201
IMPLEMENTATION_STATUS.md
Normal 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
247
SMART_GUIDE.md
Normal 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
129
TEST_BENCH.md
Normal 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
267
USAGE_DEBUG.md
Normal 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
2457
analyse_chatgpt.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
48
backend/app/models/disk_smart.py
Normal file
48
backend/app/models/disk_smart.py
Normal 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}')>"
|
||||||
@@ -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}')>"
|
||||||
|
|||||||
@@ -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
75
backend/apply_migration.py
Executable 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()
|
||||||
66
backend/apply_migration_002.py
Normal file
66
backend/apply_migration_002.py
Normal 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()
|
||||||
43
backend/migrations/001_add_ram_stats_and_smart.sql
Normal file
43
backend/migrations/001_add_ram_stats_and_smart.sql
Normal 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);
|
||||||
4
backend/migrations/002_add_network_results.sql
Normal file
4
backend/migrations/002_add_network_results.sql
Normal 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;
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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
145
result.json
Executable 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
1442
result_bench.md
Normal file
File diff suppressed because it is too large
Load Diff
968
script_test.sh
Executable file
968
script_test.sh
Executable 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 "$@"
|
||||||
1427
scripts/bench.sh
1427
scripts/bench.sh
File diff suppressed because it is too large
Load Diff
332
simple_bench.md
Normal file
332
simple_bench.md
Normal 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
27
test_path_fix.sh
Executable 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
150
test_smart.sh
Executable 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}"
|
||||||
Reference in New Issue
Block a user