Skip to content
File

Blob: chord/overview_handler.go

go106 lines
1package chord
2 
3import (
4 "encoding/json"
5 "net/http"
6 "strconv"
7 "time"
8 
9 "go.miragespace.co/specter/spec/protocol"
10)
11 
12type overviewIdentity struct {
13 ID string `json:"id"`
14 Address string `json:"address"`
15}
16 
17type overviewVNode struct {
18 Identity overviewIdentity `json:"identity"`
19 Root bool `json:"root"`
20 State string `json:"state"`
21 LastStabilized *time.Time `json:"last_stabilized"`
22 ChordRequests uint64 `json:"chord_requests"`
23 KVRequests uint64 `json:"kv_requests"`
24 RPCErrors uint64 `json:"rpc_errors"`
25 KVStale uint64 `json:"kv_stale"`
26 PredecessorAvailable bool `json:"predecessor_available"`
27 Predecessor *overviewIdentity `json:"predecessor"`
28 SuccessorsAvailable bool `json:"successors_available"`
29 Successors []overviewIdentity `json:"successors"`
30}
31 
32type localOverview struct {
33 ObservedAt time.Time `json:"observed_at"`
34 Identity overviewIdentity `json:"identity"`
35 Provider string `json:"provider"`
36 VNodes []overviewVNode `json:"vnodes"`
37}
38 
39func overviewNodeIdentity(node *protocol.Node) overviewIdentity {
40 return overviewIdentity{
41 ID: strconv.FormatUint(node.GetId(), 10),
42 Address: node.GetAddress(),
43 }
44}
45 
46// snapshotOverview samples local counters and membership references independently.
47// It never consults storage or calls peers. Membership changes can hold locks
48// across network requests, so a busy reference is reported as unavailable.
49func snapshotOverview(rootNode *LocalNode, virtualNodes []*LocalNode, provider string) localOverview {
50 info := localOverview{
51 ObservedAt: time.Now().UTC(),
52 Identity: overviewNodeIdentity(rootNode.Identity()),
53 Provider: provider,
54 VNodes: make([]overviewVNode, 0, len(virtualNodes)),
55 }
56 for _, node := range virtualNodes {
57 vnode := overviewVNode{
58 Identity: overviewNodeIdentity(node.Identity()),
59 Root: node == rootNode,
60 State: node.state.Get().String(),
61 ChordRequests: node.chordRate.Total(),
62 KVRequests: node.kvRate.Total(),
63 RPCErrors: node.rpcErrorCount.Load(),
64 KVStale: node.kvStaleCount.Load(),
65 Successors: make([]overviewIdentity, 0),
66 }
67 if stabilized := node.lastStabilized.Load(); !stabilized.IsZero() {
68 vnode.LastStabilized = &stabilized
69 }
70 if node.predecessorMu.TryRLock() {
71 vnode.PredecessorAvailable = true
72 if node.predecessor != nil {
73 predecessor := overviewNodeIdentity(node.predecessor.Identity())
74 vnode.Predecessor = &predecessor
75 }
76 node.predecessorMu.RUnlock()
77 }
78 if node.successorsMu.TryRLock() {
79 vnode.SuccessorsAvailable = true
80 for _, successor := range node.successors {
81 if successor != nil {
82 vnode.Successors = append(vnode.Successors, overviewNodeIdentity(successor.Identity()))
83 }
84 }
85 node.successorsMu.RUnlock()
86 }
87 info.VNodes = append(info.VNodes, vnode)
88 }
89 return info
90}
91 
92// OverviewHandler serves a bounded local JSON overview. The composition layer supplies
93// the effective provider name; the diagnostic snapshot does not identify storage
94// implementations or inspect their data directories.
95//
96// Mount this behind the same authentication as the other operator handlers at
97// /_internal/overview.json.
98func OverviewHandler(rootNode *LocalNode, virtualNodes []*LocalNode, provider string) http.Handler {
99 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
100 info := snapshotOverview(rootNode, virtualNodes, provider)
101 w.Header().Set("Cache-Control", "no-store")
102 w.Header().Set("Content-Type", "application/json; charset=utf-8")
103 json.NewEncoder(w).Encode(info)
104 })
105}