Skip to content

Commit dd2f799

Browse files
authored
chore(tailnet): rewrite coordinator debug using html/template (coder#8752)
1 parent 02550a9 commit dd2f799

File tree

3 files changed

+173
-111
lines changed

3 files changed

+173
-111
lines changed

enterprise/tailnet/coordinator.go

+1-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8-
"fmt"
98
"io"
109
"net"
1110
"net/http"
@@ -702,13 +701,8 @@ func (c *haCoordinator) formatAgentUpdate(id uuid.UUID, node *agpl.Node) ([]byte
702701
}
703702

704703
func (c *haCoordinator) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
705-
w.Header().Set("Content-Type", "text/html; charset=utf-8")
706-
707704
c.mutex.RLock()
708705
defer c.mutex.RUnlock()
709706

710-
_, _ = fmt.Fprintln(w, "<h1>high-availability wireguard coordinator debug</h1>")
711-
_, _ = fmt.Fprintln(w, "<h4 style=\"margin-top:-25px\">warning: this only provides info from the node that served the request, if there are multiple replicas this data may be incomplete</h4>")
712-
713-
agpl.CoordinatorHTTPDebug(c.agentSockets, c.agentToConnectionSockets, c.agentNameCache)(w, r)
707+
agpl.CoordinatorHTTPDebug(true, c.agentSockets, c.agentToConnectionSockets, c.nodes, c.agentNameCache)(w, r)
714708
}

enterprise/tailnet/pgcoord.go

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"nhooyr.io/websocket"
1818

1919
"cdr.dev/slog"
20-
2120
"github.com/coder/coder/coderd/database"
2221
"github.com/coder/coder/coderd/database/dbauthz"
2322
"github.com/coder/coder/coderd/database/pubsub"

tailnet/coordinator.go

+172-103
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"html/template"
89
"io"
910
"net"
1011
"net/http"
@@ -646,136 +647,204 @@ func (c *coordinator) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
646647
}
647648

648649
func (c *core) serveHTTPDebug(w http.ResponseWriter, r *http.Request) {
649-
w.Header().Set("Content-Type", "text/html; charset=utf-8")
650-
651650
c.mutex.RLock()
652651
defer c.mutex.RUnlock()
653652

654-
_, _ = fmt.Fprintln(w, "<h1>in-memory wireguard coordinator debug</h1>")
655-
656-
CoordinatorHTTPDebug(c.agentSockets, c.agentToConnectionSockets, c.agentNameCache)(w, r)
653+
CoordinatorHTTPDebug(false, c.agentSockets, c.agentToConnectionSockets, c.nodes, c.agentNameCache)(w, r)
657654
}
658655

659656
func CoordinatorHTTPDebug(
657+
ha bool,
660658
agentSocketsMap map[uuid.UUID]Queue,
661659
agentToConnectionSocketsMap map[uuid.UUID]map[uuid.UUID]Queue,
660+
nodesMap map[uuid.UUID]*Node,
662661
agentNameCache *lru.Cache[uuid.UUID, string],
663662
) func(w http.ResponseWriter, _ *http.Request) {
664663
return func(w http.ResponseWriter, _ *http.Request) {
665-
now := time.Now()
666-
667-
type idConn struct {
668-
id uuid.UUID
669-
conn Queue
664+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
665+
666+
tmpl, err := template.New("coordinator_debug").Funcs(template.FuncMap{
667+
"marshal": func(v interface{}) template.JS {
668+
a, err := json.MarshalIndent(v, "", " ")
669+
if err != nil {
670+
//nolint:gosec
671+
return template.JS(fmt.Sprintf(`{"err": %q}`, err))
672+
}
673+
//nolint:gosec
674+
return template.JS(a)
675+
},
676+
}).Parse(coordinatorDebugTmpl)
677+
if err != nil {
678+
w.WriteHeader(http.StatusInternalServerError)
679+
_, _ = w.Write([]byte(err.Error()))
680+
return
670681
}
671682

672-
{
673-
_, _ = fmt.Fprintf(w, "<h2 id=agents><a href=#agents>#</a> agents: total %d</h2>\n", len(agentSocketsMap))
674-
_, _ = fmt.Fprintln(w, "<ul>")
675-
agentSockets := make([]idConn, 0, len(agentSocketsMap))
676-
677-
for id, conn := range agentSocketsMap {
678-
agentSockets = append(agentSockets, idConn{id, conn})
683+
now := time.Now()
684+
data := htmlDebug{HA: ha}
685+
for id, conn := range agentSocketsMap {
686+
start, lastWrite := conn.Stats()
687+
agent := &htmlAgent{
688+
Name: conn.Name(),
689+
ID: id,
690+
CreatedAge: now.Sub(time.Unix(start, 0)).Round(time.Second),
691+
LastWriteAge: now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
692+
Overwrites: int(conn.Overwrites()),
679693
}
680694

681-
slices.SortFunc(agentSockets, func(a, b idConn) bool {
682-
return a.conn.Name() < b.conn.Name()
683-
})
684-
685-
for _, agent := range agentSockets {
686-
start, lastWrite := agent.conn.Stats()
687-
_, _ = fmt.Fprintf(w, "<li style=\"margin-top:4px\"><b>%s</b> (<code>%s</code>): created %v ago, write %v ago, overwrites %d </li>\n",
688-
agent.conn.Name(),
689-
agent.id.String(),
690-
now.Sub(time.Unix(start, 0)).Round(time.Second),
691-
now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
692-
agent.conn.Overwrites(),
693-
)
694-
695-
if conns := agentToConnectionSocketsMap[agent.id]; len(conns) > 0 {
696-
_, _ = fmt.Fprintf(w, "<h3 style=\"margin:0px;font-size:16px;font-weight:400\">connections: total %d</h3>\n", len(conns))
697-
698-
connSockets := make([]idConn, 0, len(conns))
699-
for id, conn := range conns {
700-
connSockets = append(connSockets, idConn{id, conn})
701-
}
702-
slices.SortFunc(connSockets, func(a, b idConn) bool {
703-
return a.id.String() < b.id.String()
704-
})
705-
706-
_, _ = fmt.Fprintln(w, "<ul>")
707-
for _, connSocket := range connSockets {
708-
start, lastWrite := connSocket.conn.Stats()
709-
_, _ = fmt.Fprintf(w, "<li><b>%s</b> (<code>%s</code>): created %v ago, write %v ago </li>\n",
710-
connSocket.conn.Name(),
711-
connSocket.id.String(),
712-
now.Sub(time.Unix(start, 0)).Round(time.Second),
713-
now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
714-
)
715-
}
716-
_, _ = fmt.Fprintln(w, "</ul>")
717-
}
695+
for id, conn := range agentToConnectionSocketsMap[id] {
696+
start, lastWrite := conn.Stats()
697+
agent.Connections = append(agent.Connections, &htmlClient{
698+
Name: conn.Name(),
699+
ID: id,
700+
CreatedAge: now.Sub(time.Unix(start, 0)).Round(time.Second),
701+
LastWriteAge: now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
702+
})
718703
}
704+
slices.SortFunc(agent.Connections, func(a, b *htmlClient) bool {
705+
return a.Name < b.Name
706+
})
719707

720-
_, _ = fmt.Fprintln(w, "</ul>")
708+
data.Agents = append(data.Agents, agent)
721709
}
710+
slices.SortFunc(data.Agents, func(a, b *htmlAgent) bool {
711+
return a.Name < b.Name
712+
})
722713

723-
{
724-
type agentConns struct {
725-
id uuid.UUID
726-
conns []idConn
714+
for agentID, conns := range agentToConnectionSocketsMap {
715+
if len(conns) == 0 {
716+
continue
727717
}
728718

729-
missingAgents := []agentConns{}
730-
for agentID, conns := range agentToConnectionSocketsMap {
731-
if len(conns) == 0 {
732-
continue
719+
if _, ok := agentSocketsMap[agentID]; !ok {
720+
agentName, ok := agentNameCache.Get(agentID)
721+
if !ok {
722+
agentName = "unknown"
733723
}
734-
735-
if _, ok := agentSocketsMap[agentID]; !ok {
736-
connsSlice := make([]idConn, 0, len(conns))
737-
for id, conn := range conns {
738-
connsSlice = append(connsSlice, idConn{id, conn})
739-
}
740-
slices.SortFunc(connsSlice, func(a, b idConn) bool {
741-
return a.id.String() < b.id.String()
724+
agent := &htmlAgent{
725+
Name: agentName,
726+
ID: agentID,
727+
}
728+
for id, conn := range conns {
729+
start, lastWrite := conn.Stats()
730+
agent.Connections = append(agent.Connections, &htmlClient{
731+
Name: conn.Name(),
732+
ID: id,
733+
CreatedAge: now.Sub(time.Unix(start, 0)).Round(time.Second),
734+
LastWriteAge: now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
742735
})
743-
744-
missingAgents = append(missingAgents, agentConns{agentID, connsSlice})
745736
}
737+
slices.SortFunc(agent.Connections, func(a, b *htmlClient) bool {
738+
return a.Name < b.Name
739+
})
740+
741+
data.MissingAgents = append(data.MissingAgents, agent)
746742
}
747-
slices.SortFunc(missingAgents, func(a, b agentConns) bool {
748-
return a.id.String() < b.id.String()
743+
}
744+
slices.SortFunc(data.MissingAgents, func(a, b *htmlAgent) bool {
745+
return a.Name < b.Name
746+
})
747+
748+
for id, node := range nodesMap {
749+
name, _ := agentNameCache.Get(id)
750+
data.Nodes = append(data.Nodes, &htmlNode{
751+
ID: id,
752+
Name: name,
753+
Node: node,
749754
})
755+
}
756+
slices.SortFunc(data.Nodes, func(a, b *htmlNode) bool {
757+
return a.Name+a.ID.String() < b.Name+b.ID.String()
758+
})
750759

751-
_, _ = fmt.Fprintf(w, "<h2 id=missing-agents><a href=#missing-agents>#</a> missing agents: total %d</h2>\n", len(missingAgents))
752-
_, _ = fmt.Fprintln(w, "<ul>")
753-
754-
for _, agentConns := range missingAgents {
755-
agentName, ok := agentNameCache.Get(agentConns.id)
756-
if !ok {
757-
agentName = "unknown"
758-
}
759-
760-
_, _ = fmt.Fprintf(w, "<li style=\"margin-top:4px\"><b>%s</b> (<code>%s</code>): created ? ago, write ? ago, overwrites ? </li>\n",
761-
agentName,
762-
agentConns.id.String(),
763-
)
764-
765-
_, _ = fmt.Fprintf(w, "<h3 style=\"margin:0px;font-size:16px;font-weight:400\">connections: total %d</h3>\n", len(agentConns.conns))
766-
_, _ = fmt.Fprintln(w, "<ul>")
767-
for _, agentConn := range agentConns.conns {
768-
start, lastWrite := agentConn.conn.Stats()
769-
_, _ = fmt.Fprintf(w, "<li><b>%s</b> (<code>%s</code>): created %v ago, write %v ago </li>\n",
770-
agentConn.conn.Name(),
771-
agentConn.id.String(),
772-
now.Sub(time.Unix(start, 0)).Round(time.Second),
773-
now.Sub(time.Unix(lastWrite, 0)).Round(time.Second),
774-
)
775-
}
776-
_, _ = fmt.Fprintln(w, "</ul>")
777-
}
778-
_, _ = fmt.Fprintln(w, "</ul>")
760+
err = tmpl.Execute(w, data)
761+
if err != nil {
762+
w.WriteHeader(http.StatusInternalServerError)
763+
_, _ = w.Write([]byte(err.Error()))
764+
return
779765
}
780766
}
781767
}
768+
769+
type htmlDebug struct {
770+
HA bool
771+
Agents []*htmlAgent
772+
MissingAgents []*htmlAgent
773+
Nodes []*htmlNode
774+
}
775+
776+
type htmlAgent struct {
777+
Name string
778+
ID uuid.UUID
779+
CreatedAge time.Duration
780+
LastWriteAge time.Duration
781+
Overwrites int
782+
Connections []*htmlClient
783+
}
784+
785+
type htmlClient struct {
786+
Name string
787+
ID uuid.UUID
788+
CreatedAge time.Duration
789+
LastWriteAge time.Duration
790+
}
791+
792+
type htmlNode struct {
793+
ID uuid.UUID
794+
Name string
795+
Node *Node
796+
}
797+
798+
var coordinatorDebugTmpl = `
799+
<!DOCTYPE html>
800+
<html>
801+
<head>
802+
<meta charset="UTF-8">
803+
</head>
804+
<body>
805+
{{- if .HA }}
806+
<h1>high-availability wireguard coordinator debug</h1>
807+
<h4 style="margin-top:-25px">warning: this only provides info from the node that served the request, if there are multiple replicas this data may be incomplete</h4>
808+
{{- else }}
809+
<h1>in-memory wireguard coordinator debug</h1>
810+
{{- end }}
811+
812+
<h2 id=agents> <a href=#agents>#</a> agents: total {{ len .Agents }} </h2>
813+
<ul>
814+
{{- range .Agents }}
815+
<li style="margin-top:4px">
816+
<b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago, overwrites {{ .Overwrites }}
817+
<h3 style="margin:0px;font-size:16px;font-weight:400"> connections: total {{ len .Connections}} </h3>
818+
<ul>
819+
{{- range .Connections }}
820+
<li><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago </li>
821+
{{- end }}
822+
</ul>
823+
</li>
824+
{{- end }}
825+
</ul>
826+
827+
<h2 id=missing-agents><a href=#missing-agents>#</a> missing agents: total {{ len .MissingAgents }}</h2>
828+
<ul>
829+
{{- range .MissingAgents}}
830+
<li style="margin-top:4px"><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created ? ago, write ? ago, overwrites ? </li>
831+
<h3 style="margin:0px;font-size:16px;font-weight:400"> connections: total {{ len .Connections }} </h3>
832+
<ul>
833+
{{- range .Connections }}
834+
<li><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago </li>
835+
{{- end }}
836+
</ul>
837+
{{- end }}
838+
</ul>
839+
840+
<h2 id=nodes><a href=#nodes>#</a> nodes: total {{ len .Nodes }}</h2>
841+
<ul>
842+
{{- range .Nodes }}
843+
<li style="margin-top:4px"><b>{{ .Name }}</b> (<code>{{ .ID }}</code>):
844+
<span style="white-space: pre;"><code>{{ marshal .Node }}</code></span>
845+
</li>
846+
{{- end }}
847+
</ul>
848+
</body>
849+
</html>
850+
`

0 commit comments

Comments
 (0)