CI / test (push) Failing after 6m42s
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>
184 lines
7.0 KiB
Go
184 lines
7.0 KiB
Go
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'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'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)
|
||
}
|
||
}
|