diff --git a/enterprise/tailnet/coordinator.go b/enterprise/tailnet/coordinator.go index 12c3d3ad38bd8..9974f803bd92f 100644 --- a/enterprise/tailnet/coordinator.go +++ b/enterprise/tailnet/coordinator.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "io" "net" "net/http" @@ -702,13 +701,8 @@ func (c *haCoordinator) formatAgentUpdate(id uuid.UUID, node *agpl.Node) ([]byte } func (c *haCoordinator) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - c.mutex.RLock() defer c.mutex.RUnlock() - _, _ = fmt.Fprintln(w, "

high-availability wireguard coordinator debug

") - _, _ = fmt.Fprintln(w, "

warning: this only provides info from the node that served the request, if there are multiple replicas this data may be incomplete

") - - agpl.CoordinatorHTTPDebug(c.agentSockets, c.agentToConnectionSockets, c.agentNameCache)(w, r) + agpl.CoordinatorHTTPDebug(true, c.agentSockets, c.agentToConnectionSockets, c.nodes, c.agentNameCache)(w, r) } diff --git a/enterprise/tailnet/pgcoord.go b/enterprise/tailnet/pgcoord.go index cb577fe271354..25396e9c84546 100644 --- a/enterprise/tailnet/pgcoord.go +++ b/enterprise/tailnet/pgcoord.go @@ -17,7 +17,6 @@ import ( "nhooyr.io/websocket" "cdr.dev/slog" - "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/database/dbauthz" "github.com/coder/coder/coderd/database/pubsub" diff --git a/tailnet/coordinator.go b/tailnet/coordinator.go index 23e3bad99fc65..a5347f981a210 100644 --- a/tailnet/coordinator.go +++ b/tailnet/coordinator.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "html/template" "io" "net" "net/http" @@ -646,136 +647,204 @@ func (c *coordinator) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) { } func (c *core) serveHTTPDebug(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - c.mutex.RLock() defer c.mutex.RUnlock() - _, _ = fmt.Fprintln(w, "

in-memory wireguard coordinator debug

") - - CoordinatorHTTPDebug(c.agentSockets, c.agentToConnectionSockets, c.agentNameCache)(w, r) + CoordinatorHTTPDebug(false, c.agentSockets, c.agentToConnectionSockets, c.nodes, c.agentNameCache)(w, r) } func CoordinatorHTTPDebug( + ha bool, agentSocketsMap map[uuid.UUID]Queue, agentToConnectionSocketsMap map[uuid.UUID]map[uuid.UUID]Queue, + nodesMap map[uuid.UUID]*Node, agentNameCache *lru.Cache[uuid.UUID, string], ) func(w http.ResponseWriter, _ *http.Request) { return func(w http.ResponseWriter, _ *http.Request) { - now := time.Now() - - type idConn struct { - id uuid.UUID - conn Queue + w.Header().Set("Content-Type", "text/html; charset=utf-8") + + tmpl, err := template.New("coordinator_debug").Funcs(template.FuncMap{ + "marshal": func(v interface{}) template.JS { + a, err := json.MarshalIndent(v, "", " ") + if err != nil { + //nolint:gosec + return template.JS(fmt.Sprintf(`{"err": %q}`, err)) + } + //nolint:gosec + return template.JS(a) + }, + }).Parse(coordinatorDebugTmpl) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(err.Error())) + return } - { - _, _ = fmt.Fprintf(w, "

# agents: total %d

\n", len(agentSocketsMap)) - _, _ = fmt.Fprintln(w, "") + data.Agents = append(data.Agents, agent) } + slices.SortFunc(data.Agents, func(a, b *htmlAgent) bool { + return a.Name < b.Name + }) - { - type agentConns struct { - id uuid.UUID - conns []idConn + for agentID, conns := range agentToConnectionSocketsMap { + if len(conns) == 0 { + continue } - missingAgents := []agentConns{} - for agentID, conns := range agentToConnectionSocketsMap { - if len(conns) == 0 { - continue + if _, ok := agentSocketsMap[agentID]; !ok { + agentName, ok := agentNameCache.Get(agentID) + if !ok { + agentName = "unknown" } - - if _, ok := agentSocketsMap[agentID]; !ok { - connsSlice := make([]idConn, 0, len(conns)) - for id, conn := range conns { - connsSlice = append(connsSlice, idConn{id, conn}) - } - slices.SortFunc(connsSlice, func(a, b idConn) bool { - return a.id.String() < b.id.String() + agent := &htmlAgent{ + Name: agentName, + ID: agentID, + } + for id, conn := range conns { + start, lastWrite := conn.Stats() + agent.Connections = append(agent.Connections, &htmlClient{ + Name: conn.Name(), + ID: id, + CreatedAge: now.Sub(time.Unix(start, 0)).Round(time.Second), + LastWriteAge: now.Sub(time.Unix(lastWrite, 0)).Round(time.Second), }) - - missingAgents = append(missingAgents, agentConns{agentID, connsSlice}) } + slices.SortFunc(agent.Connections, func(a, b *htmlClient) bool { + return a.Name < b.Name + }) + + data.MissingAgents = append(data.MissingAgents, agent) } - slices.SortFunc(missingAgents, func(a, b agentConns) bool { - return a.id.String() < b.id.String() + } + slices.SortFunc(data.MissingAgents, func(a, b *htmlAgent) bool { + return a.Name < b.Name + }) + + for id, node := range nodesMap { + name, _ := agentNameCache.Get(id) + data.Nodes = append(data.Nodes, &htmlNode{ + ID: id, + Name: name, + Node: node, }) + } + slices.SortFunc(data.Nodes, func(a, b *htmlNode) bool { + return a.Name+a.ID.String() < b.Name+b.ID.String() + }) - _, _ = fmt.Fprintf(w, "

# missing agents: total %d

\n", len(missingAgents)) - _, _ = fmt.Fprintln(w, "") + err = tmpl.Execute(w, data) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(err.Error())) + return } } } + +type htmlDebug struct { + HA bool + Agents []*htmlAgent + MissingAgents []*htmlAgent + Nodes []*htmlNode +} + +type htmlAgent struct { + Name string + ID uuid.UUID + CreatedAge time.Duration + LastWriteAge time.Duration + Overwrites int + Connections []*htmlClient +} + +type htmlClient struct { + Name string + ID uuid.UUID + CreatedAge time.Duration + LastWriteAge time.Duration +} + +type htmlNode struct { + ID uuid.UUID + Name string + Node *Node +} + +var coordinatorDebugTmpl = ` + + + + + + + {{- if .HA }} +

high-availability wireguard coordinator debug

+

warning: this only provides info from the node that served the request, if there are multiple replicas this data may be incomplete

+ {{- else }} +

in-memory wireguard coordinator debug

+ {{- end }} + +

# agents: total {{ len .Agents }}

+ + +

# missing agents: total {{ len .MissingAgents }}

+ + +

# nodes: total {{ len .Nodes }}

+ + + +`