Files
llmgw/internal/web/i18n_cluster_test.go
T
Ilker ManapandClaude Opus 5.5 c866b3a8ee
CI / test (push) Failing after 6m42s
How much of each GPU works, beside whether anything does
The busy figure is nvidia-smi's utilization.gpu: the share of time any
kernel ran, which a kernel on one SM fills. On cuda01 decode read 92% busy
while the SMs had work 69% of the time and issued on 18% of their cycles.

Busy is now said to be that, and beside it: each card's draw against its
power limit (efficiency table, fleet and node charts, the console's GPU
work chart), and where the profiling counters can be read, SMs active, SM
occupancy, tensor cores and DRAM (llmgw_gpu_*_active_percent), from NVML's
GPM on Hopper and later or a dcgm-exporter on the node
(discovery.dcgm_exporter). The GPU map colours by work by default and keeps
busy as a mode. A MIG instance is never published with its card's profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:11:26 +01:00

184 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package web
import (
"encoding/json"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/promq"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
var clusterTR = map[string]string{"Accept-Language": "tr-TR,tr;q=0.9"}
func clusterTurkish(t *testing.T) *Lang {
t.Helper()
l := langByCode("tr")
if l == nil {
t.Fatal("the portal does not speak Turkish")
}
return l
}
func TestTheDashboardSpeaksTurkishWhenAsked(t *testing.T) {
now := time.Now().Truncate(time.Hour)
usage := fakeUsage{buckets: []store.Bucket{
{Start: now.Add(-time.Hour), Requests: 1500, Prompt: 4000, Completion: 900, LatencyMs: 120},
{Start: now, Requests: 95, Prompt: 9000, Completion: 2100, LatencyMs: 310},
}}
_, body := get(t, Dashboard(mapFleet{}, usage, "v1", 5*time.Second, true, NavExtras{}, nil),
"http://127.0.0.1:9090/", clusterTR)
for _, want := range []string{
`<html lang="tr">`, "<h1>Küme</h1>", "Kapasite", "<h2>Düğümler</h2>",
// A state is still its class, and a Turkish word.
`<td class="state state-ready">hazır</td>`,
// The GPU map: its modes, and what a card says under the pointer.
"<strong>iş</strong>", "gpu07 GPU 2 · code (code-1) · %93 meşgul · 39,1 / 80 GB",
// How long a replica has sat idle, in Turkish units.
"30dk0sn önce",
// The traffic chart's peak, with Turkish thousands.
"en yüksek 1,5 bin istek/sa",
"5 saniyede bir yenilenir",
} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish dashboard lacks %q", want)
}
}
for _, english := range []string{"Capacity", "Holding hardware", "peak ", "requests/h", "No requests recorded"} {
if strings.Contains(body, english) {
t.Errorf("the Turkish dashboard still says %q", english)
}
}
}
func TestTheNodePageWritesTurkishNumbers(t *testing.T) {
q := func(expr string) string { return strings.Replace(expr, "%s", `"llmgw-node01"`, 1) }
h := &fakeHistory{byExpr: map[string][]promq.Series{
q(`llmgw_gpu_utilization_percent{node=%s} / 100`): {ts("0", 0.9, 0.97), ts("1", 0, 0)},
q(`llmgw_gpu_temperature_celsius{node=%s}`): {ts("0", 65, 71)},
}}
r := httptest.NewRequest(http.MethodGet, "http://x/admin/nodes/llmgw-node01", nil)
r.AddCookie(&http.Cookie{Name: langCookie, Value: "tr"})
w := httptest.NewRecorder()
nodeMux(gpuSource{}, h).ServeHTTP(w, r)
body := w.Body.String()
for _, want := range []string{
"64 çekirdek", "bellek %64", "71,2 <span class=\"note\">/ 80 GB", "1.980 MHz",
"3 düzeltilemeyen ECC hatası", "<h2>GPU&#39;lar</h2>",
"Meşgul GPU, karta göre", "GPU sıcaklığı, °C", "Son bir saat",
// The chart's axis and peak in Turkish figures.
"en yüksek 100%",
} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish node page lacks %q", want)
}
}
if strings.Contains(body, "uncorrected") || strings.Contains(body, "GPU busy, by card") {
t.Error("the Turkish node page still speaks English")
}
}
func TestChartTooltipsNameTheDayInTurkish(t *testing.T) {
tr := clusterTurkish(t)
// 5 October 2026 is a Monday.
at := time.Date(2026, 10, 5, 15, 4, 0, 0, time.Local)
if got := tr.hourLabel(at, 1500, tr.T("dash.unit.requests")); got != "Pzt 15:04 — 1,5 bin istek" {
t.Errorf("a Turkish tooltip reads %q", got)
}
if got := hourLabel(at, 1500, "requests"); got != "Mon 15:04 — 1.5k requests" {
t.Errorf("the English tooltip changed: %q", got)
}
bars := string(tr.BarChart(Series{Values: []float64{1, 2500}, Labels: []string{tr.hourLabel(at, 1, "istek")}}, "istek/sa"))
if !strings.Contains(bars, "<title>Pzt 15:04 — 1 istek</title>") || !strings.Contains(bars, "en yüksek 2,5 bin istek/sa") {
t.Errorf("the Turkish bar chart:\n%s", bars)
}
if got := string(tr.BarChart(Series{}, "istek")); !strings.Contains(got, "kayıt yok") {
t.Errorf("an empty Turkish chart: %s", got)
}
// The axis of a line chart, and its legend's fold.
c := tr.LineChart([]promq.Series{ts("a", 0.001, 0.004), ts("b", 0, 0.001), ts("c", 0, 0.001),
ts("d", 0, 0.001), ts("e", 0, 0.001)}, "%", 4)
if !strings.Contains(string(c.SVG), ">0,5%<") || c.Peak != "0,5%" {
t.Errorf("a Turkish percent axis: peak %q\n%s", c.Peak, c.SVG)
}
if last := c.Legend[len(c.Legend)-1]; last.Name != "diğer" {
t.Errorf("the folded line is called %q", last.Name)
}
// The coordinates are written the C way whatever the language.
if regexp.MustCompile(`points="[^"]*\d,\d+,`).MatchString(string(c.SVG)) {
t.Error("a Turkish decimal comma reached an SVG coordinate")
}
if got := tr.windowWord(6 * time.Hour); got != "6 saat" {
t.Errorf("six hours in Turkish: %q", got)
}
}
func TestTheFabricMapIsHandedItsWordsInThePagesLanguage(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "http://x/admin/fabrics", nil)
r.Header.Set("Accept-Language", "tr")
w := httptest.NewRecorder()
FabricsPage(topoSource{}, false, "v1")(w, r)
body := w.Body.String()
m := regexp.MustCompile(`(?s)<script type="application/json" id="fabric-i18n">(.*?)</script>`).FindStringSubmatch(body)
if m == nil {
t.Fatalf("the page hands the map no words:\n%s", body)
}
var words map[string]string
if err := json.Unmarshal([]byte(m[1]), &words); err != nil {
t.Fatalf("the words are not JSON: %v\n%s", err, m[1])
}
for k, want := range map[string]string{
"decimal": ",", "stat.switches": "anahtarlar", "ports_used": "{0}/{1} port",
"map_failed": "Harita çıkarılamadı: {0}",
} {
if words[k] != want {
t.Errorf("%s is %q, want %q", k, words[k], want)
}
}
for _, want := range []string{"<h1>Fabric&#39;ler</h1>", "Görünümü sıfırla", `placeholder="Düğüm bul"`} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish fabrics page lacks %q", want)
}
}
// Every sentence the script falls back to has a Turkish text.
for _, s := range fabricJSStrings {
if words[s.key] == "" || strings.Contains(words[s.key], "%!") {
t.Errorf("the map's %q is %q", s.key, words[s.key])
}
}
}
func TestTheNVLinkAndLogPagesSpeakTurkish(t *testing.T) {
_, body := get(t, NVLinkPage(nvlSource{}, false, "v1"), "http://x/admin/nvlink", clusterTR)
for _, want := range []string{"<h1>NVLink alanları</h1>", "tepsi kullanılabilir", `class="tag bad">hata</span>`, "30 saniyede bir yenilenir"} {
if !strings.Contains(body, want) {
t.Errorf("the Turkish NVLink page lacks %q", want)
}
}
req := httptest.NewRequest("GET", "/admin/nodes/n6/logs?source=agent", nil)
req.SetPathValue("id", "n6")
req.Header.Set("Accept-Language", "tr")
rec := httptest.NewRecorder()
NodeLogsPage(logsFleet{}, false, "test")(rec, req)
for _, want := range []string{"ajanın günlüğü", "son 300 satır", "izle"} {
if !strings.Contains(rec.Body.String(), want) {
t.Errorf("the Turkish log page lacks %q", want)
}
}
}
func TestAChosenBuildIsAnnouncedInTheChoosersLanguage(t *testing.T) {
tr := clusterTurkish(t)
if got := buildNotice(tr, "gpu07", "vllm", "cu129"); !strings.Contains(got, "CUDA 12.9 için derlenmiş") {
t.Errorf("in Turkish: %q", got)
}
if got := buildNotice(english, "gpu07", "vllm", "none"); got != "gpu07 installs vllm no longer; the node is told now, and installs it in the background" {
t.Errorf("in English: %q", got)
}
}