diff --git a/fe/http.go b/fe/http.go index 73d8f3924ddbd5067df45948e3be3db4afbd8845..89cd8fceaa58b77551dcc2acf13ecc3ad568770a 100644 --- a/fe/http.go +++ b/fe/http.go @@ -316,10 +316,12 @@ func (h *HttpRedirector) createHandler() http.Handler { nil)) // Pass /debug/ to the default ServeMux, all the default debug - // handlers are installed there. - mux.Handle( - "/debug/", - handlers.GZIPHandler(http.DefaultServeMux, nil)) + // handlers are installed there. Add a debug handler for the + // LoadBalancer data. Gzip the responses. + debugMux := http.NewServeMux() + debugMux.Handle("/debug/lbv2", h.lb) + debugMux.Handle("/", http.DefaultServeMux) + mux.Handle("/debug/", handlers.GZIPHandler(debugMux, nil)) // Optionally enable a reverse proxy to the local Icecast for // the direct stream URLs (below IcecastMountPrefix). diff --git a/fe/http_debug.go b/fe/http_debug.go index 4861be2a0edb9b82de5609dcf8d7ab347ea7843b..cb656ef3287643c2b7eaecaaa9719a2e3269213d 100644 --- a/fe/http_debug.go +++ b/fe/http_debug.go @@ -2,10 +2,10 @@ package fe import ( "fmt" + "html/template" "net/http" "sort" "sync" - "text/template" "time" ) diff --git a/fe/http_test.go b/fe/http_test.go index 531e5919a11ae41ff87320a343fe16dc32f36ab0..2b849fc6a7dae2dd0fc4f4df0c78396712352d62 100644 --- a/fe/http_test.go +++ b/fe/http_test.go @@ -103,6 +103,16 @@ func TestHttpRedirector_Static(t *testing.T) { } } +func TestHttpRedirector_LBDebugPage(t *testing.T) { + _, srv := createTestHttpServer(t) + defer srv.Close() + + data := doHttpRequest(t, "GET", srv.URL+"/debug/lbv2", 200) + if !strings.Contains(data, "node1") { + t.Errorf("Bad response:\n%s", data) + } +} + type httpTestContext struct { srv *httptest.Server targetSrv *httptest.Server diff --git a/fe/lbv2/debug.go b/fe/lbv2/debug.go new file mode 100644 index 0000000000000000000000000000000000000000..6bf69cdd2b9da7686b1e0612f914938cec600e4a --- /dev/null +++ b/fe/lbv2/debug.go @@ -0,0 +1,80 @@ +package lbv2 + +import ( + "fmt" + "html/template" + "net/http" +) + +const debugText = `<html> + <body> + <title>Load Balancer</title> + + <h3>Query costs</h3> + <table> + <tr><th>Node</th><th>Requests</th><th>Utilization/Cost</th></tr> + {{$dimensions := .Dimensions}} + {{range .Nodes}} + <tr> + <td>{{.Name}}</td> + <td>{{.Requests}}</td> + {{range $d := $dimensions}} + {{$data := index .Data $d}} + <td> + {{$data.PredictedUtilization}}/{{$data.ReportedUtilization}}/{{$data.Cost}} + </td> + {{end}} + </tr> + {{end}} + </table> + </body> + </html>` + +var debugTmpl = template.Must(template.New("lbv2 debug").Parse(debugText)) + +type nodeDebugData struct { + Name string + Requests int + Data map[int]costAndUtil +} + +type costAndUtil struct { + ReportedUtilization float64 + PredictedUtilization float64 + Cost float64 +} + +func (l *LoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Build a view of the cost/utilization data. + l.lock.Lock() + var nodes []nodeDebugData + for _, n := range l.nodes { + ndata := nodeDebugData{Name: n.Name(), Data: make(map[int]costAndUtil)} + for dim, pred := range l.predictors { + util := n.Utilization(dim) + ndata.Requests = util.Requests + ndata.Data[dim] = costAndUtil{ + ReportedUtilization: util.Utilization, + PredictedUtilization: pred.Utilization(n), + Cost: float64(pred.cost[n.Name()]), + } + } + nodes = append(nodes, ndata) + } + var dimensions []int + for dim := range l.predictors { + dimensions = append(dimensions, dim) + } + l.lock.Unlock() + + ctx := struct { + Nodes []nodeDebugData + Dimensions []int + NumDimensions int + }{nodes, dimensions, len(dimensions)} + + err := debugTmpl.Execute(w, ctx) + if err != nil { + fmt.Fprintln(w, "debug: error executing template: ", err.Error()) + } +}