diff --git a/01_vision_fonctionnelle.md b/01_vision_fonctionnelle.md new file mode 100644 index 0000000..5afd9be --- /dev/null +++ b/01_vision_fonctionnelle.md @@ -0,0 +1,121 @@ +# 01 – Vision fonctionnelle et objectifs + +## Nom de l’application + +Nom provisoire : **Linux BenchTools** + +Objectif : application self-hosted permettant de : +- Recenser les machines (physiques, VM, SBC type Raspberry Pi). +- Collecter des informations matérielles (CPU, RAM, GPU, stockage, réseau, carte mère, OS). +- Lancer des benchmarks standardisés via un **script client** exécuté sur les machines. +- Recevoir et stocker les résultats de bench sur un **serveur central**. +- Calculer une **note globale** et des sous-scores (CPU, mémoire, disque, réseau, GPU). +- Afficher un **dashboard** triant les machines par performance globale. +- Associer des liens constructeurs et des documents (PDF / images) à chaque machine. + +L’application doit être : +- **Self-hosted** sur une machine Linux (Debian) au format Docker. +- Légère (SQLite, pas de dépendance à une grosse base). +- Accessible sur le réseau local (via reverse proxy existant). +- Exploitable autant pour des serveurs que des PC ou des VMs. + +--- + +## Rôles et usages + +### Rôle principal : utilisateur/admin unique (Gilles) + +Actions possibles : +- Ajouter/modifier une machine (device) et ses métadonnées (description, localisation, tags). +- Visualiser les informations hardware associées à une machine. +- Lancer un bench sur une machine en copiant une commande fournie par l’interface. +- Visualiser l’historique des benchmarks d’une machine. +- Consulter les notes globales et par catégorie (CPU, RAM, disque, réseau, GPU). +- Ajouter/éditer des liens vers les sites constructeurs. +- Uploader des notices PDF, factures, schémas, photos. + +--- + +## Fonctionnalités principales (MVP) + +1. **Gestion des machines (devices)** + - CRUD basique : + - Créer un device (ou auto-création à la réception d’un bench). + - Lire les infos d’un device. + - Modifier la description, la localisation, les tags. + - Un device représente une machine logique (hostname ou nom choisi). + +2. **Snapshots hardware** + - À chaque bench, un snapshot de l’état matériel et OS est enregistré : + - CPU (modèle, cœurs, threads, fréquences, cache, vendor). + - RAM (total, slots, type, capacités). + - GPU (modèle, mémoire, vendor). + - Disques (liste, type, taille, interface, SMART résumé). + - Réseau (interfaces, vitesses, IPs). + - Carte mère (vendor, modèle, BIOS). + - OS (nom, version, kernel, architecture, type machine : bare metal / VM). + - Ces infos sont liées à un `hardware_snapshot` et référencées par un `benchmark`. + +3. **Benchmarks** + - Réception d’un JSON produit par un script Bash exécuté sur la machine. + - Enregistrement d’un `benchmark` avec : + - Date/heure du run. + - Version du script client. + - Scores par catégorie : `cpu_score`, `memory_score`, `disk_score`, `network_score`, `gpu_score`. + - `global_score` calculé selon une formule configurable (par ex. moyenne pondérée). + - Détails bruts en JSON (`details_json`) pour audit. + +4. **Liens constructeur** + - Attacher à un device des liens HTTP : + - Fiche produit constructeur. + - Page support / BIOS / drivers. + - Documentation technique. + +5. **Documents** + - Upload de fichiers (surtout PDF) associés à un device : + - Notices / manuels. + - Factures. + - Photos / schémas. + - Stockage sur disque, références dans SQLite. + +6. **Dashboard Web** + - Page d’accueil : + - Statistiques globales (nombre de devices, nombre total de benchmarks, date dernier bench, score moyen). + - Tableau trié par `global_score` décroissant. + - Indicateur compact par ligne : + - Hostname + description. + - Dernier `global_score`. + - Détail CPU/Mem/Disk/Net/GPU du dernier bench. + - Section "Quick bench script" : + - Affichage de la commande shell à copier/coller pour exécuter le script client. + +--- + +## Non-objectifs (MVP) + +- Pas de multi-tenant ni de gestion avancée de droits utilisateurs. +- Pas de monitoring temps réel (ce n’est pas un outil de supervision). +- Pas de support Windows/macOS dans la première version (Linux seulement). +- Pas d’agent resident/daemon permanent sur les clients (simple script à exécuter ponctuellement). + +--- + +## Contraintes techniques + +- Backend en **Python** (FastAPI recommandé). +- Base de données : **SQLite** (fichier sur disque). +- WebUI simple (HTML/CSS/JS minimal, éventuellement HTMX). +- Déploiement via **Docker** + `docker-compose`. +- Script client en **Bash** (priorité Debian/Ubuntu, extensible ensuite). + +--- + +## Critères de réussite (MVP) + +- Être capable de : + - Déployer l’app via `docker-compose up -d`. + - Copier une commande depuis le Dashboard, l’exécuter sur une machine Linux, et voir : + - Le device apparaître (ou se mettre à jour). + - Un nouveau benchmark visible dans l’interface. + - Voir la machine triée correctement dans le dashboard par `global_score`. +- Pouvoir attacher au moins un PDF à un device et le télécharger depuis l’interface. diff --git a/02_model_donnees.md b/02_model_donnees.md new file mode 100644 index 0000000..7329f0d --- /dev/null +++ b/02_model_donnees.md @@ -0,0 +1,242 @@ + +# 02 – Modèle de données (SQLite) + +Objectif : définir le schéma relationnel pour SQLite, exploité par SQLAlchemy (ou équivalent). + +## Vue d’ensemble + +Tables principales : +- `devices` +- `hardware_snapshots` +- `benchmarks` +- `manufacturer_links` +- `documents` + +Optionnel (plus tard) : +- `users` (si auth interne) +- `settings` (config globale de l’app) + +--- + +## Table devices + +Représente une machine (physique ou virtuelle). + +Champs : + +- `id` (INTEGER, PK, autoincrement) +- `hostname` (TEXT, non nul) +- `fqdn` (TEXT, nullable) +- `description` (TEXT, nullable) +- `asset_tag` (TEXT, nullable) – identifiant interne/inventaire +- `location` (TEXT, nullable) +- `owner` (TEXT, nullable) +- `tags` (TEXT, nullable) – liste séparée par virgules ou JSON +- `created_at` (DATETIME, non nul) +- `updated_at` (DATETIME, non nul) + +Contraintes : +- `hostname` peut être utilisé comme identifiant logique si `device_identifier` n’est pas fourni par le client. +- Index recommandé : `idx_devices_hostname`. + +--- + +## Table hardware_snapshots + +Snapshot détaillé de la configuration matérielle et OS au moment d’un bench. + +Champs : + +- `id` (INTEGER, PK) +- `device_id` (INTEGER, FK -> devices.id) +- `captured_at` (DATETIME, non nul) + +### CPU + +- `cpu_vendor` (TEXT) +- `cpu_model` (TEXT) +- `cpu_microarchitecture` (TEXT, nullable) +- `cpu_cores` (INTEGER) +- `cpu_threads` (INTEGER) +- `cpu_base_freq_ghz` (REAL, nullable) +- `cpu_max_freq_ghz` (REAL, nullable) +- `cpu_cache_l1_kb` (INTEGER, nullable) +- `cpu_cache_l2_kb` (INTEGER, nullable) +- `cpu_cache_l3_kb` (INTEGER, nullable) +- `cpu_flags` (TEXT, nullable) – chaîne ou JSON (AVX, AES, etc.) +- `cpu_tdp_w` (REAL, nullable) + +### RAM + +- `ram_total_mb` (INTEGER) +- `ram_slots_total` (INTEGER, nullable) +- `ram_slots_used` (INTEGER, nullable) +- `ram_ecc` (BOOLEAN, nullable) +- `ram_layout_json` (TEXT, nullable) + Exemple JSON : + ```json + [ + { "slot": "DIMM_A1", "size_mb": 16384, "type": "DDR4", "speed_mhz": 2133, "vendor": "Corsair", "part_number": "XXXX" } + ] + ``` + +### GPU + +- `gpu_summary` (TEXT, nullable) – ex: "Intel UHD 630" +- `gpu_vendor` (TEXT, nullable) +- `gpu_model` (TEXT, nullable) +- `gpu_driver_version` (TEXT, nullable) +- `gpu_memory_dedicated_mb` (INTEGER, nullable) +- `gpu_memory_shared_mb` (INTEGER, nullable) +- `gpu_api_support` (TEXT, nullable) – ex: "OpenGL 4.6, Vulkan" + +### Stockage + +- `storage_summary` (TEXT, nullable) – résumé human readable +- `storage_devices_json` (TEXT, nullable) + Exemple JSON : + ```json + [ + { + "name": "/dev/nvme0n1", + "type": "NVMe", + "interface": "PCIe 3.0 x4", + "capacity_gb": 1000, + "vendor": "Samsung", + "model": "970 EVO Plus", + "smart_health": "PASSED", + "temperature_c": 42 + } + ] + ``` +- `partitions_json` (TEXT, nullable) + +### Réseau + +- `network_interfaces_json` (TEXT, nullable) + Exemple : + ```json + [ + { + "name": "eth0", + "type": "ethernet", + "mac": "aa:bb:cc:dd:ee:ff", + "ip": "10.0.0.10", + "speed_mbps": 1000, + "driver": "e1000e" + } + ] + ``` + +### OS / Carte mère + +- `os_name` (TEXT) +- `os_version` (TEXT) +- `kernel_version` (TEXT) +- `architecture` (TEXT) – "x86_64", "arm64" +- `virtualization_type` (TEXT, nullable) – "kvm", "vmware", "none", etc. +- `motherboard_vendor` (TEXT, nullable) +- `motherboard_model` (TEXT, nullable) +- `bios_version` (TEXT, nullable) +- `bios_date` (TEXT, nullable) + +### Divers + +- `sensors_json` (TEXT, nullable) – valeurs de températures, etc. +- `raw_info_json` (TEXT, nullable) – dump brut de commandes (inxi, lshw, etc.) + +Index recommandés : +- `idx_hw_snapshots_device_id` +- `idx_hw_snapshots_captured_at` + +--- + +## Table benchmarks + +Représente un run de bench. + +Champs : + +- `id` (INTEGER, PK) +- `device_id` (INTEGER, FK -> devices.id) +- `hardware_snapshot_id` (INTEGER, FK -> hardware_snapshots.id) +- `run_at` (DATETIME, non nul) +- `bench_script_version` (TEXT, non nul) + +### Scores + +- `global_score` (REAL, non nul) +- `cpu_score` (REAL, nullable) +- `memory_score` (REAL, nullable) +- `disk_score` (REAL, nullable) +- `network_score` (REAL, nullable) +- `gpu_score` (REAL, nullable) + +### Détails + +- `details_json` (TEXT, non nul) + Contient toutes les valeurs brutes : + - CPU events/sec, temps d’exécution, etc. + - Throughput mémoire. + - Read/write MB/s, IOPS. + - Vitesse iperf3, ping. + - Score GPU, etc. + +- `notes` (TEXT, nullable) + +Index : +- `idx_benchmarks_device_id` +- `idx_benchmarks_run_at` + +--- + +## Table manufacturer_links + +Liens vers les ressources en ligne du constructeur. + +Champs : + +- `id` (INTEGER, PK) +- `device_id` (INTEGER, FK -> devices.id) +- `label` (TEXT, non nul) – ex: "Support HP", "Page produit Lenovo" +- `url` (TEXT, non nul) + +Index : +- `idx_links_device_id` + +--- + +## Table documents + +Fichiers (PDF, images, etc.) associés à un device. + +Champs : + +- `id` (INTEGER, PK) +- `device_id` (INTEGER, FK -> devices.id) +- `doc_type` (TEXT, non nul) – `manual`, `warranty`, `invoice`, `photo`, `other` +- `filename` (TEXT, non nul) – nom original +- `stored_path` (TEXT, non nul) – chemin relatif sur le serveur +- `mime_type` (TEXT, non nul) +- `size_bytes` (INTEGER, non nul) +- `uploaded_at` (DATETIME, non nul) + +Index : +- `idx_documents_device_id` + +--- + +## Éventuelle table settings (optionnelle) + +Pour stocker des paramètres globaux de l’application. + +Champs : + +- `id` (INTEGER, PK) +- `key` (TEXT, unique) +- `value` (TEXT) + +Exemples : +- `default_bench_server_url` +- `default_iperf_server` +- `score_weights_json` (pondération CPU/Mem/Disque/Réseau/GPU) diff --git a/03_api_backend.md b/03_api_backend.md new file mode 100644 index 0000000..39d0548 --- /dev/null +++ b/03_api_backend.md @@ -0,0 +1,537 @@ + +# 03 – Spécification API Backend (FastAPI) + +Objectif : définir précisément les endpoints backend, les schémas JSON échangés et les règles métier associées. + +L’API sera exposée sous `/api`. + +--- + +## 1. Contenu attendu de ce fichier + +Ce document doit servir de référence au développeur backend. Il décrit : + +1. Le format du payload JSON envoyé par le script de benchmark (`bench.sh`) vers le backend. +2. Les endpoints REST nécessaires : + - Réception d’un benchmark. + - Consultation des devices. + - Consultation de l’historique des benchmarks. + - Gestion des liens constructeur. + - Gestion des documents (PDF, images). +3. Les règles d’authentification (token simple). +4. Les codes d’erreur principaux et attentes de validation. + +--- + +## 2. Schéma JSON – payload d’un benchmark + +Endpoint cible : `POST /api/benchmark` + +Le script client envoie un JSON de la forme : + +```json +{ + "device_identifier": "elitedesk-800g3", + "bench_script_version": "1.0.0", + "hardware": { + "cpu": { + "vendor": "Intel", + "model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz", + "microarchitecture": "Skylake", + "cores": 4, + "threads": 8, + "base_freq_ghz": 3.4, + "max_freq_ghz": 4.0, + "cache_l1_kb": 256, + "cache_l2_kb": 1024, + "cache_l3_kb": 8192, + "flags": ["avx", "avx2", "aes"], + "tdp_w": 65 + }, + "ram": { + "total_mb": 65536, + "slots_total": 4, + "slots_used": 4, + "ecc": false, + "layout": [ + { + "slot": "DIMM_A1", + "size_mb": 16384, + "type": "DDR4", + "speed_mhz": 2133, + "vendor": "Corsair", + "part_number": "XYZ" + } + ] + }, + "gpu": { + "vendor": "Intel", + "model": "Intel HD Graphics 530", + "driver_version": "xxx", + "memory_dedicated_mb": null, + "memory_shared_mb": 512, + "api_support": ["OpenGL 4.5"] + }, + "storage": { + "devices": [ + { + "name": "/dev/nvme0n1", + "type": "NVMe", + "interface": "PCIe 3.0 x4", + "capacity_gb": 1000, + "vendor": "Samsung", + "model": "970 EVO Plus", + "smart_health": "PASSED", + "temperature_c": 42 + } + ], + "partitions": [ + { + "name": "/dev/nvme0n1p1", + "mount_point": "/", + "fs_type": "ext4", + "used_gb": 50, + "total_gb": 100 + } + ] + }, + "network": { + "interfaces": [ + { + "name": "eth0", + "type": "ethernet", + "mac": "aa:bb:cc:dd:ee:ff", + "ip": "10.0.0.10", + "speed_mbps": 1000, + "driver": "e1000e" + } + ] + }, + "motherboard": { + "vendor": "HP", + "model": "HP 8054", + "bios_version": "P01 Ver. 02.48", + "bios_date": "2023-05-10" + }, + "os": { + "name": "Debian", + "version": "12 (bookworm)", + "kernel_version": "6.1.0-xx-amd64", + "architecture": "x86_64", + "virtualization_type": "none" + }, + "sensors": { + "cpu_temp_c": 45, + "disk_temps_c": { + "/dev/nvme0n1": 42 + } + }, + "raw_info": { + "lscpu": "raw text…", + "lsblk": "raw text…" + } + }, + "results": { + "cpu": { + "events_per_sec": 12000, + "duration_s": 10, + "score": 90 + }, + "memory": { + "throughput_mib_s": 20000, + "score": 95 + }, + "disk": { + "read_mb_s": 1200, + "write_mb_s": 1000, + "iops_read": 50000, + "iops_write": 45000, + "latency_ms": 1.2, + "score": 94 + }, + "network": { + "upload_mbps": 930, + "download_mbps": 940, + "ping_ms": 1.2, + "jitter_ms": 0.3, + "packet_loss_percent": 0.0, + "score": 88 + }, + "gpu": { + "glmark2_score": null, + "score": null + }, + "global_score": 92 + } +} +``` + +Remarques importantes : + +- Certains champs peuvent être `null` ou absents en fonction de la machine (ex: pas de GPU dédié). +- Le backend doit être robuste à l’absence de certains blocs (ex: pas de `network` si aucun test réseau). + +--- + +## 3. Logique de traitement côté backend + +1. **Auth** + - Lecture du header `Authorization: Bearer `. + - Comparaison avec une valeur stockée dans la config/ENV (`API_TOKEN`). + - Si token invalide ou absent : réponse 401. + +2. **Résolution du device** + - Chercher un enregistrement `devices` avec `hostname == device_identifier`. + - Si trouvé : + - Utiliser `device_id` existant. + - Sinon : + - Créer un nouveau `device` minimal avec : + - `hostname = device_identifier` + - `created_at`, `updated_at` = maintenant. + +3. **Création d’un hardware_snapshot** + - Mapper le contenu `hardware` vers la table `hardware_snapshots` telle que définie dans `02_model_donnees.md`. + - Exemples : + - `hardware.cpu.vendor` -> `cpu_vendor` + - `hardware.ram.total_mb` -> `ram_total_mb` + - `hardware.storage.devices` -> JSON dans `storage_devices_json` + - etc. + - `captured_at` = `now()` (ou `run_at` du benchmark si fourni). + +4. **Création d’un benchmark** + - `run_at` = `now()` (ou fourni explicitement plus tard). + - `bench_script_version` = champ du payload. + - Scores : + - `cpu_score` = `results.cpu.score` (si présent). + - `memory_score` = `results.memory.score` + - `disk_score` = `results.disk.score` + - `network_score` = `results.network.score` + - `gpu_score` = `results.gpu.score` + - `global_score` = `results.global_score` + - `details_json` = JSON complet de `results` (ou éventuellement de `results` + métriques brutes). + +5. **Réponse** + - En cas de succès, renvoyer : + ```json + { + "status": "ok", + "device_id": 1, + "benchmark_id": 42 + } + ``` + +--- + +## 4. Endpoints détaillés + +### 4.1. POST /api/benchmark + +- Rôle : point d’entrée unique des résultats du script client. +- Auth : header `Authorization: Bearer `. +- Body : JSON (voir schéma ci-dessus). +- Codes de réponse : + - `200` – succès. + - `400` – JSON invalide / validation Pydantic échouée. + - `401` – token invalide ou manquant. + - `500` – erreur interne. + +--- + +### 4.2. GET /api/devices + +Objectif : lister les devices avec un résumé de leur dernier benchmark. + +Paramètres query : +- `page` (int, optionnel, défaut 1) +- `page_size` (int, optionnel, défaut 20) +- `search` (string, optionnel – filtre sur hostname/description/tags) + +Réponse (exemple simplifié) : + +```json +{ + "items": [ + { + "id": 1, + "hostname": "elitedesk-800g3", + "description": "HP EliteDesk 800 G3 SFF", + "location": "Bureau", + "tags": ["lab", "dev"], + "last_benchmark": { + "id": 42, + "run_at": "2025-12-07T10:32:00Z", + "global_score": 92, + "cpu_score": 90, + "memory_score": 95, + "disk_score": 94, + "network_score": 88, + "gpu_score": null + } + } + ], + "total": 1, + "page": 1, + "page_size": 20 +} +``` + +--- + +### 4.3. GET /api/devices/{device_id} + +Objectif : récupérer le détail d’un device. + +Réponse (exemple) : + +```json +{ + "id": 1, + "hostname": "elitedesk-800g3", + "fqdn": "elitedesk-800g3.maison43", + "description": "HP EliteDesk 800 G3 SFF (serveur dev)", + "asset_tag": "LAB-001", + "location": "Bureau", + "owner": "Gilles", + "tags": ["lab", "proxmox"], + "created_at": "2025-12-01T10:00:00Z", + "updated_at": "2025-12-07T10:32:00Z", + "last_hardware_snapshot": { + "captured_at": "2025-12-07T10:32:00Z", + "cpu_vendor": "Intel", + "cpu_model": "Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz", + "cpu_cores": 4, + "cpu_threads": 8, + "ram_total_mb": 65536, + "storage_summary": "NVMe 1TB + HDD 2TB", + "gpu_summary": "Intel HD Graphics 530", + "os_name": "Debian", + "os_version": "12 (bookworm)", + "kernel_version": "6.1.0-xx-amd64" + }, + "last_benchmark": { + "id": 42, + "run_at": "2025-12-07T10:32:00Z", + "global_score": 92, + "cpu_score": 90, + "memory_score": 95, + "disk_score": 94, + "network_score": 88, + "gpu_score": null + } +} +``` + +--- + +### 4.4. GET /api/devices/{device_id}/benchmarks + +Objectif : récupérer l’historique de benchmarks d’un device. + +Paramètres : +- `limit` (int, défaut 20) +- `offset` (int, défaut 0) + +Réponse : + +```json +{ + "items": [ + { + "id": 42, + "run_at": "2025-12-07T10:32:00Z", + "global_score": 92, + "cpu_score": 90, + "memory_score": 95, + "disk_score": 94, + "network_score": 88, + "gpu_score": null + }, + { + "id": 35, + "run_at": "2025-12-05T09:10:00Z", + "global_score": 89, + "cpu_score": 88, + "memory_score": 92, + "disk_score": 90, + "network_score": 85, + "gpu_score": null + } + ], + "total": 2, + "limit": 20, + "offset": 0 +} +``` + +--- + +### 4.5. GET /api/benchmarks/{benchmark_id} + +Objectif : récupérer les détails complets d’un benchmark. + +Réponse : + +```json +{ + "id": 42, + "device_id": 1, + "hardware_snapshot_id": 10, + "run_at": "2025-12-07T10:32:00Z", + "bench_script_version": "1.0.0", + "global_score": 92, + "cpu_score": 90, + "memory_score": 95, + "disk_score": 94, + "network_score": 88, + "gpu_score": null, + "details": { + "cpu": { + "events_per_sec": 12000, + "duration_s": 10, + "score": 90 + }, + "memory": { + "throughput_mib_s": 20000, + "score": 95 + }, + "disk": { + "read_mb_s": 1200, + "write_mb_s": 1000, + "iops_read": 50000, + "iops_write": 45000, + "latency_ms": 1.2, + "score": 94 + }, + "network": { + "upload_mbps": 930, + "download_mbps": 940, + "ping_ms": 1.2, + "jitter_ms": 0.3, + "packet_loss_percent": 0, + "score": 88 + }, + "gpu": { + "glmark2_score": null, + "score": null + }, + "global_score": 92 + } +} +``` + +Note : `details` reflète `details_json` de la base. + +--- + +### 4.6. Liens constructeur + +#### GET /api/devices/{device_id}/links + +Réponse : + +```json +[ + { + "id": 1, + "label": "Support HP", + "url": "https://support.hp.com/..." + }, + { + "id": 2, + "label": "Page produit", + "url": "https://www.hp.com/..." + } +] +``` + +#### POST /api/devices/{device_id}/links + +Body : + +```json +{ + "label": "Support HP", + "url": "https://support.hp.com/..." +} +``` + +Réponse : l’objet créé avec `id`. + +#### PUT /api/links/{id} + +Body idem POST (label + url). + +#### DELETE /api/links/{id} + +- Réponse : `204 No Content` en cas de succès. + +--- + +### 4.7. Documents + +#### GET /api/devices/{device_id}/docs + +Réponse : + +```json +[ + { + "id": 1, + "doc_type": "manual", + "filename": "manual_elitedesk.pdf", + "mime_type": "application/pdf", + "size_bytes": 3456789, + "uploaded_at": "2025-05-01T10:00:00Z" + } +] +``` + +#### POST /api/devices/{device_id}/docs + +- Type : `multipart/form-data` +- Champs : + - `file` (fichier binaire) + - `doc_type` (string) + +Réponse : métadonnées du document créé. + +#### GET /api/docs/{doc_id}/download + +- Sert le fichier binaire avec le bon `Content-Type`. + +--- + +## 5. Authentification + +MVP : token simple. + +- Variable d’env `API_TOKEN` côté backend. +- Chaque requête `POST /api/benchmark` doit inclure : + +```http +Authorization: Bearer +``` + +- Les endpoints de lecture (GET) peuvent être : + - soit publics sur le LAN, + - soit protégés par un autre mécanisme (reverse proxy) – hors scope de cette spec. + +--- + +## 6. Gestion des erreurs et validations + +- Utiliser des modèles Pydantic pour : + - valider la structure de `hardware` et `results` autant que possible sans être bloquant. + - autoriser des champs optionnels (None / manquants). +- Codes d’erreur génériques : + - 400 : données invalides (schema). + - 401 : auth. + - 404 : ressources inexistantes (device, benchmark, link, doc). + - 413 : payload trop volumineux (facultatif selon config). + - 500 : erreur interne. + +Logs : +- Logguer les erreurs de parsing JSON. +- Logguer l’origine (IP) des appels `POST /api/benchmark`. + +--- diff --git a/04_bench_script_client.md b/04_bench_script_client.md new file mode 100644 index 0000000..901faeb --- /dev/null +++ b/04_bench_script_client.md @@ -0,0 +1,475 @@ + +# 04 – Spécification du script client de benchmark (bench.sh) + +Objectif : définir précisément le comportement du script Bash exécuté sur les machines clientes pour : +- collecter les informations matérielles et système, +- exécuter les benchmarks, +- calculer les scores, +- envoyer un JSON complet au backend via HTTP. + +Ce fichier sert de référence pour écrire `scripts/bench.sh` dans le dépôt. + +--- + +## 1. Usage et interface en ligne de commande + +Le script doit être exécutable en one-liner depuis une machine cliente, par exemple : + +```bash +curl -s https://gitea.maison43.duckdns.org/gilles/linux-benchtools/raw/branch/main/scripts/bench.sh \ + | bash -s -- \ + --server https://bench.maison43/api/benchmark \ + --token "XXXXXXX" \ + --device "elitedesk-800g3" \ + --iperf-server 10.0.0.10 \ + --short +``` + +### 1.1. Arguments supportés + +- `--server ` (obligatoire) + URL de l’endpoint backend `POST /api/benchmark`. + +- `--token ` (obligatoire) + Token d’authentification à envoyer dans `Authorization: Bearer `. + +- `--device ` (optionnel) + Identifiant logique de la machine (`device_identifier`). + Si non fourni, utiliser `hostname` de la machine. + +- `--iperf-server ` (optionnel) + Hôte/IP du serveur iperf3 utilisé pour les tests réseau. + +- `--skip-cpu`, `--skip-memory`, `--skip-disk`, `--skip-network`, `--skip-gpu` (optionnels) + Permettent de désactiver certains tests. + +- `--short` (optionnel) + Version “rapide” des tests (durées/tailles réduites). + +- `--help` + Affiche un message d’aide et quitte. + +### 1.2. Variables internes + +- `BENCH_SCRIPT_VERSION` (string, ex: `"1.0.0"`) + Doit être mise à jour à chaque changement incompatible du script. + +--- + +## 2. Pré-requis et compatibilité + +### 2.1. OS visés (MVP) + +- Debian / Ubuntu / Proxmox (base Debian). + +Le script doit : +- Lire `/etc/os-release` pour détecter l’OS. +- Utiliser `apt-get` pour installer les paquets manquants. + +### 2.2. Outils nécessaires + +Le script doit vérifier et installer si besoin : + +- `curl` +- `jq` (construction JSON) +- `sysbench` (CPU + mémoire) +- `fio` (disque) +- `iperf3` (réseau, si `--iperf-server` fourni) +- `dmidecode` (RAM, carte mère, BIOS) +- `lsblk` +- `lscpu` +- `smartmontools` (optionnel pour SMART disques) +- `lm-sensors` (optionnel pour températures) +- `glmark2` (optionnel pour GPU si dispo) + +--- + +## 3. Structure générale du script + +1. Parser les arguments. +2. Vérifier `--server` et `--token` (sinon erreur + exit 1). +3. Déterminer : + - `DEVICE_IDENTIFIER` = `--device` ou `hostname`. + - `BENCH_SCRIPT_VERSION`. +4. Détecter l’OS et préparer la commande d’installation de paquets. +5. Vérifier/installer les outils nécessaires. +6. Collecter les informations hardware et OS. +7. Exécuter les benchmarks (en respectant les flags `--skip-*`). +8. Calculer les scores (CPU, mémoire, disque, réseau, GPU, global). +9. Construire le JSON. +10. Envoyer le JSON au backend. +11. Afficher un récap et le statut HTTP. + +--- + +## 4. Collecte des informations matérielles et système + +Toutes les infos doivent ensuite être assemblées dans le bloc `hardware` du JSON. + +### 4.1. CPU + +Commandes possibles : + +- `lscpu` +- `/proc/cpuinfo` + +Informations à extraire : + +- `vendor` : ligne `Vendor ID` (ou `GenuineIntel`, `AuthenticAMD`, etc.). +- `model` : `Model name`. +- `microarchitecture` : optionnel (peut être déterminé via une table interne si souhaité, sinon laisser vide). +- `cores` : `Core(s) per socket` × `Socket(s)` ou `CPU(s)` minus hyperthreading. +- `threads` : `CPU(s)` (nombre logique). +- `base_freq_ghz` : depuis `lscpu` (MHz -> GHz). +- `max_freq_ghz` : `CPU max MHz` si disponible. +- `cache_l1_kb`, `cache_l2_kb`, `cache_l3_kb` : `L1d cache`, `L2 cache`, `L3 cache`. +- `flags` : liste depuis `Flags` / `Features`. +- `tdp_w` : non triviale à extraire dans un script, peut rester null. + +### 4.2. RAM + +Commandes : + +- `free -m` +- `dmidecode --type memory` (requiert sudo) + +Infos : + +- `total_mb` : depuis `free -m`. +- `slots_total` : nombre d’entrées `Locator` dans `dmidecode` (type DIMM). +- `slots_used` : slots où `Size` n’est pas `No Module Installed`. +- `ecc` : champ `Total Width` vs `Data Width` ou `Error Correction Type`. +- `layout` : tableau d’objets avec : + - `slot` (Locator) + - `size_mb` + - `type` (DDR3/DDR4/DDR5/etc.) + - `speed_mhz` + - `vendor` + - `part_number` + +### 4.3. GPU + +Commandes : + +- `lspci | grep -i vga` +- éventuellement `nvidia-smi` si NVIDIA. + +Infos : + +- `vendor` : Intel, NVIDIA, AMD… +- `model` : texte brut de `lspci`. +- `driver_version` : si récupérable (`nvidia-smi --query-gpu=driver_version`). +- `memory_dedicated_mb` : via `nvidia-smi`/outils spécifiques si possible, sinon null. +- `memory_shared_mb` : éventuellement via `lspci`/`/proc`, sinon null. +- `api_support` : optionnel (OpenGL/Vulkan), peut être laissé vide. + +### 4.4. Stockage (disques et partitions) + +Commandes : + +- `lsblk -o NAME,SIZE,TYPE,MODEL,TRAN,MOUNTPOINT,FSTYPE` +- `smartctl -H /dev/sdX` (si présent) +- éventuellement `nvme list` / `nvme smart-log`. + +Infos : + +- `devices` : tableau d’objets : + - `name` (ex: `/dev/nvme0n1`) + - `type` (HDD/SSD/NVMe, déduit de `TYPE`/`TRAN`/nom). + - `interface` (SATA, PCIe 3.0 x4, USB, etc. si déductible). + - `capacity_gb` : depuis `SIZE`. + - `vendor` / `model` : `MODEL`. + - `smart_health` : `PASSED` / `FAILED` / null. + - `temperature_c` : si dispo via SMART. + +- `partitions` : tableau d’objets : + - `name` + - `mount_point` + - `fs_type` + - `used_gb` + - `total_gb` + +### 4.5. Réseau + +Commandes : + +- `ip addr` +- `ip -o link` +- `ethtool ` (pour vitesse si dispo). +- pour Wi-Fi : `iw dev` / `iwconfig`. + +Infos : + +- `interfaces` : tableau d’objets : + - `name` (ex: `eth0`, `enp3s0`, `wlan0`) + - `type` (`ethernet`, `wifi`, `other`) + - `mac` + - `ip` (IPv4 principale si existante) + - `speed_mbps` : via `ethtool`. + - `driver` : éventuellement depuis `/sys/class/net//device/driver`. + +### 4.6. Carte mère / BIOS + +Commandes : + +- `dmidecode --type baseboard` +- `dmidecode --type bios` + +Infos : + +- `motherboard.vendor` +- `motherboard.model` +- `bios_version` +- `bios_date` + +### 4.7. OS + +Commandes : + +- `/etc/os-release` +- `uname -r` +- `uname -m` +- `systemd-detect-virt` (si dispo). + +Infos : + +- `name` : ID ou PRETTY_NAME. +- `version` : VERSION ou VERSION_CODENAME. +- `kernel_version` : `uname -r`. +- `architecture` : `uname -m`. +- `virtualization_type` : sortie de `systemd-detect-virt` (kvm, qemu, none, etc.). + +### 4.8. Capteurs (facultatif) + +- `sensors` (lm-sensors) +- `smartctl -A` pour température disques. + +Infos : + +- `sensors.cpu_temp_c` (ou valeur moyenne). +- `sensors.disk_temps_c` : map `{ "/dev/nvme0n1": 42 }`. + +--- + +## 5. Benchmarks à exécuter + +Les résultats iront dans le bloc `results` du JSON. + +### 5.1. CPU – sysbench + +Commande par défaut (mode complet) : + +```bash +sysbench cpu --cpu-max-prime=20000 --threads="$(nproc)" run +``` + +Mode `--short` : +```bash +sysbench cpu --cpu-max-prime=10000 --threads="$(nproc)" run +``` + +Valeurs à extraire : + +- `events_per_sec` : ligne `events per second: X`. +- `duration_s` : temps total (`total time:`). + +Score CPU : + +- Score simple : + - définir une valeur de référence (par ex. 5000 events/s = 50 points). + - `cpu_score = min(100, events_per_sec / ref * 50)` (ajuster plus tard). +- Pour l’instant, le script peut : + - soit calculer cette note, + - soit juste envoyer les valeurs brutes et laisser le backend calculer. + +### 5.2. Mémoire – sysbench + +Commande (complet) : + +```bash +sysbench memory --memory-total-size=2G --memory-oper=write run +``` + +Mode `--short` : +```bash +sysbench memory --memory-total-size=512M --memory-oper=write run +``` + +Valeurs : + +- `throughput_mib_s` : ligne `transferred (XXXX MiB/sec)`. + +Score mémoire : + +- Basé sur `throughput_mib_s` et une référence. + +### 5.3. Disque – fio + +Profil simple (séquentiel read/write 1GiB) : + +```bash +fio --name=bench_seq_rw \ + --rw=readwrite \ + --bs=1M \ + --size=1G \ + --numjobs=1 \ + --iodepth=16 \ + --filename=/tmp/fio_benchfile \ + --direct=1 \ + --group_reporting +``` + +Mode `--short` : +- Taille 256M. + +Valeurs à extraire (via parsing ou `--output-format=json`) : + +- `read_mb_s` +- `write_mb_s` +- éventuellement `iops_read`, `iops_write`, `latency_ms`. + +Score disque : + +- Moyenne pondérée de read/write vs valeurs de référence. + +Après test, supprimer `/tmp/fio_benchfile`. + +### 5.4. Réseau – iperf3 + +Uniquement si `--iperf-server` fourni. + +Download (client -> server, test reverse) : + +```bash +iperf3 -c "$IPERF_SERVER" -R -J +``` + +Upload : + +```bash +iperf3 -c "$IPERF_SERVER" -J +``` + +Utiliser le JSON (`-J`) + `jq` pour extraire : + +- `upload_mbps` +- `download_mbps` +- `jitter_ms` +- `packet_loss_percent` (si UDP, option future). + +Ping (latence) : + +```bash +ping -c 5 "$IPERF_SERVER" +``` + +Extraire : + +- `ping_ms` = moyenne. + +Score réseau : + +- Combinaison débit (min(up, down)) et latence (ping). + +### 5.5. GPU – glmark2 (optionnel) + +Si `glmark2` disponible : + +```bash +glmark2 +``` + +Extraire : + +- Score global `glmark2_score`. + +Score GPU : + +- Normalisation simple vs référence. + +--- + +## 6. Construction du JSON + +Le script utilise `jq` pour construire le JSON final : + +Structure : + +```json +{ + "device_identifier": "...", + "bench_script_version": "1.0.0", + "hardware": { ... }, + "results": { ... } +} +``` + +Principes : + +- Utiliser `jq -n` et passer les valeurs via `--arg` / `--argjson`. +- Attention aux nombres vs strings (utiliser `--argjson` pour les nombres). +- Gérer proprement les valeurs nulles (par exemple si test GPU non réalisé). + +Exemple (simplifié) en shell : + +```bash +payload=$(jq -n --arg device_identifier "$DEVICE_IDENTIFIER" --arg bench_script_version "$BENCH_SCRIPT_VERSION" --argjson cpu "$CPU_JSON" --argjson ram "$RAM_JSON" --argjson results "$RESULTS_JSON" '{ + device_identifier: $device_identifier, + bench_script_version: $bench_script_version, + hardware: { + cpu: $cpu, + ram: $ram + }, + results: $results + }') +``` + +--- + +## 7. Envoi au backend + +Commandes : + +```bash +HTTP_RESPONSE=$(curl -s -o /tmp/bench_response.txt -w "%{http_code}" \ + -X POST "$SERVER_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d "$payload") +``` + +- Si `HTTP_RESPONSE` != `200` : + - Afficher un message d’erreur (console). + - Optionnel : afficher `/tmp/bench_response.txt`. + +- Si succès : + - Afficher un message confirmant l’ID du benchmark si présent dans la réponse. + +--- + +## 8. Gestion des erreurs à prévoir + +- Absence de `--server` ou `--token` -> erreur et exit. +- Outils manquants et impossible à installer -> avertir, sauter le test concerné, transmettre `score = null`. +- Erreurs iperf3 (serveur indisponible) -> ignorer la partie réseau, `network` null. +- Temps de test trop long -> proposer un mode `--short`. + +--- + +## 9. Journalisation locale (optionnelle) + +- Possibilité de logger les infos dans `/var/log/linux_benchtools_client.log` ou `/tmp/linux_benchtools_client.log`. +- Log recommandé : + - Date/heure. + - SERVER, DEVICE_IDENTIFIER. + - Résumé des scores. + - Code HTTP de la réponse. + +--- + +## 10. Bonnes pratiques + +- Ne jamais supprimer ou modifier des fichiers système. +- Nettoyer les fichiers temporaires (fio, résultats intermédiaires). +- Garder le script idempotent : on peut le relancer sans casser la machine. +- Prévoir un délai total raisonnable pour un run complet (ex. < 5–10 minutes en mode complet, < 2–3 minutes en mode `--short`).