Moniteur : corrections de relecture — un nvidia-smi pendu ne fige plus les jauges

gpuStatsCached tient son verrou pendant la requête : c'est ce qui fait
partager une seule lecture aux onglets. Mais la requête n'était pas bornée,
et le code le dit ailleurs (nvidiaGPUCount) : un pilote coincé peut faire
pendre nvidia-smi. Avant le cache, chaque nouvelle requête relançait son
propre nvidia-smi et les jauges revenaient avec le pilote ; avec le verrou,
un seul processus pendu bloquait /api/vram pour tous les onglets jusqu'au
redémarrage de Loki.

- nvidiaSmiQuery : CommandContext borné à 10 s (large pour une première
  lecture lente sans mode persistant), WaitDelay d'une seconde. Au-delà,
  une erreur ordinaire, retentée au délai normal du cache.
- Le déchargement profite de la même borne : une lecture pendue devient une
  mesure ratée, que gpuSettle sait déjà sauter.
- Test : la vraie requête, binaire absent du PATH, rend bien
  exec.ErrNotFound — la seule erreur que le cache garde une minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-04 02:01:11 +02:00
1 parent 4f34694400
commit 9b2e974942
2 files changed
+27 -2

No files matched your search

+16 -2
View File
@@ -13,6 +13,7 @@
package loki
import (
"context"
"encoding/json"
"errors"
"net/http"
@@ -36,12 +37,25 @@ type gpuStat struct {
// nvidiaSmiQuery lance la requête nvidia-smi. Variable pour que les tests
// substituent un faux exécutable et comptent les lancements.
//
// Bornée dans le temps, comme nvidiaGPUCount : un pilote coincé peut faire
// pendre nvidia-smi, et gpuStatsCached tient son verrou pendant la requête. Sans
// borne, un seul nvidia-smi pendu figeait les jauges de TOUS les onglets
// jusqu'au redémarrage de Loki, même une fois le pilote revenu. Dix secondes :
// large pour une première lecture lente (pilote sans mode persistant, plusieurs
// cartes), une erreur ordinaire au-delà — retentée au délai normal.
var nvidiaSmiQuery = func() ([]byte, error) {
return hideCmd(exec.Command("nvidia-smi",
ctx, cancel := context.WithTimeout(context.Background(), nvidiaSmiTimeout)
defer cancel()
cmd := hideCmd(exec.CommandContext(ctx, "nvidia-smi",
"--query-gpu=name,memory.used,memory.total,utilization.gpu,temperature.gpu",
"--format=csv,noheader,nounits")).Output()
"--format=csv,noheader,nounits"))
cmd.WaitDelay = time.Second // un descendant qui garderait la sortie ouverte ne retient pas Wait
return cmd.Output()
}
const nvidiaSmiTimeout = 10 * time.Second
// gpuStats interroge nvidia-smi. Renvoie nil quand il est absent (machine sans
// GPU NVIDIA, Mac, CPU seul) : l'absence de mesure n'est pas une erreur, elle
// prive juste le déchargement de son bilan chiffré. Toujours une lecture
+11
View File
@@ -170,3 +170,14 @@ func TestGpuSettleSampleFrais(t *testing.T) {
t.Errorf("jauges : %d, attendu la lecture en cache 19000", g[0].Used)
}
}
// Le cache ne mémorise une minute que l'ABSENCE de nvidia-smi : il faut donc que
// la vraie requête (bornée dans le temps) rende toujours exec.ErrNotFound quand
// le binaire n'est pas dans le PATH — sinon Mac et CPU seul relanceraient une
// recherche toutes les deux secondes.
func TestNvidiaSmiQueryAbsent(t *testing.T) {
t.Setenv("PATH", t.TempDir())
if _, err := nvidiaSmiQuery(); !errors.Is(err, exec.ErrNotFound) {
t.Fatalf("erreur %v, attendu exec.ErrNotFound", err)
}
}