Skip to content

Commit 66eba87

Browse files
committed
collectors: add StateCollector
1 parent 02ee7ca commit 66eba87

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

collectors/prometheus.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices,
104104
NewWalletCollector(lnd, errChan),
105105
NewPeerCollector(lnd.Client, errChan),
106106
NewInfoCollector(lnd.Client, errChan),
107+
NewStateCollector(lnd, errChan),
107108
}
108109

109110
if !monitoringCfg.DisableHtlc {

collectors/state_collector.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package collectors
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync"
7+
"time"
8+
9+
"github.com/lightninglabs/lndclient"
10+
"github.com/prometheus/client_golang/prometheus"
11+
)
12+
13+
type StateCollector struct {
14+
lnd *lndclient.LndServices
15+
16+
// Use one gauge to track the starting time of LND.
17+
timeToStartDesc *prometheus.Desc
18+
19+
// startTime records a best-effort timestamp of when LND was started.
20+
startTime time.Time
21+
22+
// endTime records when LND makes a transition from RPC_ACTIVE to
23+
// SERVER_ACTIVE.
24+
endTime time.Time
25+
26+
// mutex is a lock for preventing concurrent writes to startTime or
27+
// endTime.
28+
mutex sync.RWMutex
29+
30+
// errChan is a channel that we send any errors that we encounter into.
31+
// This channel should be buffered so that it does not block sends.
32+
errChan chan<- error
33+
}
34+
35+
// NewStateCollector returns a new instance of the StateCollector.
36+
func NewStateCollector(lnd *lndclient.LndServices,
37+
errChan chan<- error) *StateCollector {
38+
39+
sc := &StateCollector{
40+
lnd: lnd,
41+
timeToStartDesc: prometheus.NewDesc(
42+
"lnd_time_to_start_secs",
43+
"time to start in seconds",
44+
nil, nil,
45+
),
46+
errChan: errChan,
47+
}
48+
49+
go sc.monitorStateChanges()
50+
return sc
51+
}
52+
53+
// monitorStateChanges checks the state every second to catch fast transitions.
54+
func (s *StateCollector) monitorStateChanges() {
55+
for {
56+
state, err := s.lnd.State.GetState(context.Background())
57+
if err != nil {
58+
s.errChan <- fmt.Errorf("StateCollector GetState failed with: %v", err)
59+
continue
60+
}
61+
62+
s.mutex.Lock()
63+
if state.String() == "RPC_ACTIVE" {
64+
s.startTime = time.Now()
65+
} else if state.String() == "SERVER_ACTIVE" && !s.startTime.IsZero() {
66+
s.endTime = time.Now()
67+
}
68+
s.mutex.Unlock()
69+
70+
time.Sleep(1 * time.Second)
71+
}
72+
}
73+
74+
// Describe sends the super-set of all possible descriptors of metrics
75+
// collected by this Collector to the provided channel and returns once the
76+
// last descriptor has been sent.
77+
//
78+
// NOTE: Part of the prometheus.Collector interface.
79+
func (s *StateCollector) Describe(ch chan<- *prometheus.Desc) {
80+
ch <- s.timeToStartDesc
81+
}
82+
83+
// Collect is called by the Prometheus registry when collecting metrics.
84+
//
85+
// NOTE: Part of the prometheus.Collector interface.
86+
func (s *StateCollector) Collect(ch chan<- prometheus.Metric) {
87+
// Lock for read
88+
s.mutex.RLock()
89+
defer s.mutex.RUnlock()
90+
91+
// We have set both a startTime and endTime, calculate the difference and emit a metric.
92+
if !s.startTime.IsZero() && !s.endTime.IsZero() {
93+
timeToStartInSecs := s.endTime.Sub(s.startTime).Seconds()
94+
ch <- prometheus.MustNewConstMetric(
95+
s.timeToStartDesc, prometheus.GaugeValue, timeToStartInSecs,
96+
)
97+
}
98+
}

0 commit comments

Comments
 (0)