@@ -5,17 +5,22 @@ import (
5
5
"context"
6
6
"encoding/json"
7
7
"errors"
8
+ "fmt"
9
+ "html/template"
8
10
"io"
9
11
"net"
10
12
"net/http"
11
13
"sync"
14
+ "time"
12
15
13
16
"github.com/google/uuid"
14
17
lru "github.com/hashicorp/golang-lru/v2"
18
+ "golang.org/x/exp/slices"
15
19
"golang.org/x/xerrors"
16
20
17
21
"cdr.dev/slog"
18
22
"github.com/coder/coder/v2/coderd/database/pubsub"
23
+ "github.com/coder/coder/v2/coderd/util/slice"
19
24
"github.com/coder/coder/v2/codersdk"
20
25
agpl "github.com/coder/coder/v2/tailnet"
21
26
)
@@ -719,7 +724,209 @@ func (c *haCoordinator) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
719
724
c .mutex .RLock ()
720
725
defer c .mutex .RUnlock ()
721
726
722
- agpl . CoordinatorHTTPDebug (
723
- agpl . HTTPDebugFromLocal (true , c .agentSockets , c .agentToConnectionSockets , c .nodes , c .agentNameCache ),
727
+ CoordinatorHTTPDebug (
728
+ HTTPDebugFromLocal (true , c .agentSockets , c .agentToConnectionSockets , c .nodes , c .agentNameCache ),
724
729
)(w , r )
725
730
}
731
+
732
+ func HTTPDebugFromLocal (
733
+ ha bool ,
734
+ agentSocketsMap map [uuid.UUID ]agpl.Queue ,
735
+ agentToConnectionSocketsMap map [uuid.UUID ]map [uuid.UUID ]agpl.Queue ,
736
+ nodesMap map [uuid.UUID ]* agpl.Node ,
737
+ agentNameCache * lru.Cache [uuid.UUID , string ],
738
+ ) HTMLDebugHA {
739
+ now := time .Now ()
740
+ data := HTMLDebugHA {HA : ha }
741
+ for id , conn := range agentSocketsMap {
742
+ start , lastWrite := conn .Stats ()
743
+ agent := & HTMLAgent {
744
+ Name : conn .Name (),
745
+ ID : id ,
746
+ CreatedAge : now .Sub (time .Unix (start , 0 )).Round (time .Second ),
747
+ LastWriteAge : now .Sub (time .Unix (lastWrite , 0 )).Round (time .Second ),
748
+ Overwrites : int (conn .Overwrites ()),
749
+ }
750
+
751
+ for id , conn := range agentToConnectionSocketsMap [id ] {
752
+ start , lastWrite := conn .Stats ()
753
+ agent .Connections = append (agent .Connections , & HTMLClient {
754
+ Name : conn .Name (),
755
+ ID : id ,
756
+ CreatedAge : now .Sub (time .Unix (start , 0 )).Round (time .Second ),
757
+ LastWriteAge : now .Sub (time .Unix (lastWrite , 0 )).Round (time .Second ),
758
+ })
759
+ }
760
+ slices .SortFunc (agent .Connections , func (a , b * HTMLClient ) int {
761
+ return slice .Ascending (a .Name , b .Name )
762
+ })
763
+
764
+ data .Agents = append (data .Agents , agent )
765
+ }
766
+ slices .SortFunc (data .Agents , func (a , b * HTMLAgent ) int {
767
+ return slice .Ascending (a .Name , b .Name )
768
+ })
769
+
770
+ for agentID , conns := range agentToConnectionSocketsMap {
771
+ if len (conns ) == 0 {
772
+ continue
773
+ }
774
+
775
+ if _ , ok := agentSocketsMap [agentID ]; ok {
776
+ continue
777
+ }
778
+
779
+ agentName , ok := agentNameCache .Get (agentID )
780
+ if ! ok {
781
+ agentName = "unknown"
782
+ }
783
+ agent := & HTMLAgent {
784
+ Name : agentName ,
785
+ ID : agentID ,
786
+ }
787
+ for id , conn := range conns {
788
+ start , lastWrite := conn .Stats ()
789
+ agent .Connections = append (agent .Connections , & HTMLClient {
790
+ Name : conn .Name (),
791
+ ID : id ,
792
+ CreatedAge : now .Sub (time .Unix (start , 0 )).Round (time .Second ),
793
+ LastWriteAge : now .Sub (time .Unix (lastWrite , 0 )).Round (time .Second ),
794
+ })
795
+ }
796
+ slices .SortFunc (agent .Connections , func (a , b * HTMLClient ) int {
797
+ return slice .Ascending (a .Name , b .Name )
798
+ })
799
+
800
+ data .MissingAgents = append (data .MissingAgents , agent )
801
+ }
802
+ slices .SortFunc (data .MissingAgents , func (a , b * HTMLAgent ) int {
803
+ return slice .Ascending (a .Name , b .Name )
804
+ })
805
+
806
+ for id , node := range nodesMap {
807
+ name , _ := agentNameCache .Get (id )
808
+ data .Nodes = append (data .Nodes , & HTMLNode {
809
+ ID : id ,
810
+ Name : name ,
811
+ Node : node ,
812
+ })
813
+ }
814
+ slices .SortFunc (data .Nodes , func (a , b * HTMLNode ) int {
815
+ return slice .Ascending (a .Name + a .ID .String (), b .Name + b .ID .String ())
816
+ })
817
+
818
+ return data
819
+ }
820
+
821
+ func CoordinatorHTTPDebug (data HTMLDebugHA ) func (w http.ResponseWriter , _ * http.Request ) {
822
+ return func (w http.ResponseWriter , _ * http.Request ) {
823
+ w .Header ().Set ("Content-Type" , "text/html; charset=utf-8" )
824
+
825
+ tmpl , err := template .New ("coordinator_debug" ).Funcs (template.FuncMap {
826
+ "marshal" : func (v any ) template.JS {
827
+ a , err := json .MarshalIndent (v , "" , " " )
828
+ if err != nil {
829
+ //nolint:gosec
830
+ return template .JS (fmt .Sprintf (`{"err": %q}` , err ))
831
+ }
832
+ //nolint:gosec
833
+ return template .JS (a )
834
+ },
835
+ }).Parse (haCoordinatorDebugTmpl )
836
+ if err != nil {
837
+ w .WriteHeader (http .StatusInternalServerError )
838
+ _ , _ = w .Write ([]byte (err .Error ()))
839
+ return
840
+ }
841
+
842
+ err = tmpl .Execute (w , data )
843
+ if err != nil {
844
+ w .WriteHeader (http .StatusInternalServerError )
845
+ _ , _ = w .Write ([]byte (err .Error ()))
846
+ return
847
+ }
848
+ }
849
+ }
850
+
851
+ type HTMLDebugHA struct {
852
+ HA bool
853
+ Agents []* HTMLAgent
854
+ MissingAgents []* HTMLAgent
855
+ Nodes []* HTMLNode
856
+ }
857
+
858
+ type HTMLAgent struct {
859
+ Name string
860
+ ID uuid.UUID
861
+ CreatedAge time.Duration
862
+ LastWriteAge time.Duration
863
+ Overwrites int
864
+ Connections []* HTMLClient
865
+ }
866
+
867
+ type HTMLClient struct {
868
+ Name string
869
+ ID uuid.UUID
870
+ CreatedAge time.Duration
871
+ LastWriteAge time.Duration
872
+ }
873
+
874
+ type HTMLNode struct {
875
+ ID uuid.UUID
876
+ Name string
877
+ Node any
878
+ }
879
+
880
+ var haCoordinatorDebugTmpl = `
881
+ <!DOCTYPE html>
882
+ <html>
883
+ <head>
884
+ <meta charset="UTF-8">
885
+ </head>
886
+ <body>
887
+ {{- if .HA }}
888
+ <h1>high-availability wireguard coordinator debug</h1>
889
+ <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>
890
+ {{- else }}
891
+ <h1>in-memory wireguard coordinator debug</h1>
892
+ {{- end }}
893
+
894
+ <h2 id=agents> <a href=#agents>#</a> agents: total {{ len .Agents }} </h2>
895
+ <ul>
896
+ {{- range .Agents }}
897
+ <li style="margin-top:4px">
898
+ <b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago, overwrites {{ .Overwrites }}
899
+ <h3 style="margin:0px;font-size:16px;font-weight:400"> connections: total {{ len .Connections}} </h3>
900
+ <ul>
901
+ {{- range .Connections }}
902
+ <li><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago </li>
903
+ {{- end }}
904
+ </ul>
905
+ </li>
906
+ {{- end }}
907
+ </ul>
908
+
909
+ <h2 id=missing-agents><a href=#missing-agents>#</a> missing agents: total {{ len .MissingAgents }}</h2>
910
+ <ul>
911
+ {{- range .MissingAgents}}
912
+ <li style="margin-top:4px"><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created ? ago, write ? ago, overwrites ? </li>
913
+ <h3 style="margin:0px;font-size:16px;font-weight:400"> connections: total {{ len .Connections }} </h3>
914
+ <ul>
915
+ {{- range .Connections }}
916
+ <li><b>{{ .Name }}</b> (<code>{{ .ID }}</code>): created {{ .CreatedAge }} ago, write {{ .LastWriteAge }} ago </li>
917
+ {{- end }}
918
+ </ul>
919
+ {{- end }}
920
+ </ul>
921
+
922
+ <h2 id=nodes><a href=#nodes>#</a> nodes: total {{ len .Nodes }}</h2>
923
+ <ul>
924
+ {{- range .Nodes }}
925
+ <li style="margin-top:4px"><b>{{ .Name }}</b> (<code>{{ .ID }}</code>):
926
+ <span style="white-space: pre;"><code>{{ marshal .Node }}</code></span>
927
+ </li>
928
+ {{- end }}
929
+ </ul>
930
+ </body>
931
+ </html>
932
+ `
0 commit comments