Files
Ilker ManapandClaude Opus 5.5 0416b827fd ctl bench reads a guarded engine's metrics with the engine's key, or says why it cannot
The sampler sent the key given for --target, which for the gateway is a
client's, so llama.cpp and TensorRT-LLM refused every read and the run
reported an idle engine. The engine key now comes from the admin API for the
replica at --metrics' address, or --metrics-key; unreadable metrics stop the
run before any load, and a step that read none shows why.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:20:25 +02:00

97 lines
3.6 KiB
Go

package cli
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
)
// benchCluster is a gateway taking the client's key, a llama.cpp replica
// whose /metrics takes only its engine key, and an admin API that lists the
// replica with that key, as the control plane does.
type benchCluster struct {
gateway, engine, admin *httptest.Server
metricsAuth atomic.Value
}
func newBenchCluster(t *testing.T) *benchCluster {
t.Helper()
c := &benchCluster{}
c.metricsAuth.Store("")
c.gateway = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer client" {
http.Error(w, "no", http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "text/event-stream")
fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"a\"}}]}\n\n")
fmt.Fprint(w, "data: {\"choices\":[{\"delta\":{\"content\":\"b\"}}]}\n\n")
fmt.Fprint(w, "data: [DONE]\n\n")
}))
c.engine = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.metricsAuth.Store(r.Header.Get("Authorization"))
if r.URL.Path != "/metrics" || r.Header.Get("Authorization") != "Bearer engine-key" {
http.Error(w, `{"error":"Invalid API Key"}`, http.StatusUnauthorized)
return
}
fmt.Fprint(w, "llamacpp:requests_processing 1\nllamacpp:requests_deferred 3\n")
}))
c.admin = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode([]adminapi.ReplicaView{
{ID: "r1", Model: "m", Profile: "cpu", Endpoint: c.engine.URL, EngineKey: "engine-key"},
})
}))
t.Cleanup(func() { c.gateway.Close(); c.engine.Close(); c.admin.Close() })
return c
}
func (c *benchCluster) bench(t *testing.T, args ...string) (string, error) {
t.Helper()
t.Setenv("LLMGW_ADMIN_TOKEN", "t")
asJSON := false
cmd := newBenchCmd(func() *ctlClient { return newCtlClient(c.admin.URL) }, &asJSON)
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&out)
cmd.SetArgs(append([]string{"m", "--concurrency", "1", "--duration", "1500ms", "--ttft-p99", "1m", "--tpot-p99", "1m"}, args...))
err := cmd.ExecuteContext(context.Background())
return out.String(), err
}
func TestBenchReadsAGuardedEnginesMetricsWithTheKeyTheAdminAPIGives(t *testing.T) {
// Through the gateway with a client's key, and the replica's metrics on
// the side: the client's key is not the engine's, and the engine's is
// found by the replica at --metrics' address, not at the target's.
c := newBenchCluster(t)
out, err := c.bench(t, "--target", c.gateway.URL, "--key", "client",
"--metrics", c.engine.URL+"/metrics", "--engine", "llamacpp")
if err != nil {
t.Fatalf("%v\n%s", err, out)
}
if got := c.metricsAuth.Load(); got != "Bearer engine-key" {
t.Errorf("metrics read with %q", got)
}
if !strings.Contains(out, "target_waiting: 3") {
t.Errorf("the queue depth read was not suggested:\n%s", out)
}
}
func TestBenchSaysWhyAnEnginesMetricsCannotBeRead(t *testing.T) {
// A key the engine refuses fails the run before any load, saying where
// the right one comes from, rather than reporting an idle engine.
c := newBenchCluster(t)
out, err := c.bench(t, "--target", c.gateway.URL, "--key", "client",
"--metrics", c.engine.URL+"/metrics", "--engine", "llamacpp", "--metrics-key", "wrong")
if err == nil || !strings.Contains(err.Error(), "refused the key") || !strings.Contains(err.Error(), "--metrics-key") {
t.Fatalf("expected the refusal explained, got %v\n%s", err, out)
}
}