Skip to content

Commit 2b98789

Browse files
committed
add healthcheck and refactor stats
1 parent 5c75cdf commit 2b98789

File tree

3 files changed

+151
-37
lines changed

3 files changed

+151
-37
lines changed

lncd/healthcheck.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"net/http"
7+
)
8+
9+
type HealthStatus struct {
10+
Status string `json:"status"`
11+
Stats Stats
12+
Message string `json:"message"`
13+
14+
}
15+
16+
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
17+
var stats *Stats = getStats()
18+
var err error = nil
19+
if stats == nil {
20+
err = errors.New("starting")
21+
}
22+
23+
if err == nil {
24+
w.Header().Set("Content-Type", "application/json")
25+
err = json.NewEncoder(w).Encode(HealthStatus{
26+
Status: "OK",
27+
Stats: *stats,
28+
Message: "",
29+
})
30+
}
31+
32+
if err != nil {
33+
w.Header().Set("Content-Type", "application/json")
34+
w.WriteHeader(http.StatusServiceUnavailable)
35+
json.NewEncoder(w).Encode(HealthStatus{
36+
Status: "FAIL",
37+
Stats: Stats{},
38+
Message: err.Error(),
39+
})
40+
41+
}
42+
}

lncd/lncd.go

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ type RpcRequest struct {
118118
type RpcResponse struct {
119119
Connection ConnectionInfo
120120
Result string
121+
err error
122+
errCode int
121123
}
122124

123125
func NewConnectionPool() *ConnectionPool {
@@ -305,52 +307,68 @@ func (pool *ConnectionPool) execute(info ConnectionInfo, req Action) {
305307
}
306308
}
307309

310+
func writeJSONError(w http.ResponseWriter, message string, statusCode int) {
311+
w.Header().Set("Content-Type", "application/json")
312+
w.WriteHeader(statusCode)
313+
json.NewEncoder(w).Encode(map[string]string{"error": message})
314+
}
315+
308316
func rpcHandler(pool *ConnectionPool) http.HandlerFunc {
309317
return func(w http.ResponseWriter, r *http.Request) {
318+
var request RpcRequest
319+
defer r.Body.Close()
320+
310321
if r.Method != http.MethodPost {
311-
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
322+
writeJSONError(w, "Method not allowed", http.StatusMethodNotAllowed)
312323
return
313324
}
314-
var request RpcRequest
325+
315326
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
316-
http.Error(w, "Invalid JSON", http.StatusBadRequest)
327+
writeJSONError(w, err.Error(), http.StatusBadRequest)
317328
return
318329
}
319-
defer r.Body.Close()
320330

321-
done := make(chan struct{})
322331

323332
log.Infof("Incoming RPC request: %v", request.Method)
324333
if UNSAFE_LOGS {
325334
log.Debugf("Full request: %v", request)
326335
}
336+
337+
var waitResponse chan RpcResponse = make(chan RpcResponse)
327338

328-
var response RpcResponse
329339
pool.execute(request.Connection, Action{
330340
method: request.Method,
331341
payload: request.Payload,
332342
onError: func(err error) {
333-
log.Errorf("RPC error: %v", err)
334-
http.Error(w, err.Error(), http.StatusInternalServerError)
335-
close(done)
343+
waitResponse <- RpcResponse{err: err, errCode: http.StatusInternalServerError}
344+
close(waitResponse)
336345
},
337346
onResponse: func(info ConnectionInfo, result string) {
338347
log.Debugf("RPC response: %v", result)
339348
if UNSAFE_LOGS {
340349
log.Debugf("Connection: %v", info)
341350
}
342-
response = RpcResponse{
351+
waitResponse <- RpcResponse{
343352
Connection: info,
344353
Result: result,
354+
err: nil,
355+
errCode: http.StatusOK,
345356
}
346-
close(done)
357+
close(waitResponse)
347358
},
348359
})
349360

350-
<-done
351-
w.Header().Set("Content-Type", "application/json")
352-
if err := json.NewEncoder(w).Encode(response); err != nil {
353-
http.Error(w, err.Error(), http.StatusInternalServerError)
361+
var resp RpcResponse = <-waitResponse
362+
if resp.err == nil {
363+
w.Header().Set("Content-Type", "application/json")
364+
if err := json.NewEncoder(w).Encode(resp); err != nil {
365+
resp.err = err
366+
resp.errCode = http.StatusInternalServerError
367+
}
368+
}
369+
370+
if resp.err != nil {
371+
writeJSONError(w, resp.err.Error(), resp.errCode)
354372
}
355373
}
356374
}
@@ -420,27 +438,6 @@ func parseKeys(localPrivKey, remotePubKey string) (
420438

421439

422440

423-
func stats(pool *ConnectionPool) {
424-
ticker := time.NewTicker(LNCD_STATS_INTERVAL)
425-
go func() {
426-
for range ticker.C {
427-
pool.mutex.Lock()
428-
numConnections := len(pool.connections)
429-
log.Infof("Number of active connections: %d", numConnections)
430-
431-
index := 0
432-
for _, conn := range pool.connections {
433-
log.Infof("Connection %d", index)
434-
pendingActions := len(conn.actions)
435-
log.Infof(" Pending actions: %d", pendingActions)
436-
log.Infof(" Connection status: %v", conn.connInfo.Status)
437-
index++
438-
}
439-
pool.mutex.Unlock()
440-
}
441-
}()
442-
}
443-
444441

445442
func main() {
446443
shutdownInterceptor, err := signal.Intercept()
@@ -463,10 +460,11 @@ func main() {
463460
}
464461

465462
var pool *ConnectionPool = NewConnectionPool()
466-
stats(pool)
463+
startStatsLoop(pool)
467464

468465
http.HandleFunc("/rpc", rpcHandler(pool))
469466
http.HandleFunc("/", formHandler)
467+
http.HandleFunc("/health", healthCheckHandler)
470468

471469
log.Infof("Server started at "+LNCD_RECEIVER_HOST+":" + LNCD_RECEIVER_PORT)
472470
if err := http.ListenAndServe(LNCD_RECEIVER_HOST+":"+LNCD_RECEIVER_PORT, nil); err != nil {

lncd/stats.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
var (
9+
lastStats *Stats = nil
10+
)
11+
12+
type ConnectionStats struct {
13+
NumPendingActions int
14+
Status string
15+
}
16+
17+
type Stats struct {
18+
NumConnections int
19+
Connections []ConnectionStats
20+
}
21+
22+
func refreshStats(pool *ConnectionPool, stats *Stats) *Stats {
23+
pool.mutex.Lock()
24+
defer pool.mutex.Unlock()
25+
26+
if stats == nil {
27+
stats = &Stats{
28+
NumConnections: 0,
29+
Connections: nil,
30+
}
31+
}
32+
33+
stats.NumConnections = len(pool.connections)
34+
if stats.Connections == nil || len(stats.Connections) != len(pool.connections) {
35+
stats.Connections = make([]ConnectionStats, len(pool.connections))
36+
}
37+
38+
var i int
39+
for _, conn := range pool.connections {
40+
stats.Connections[i] = ConnectionStats{
41+
NumPendingActions: len(conn.actions),
42+
Status: conn.connInfo.Status,
43+
}
44+
i++
45+
}
46+
47+
return stats
48+
}
49+
50+
func getStats() *Stats {
51+
return lastStats
52+
}
53+
54+
func startStatsLoop(pool *ConnectionPool) {
55+
ticker := time.NewTicker(LNCD_STATS_INTERVAL)
56+
go func() {
57+
for range ticker.C {
58+
lastStats = refreshStats(pool, lastStats)
59+
60+
if lastStats != nil {
61+
var statsString string = ""
62+
statsString += fmt.Sprintf("Active connections: %d\n", lastStats.NumConnections)
63+
for i, conn := range lastStats.Connections {
64+
statsString += fmt.Sprintf(" Connection id: %d\n", i)
65+
statsString += fmt.Sprintf(" Pending actions: %d\n", conn.NumPendingActions)
66+
statsString += fmt.Sprintf(" Status: %s", conn.Status)
67+
}
68+
log.Debugf("Stats:\n %s", statsString)
69+
}
70+
}
71+
}()
72+
}
73+
74+

0 commit comments

Comments
 (0)