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