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

Lines changed: 1 addition & 7 deletions
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

Lines changed: 0 additions & 1 deletion
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

Lines changed: 172 additions & 103 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy