Skip to content

Commit

Permalink
collectors: add StateCollector
Browse files Browse the repository at this point in the history
  • Loading branch information
djkazic committed Jun 26, 2024
1 parent 02ee7ca commit 66eba87
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
1 change: 1 addition & 0 deletions collectors/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices,
NewWalletCollector(lnd, errChan),
NewPeerCollector(lnd.Client, errChan),
NewInfoCollector(lnd.Client, errChan),
NewStateCollector(lnd, errChan),
}

if !monitoringCfg.DisableHtlc {
Expand Down
98 changes: 98 additions & 0 deletions collectors/state_collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package collectors

import (
"context"
"fmt"
"sync"
"time"

"github.com/lightninglabs/lndclient"
"github.com/prometheus/client_golang/prometheus"
)

type StateCollector struct {
lnd *lndclient.LndServices

// Use one gauge to track the starting time of LND.
timeToStartDesc *prometheus.Desc

// startTime records a best-effort timestamp of when LND was started.
startTime time.Time

// endTime records when LND makes a transition from RPC_ACTIVE to
// SERVER_ACTIVE.
endTime time.Time

// mutex is a lock for preventing concurrent writes to startTime or
// endTime.
mutex sync.RWMutex

// errChan is a channel that we send any errors that we encounter into.
// This channel should be buffered so that it does not block sends.
errChan chan<- error
}

// NewStateCollector returns a new instance of the StateCollector.
func NewStateCollector(lnd *lndclient.LndServices,
errChan chan<- error) *StateCollector {

sc := &StateCollector{
lnd: lnd,
timeToStartDesc: prometheus.NewDesc(
"lnd_time_to_start_secs",
"time to start in seconds",
nil, nil,
),
errChan: errChan,
}

go sc.monitorStateChanges()
return sc
}

// monitorStateChanges checks the state every second to catch fast transitions.
func (s *StateCollector) monitorStateChanges() {
for {
state, err := s.lnd.State.GetState(context.Background())
if err != nil {
s.errChan <- fmt.Errorf("StateCollector GetState failed with: %v", err)
continue
}

s.mutex.Lock()
if state.String() == "RPC_ACTIVE" {
s.startTime = time.Now()
} else if state.String() == "SERVER_ACTIVE" && !s.startTime.IsZero() {
s.endTime = time.Now()
}
s.mutex.Unlock()

time.Sleep(1 * time.Second)
}
}

// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once the
// last descriptor has been sent.
//
// NOTE: Part of the prometheus.Collector interface.
func (s *StateCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- s.timeToStartDesc
}

// Collect is called by the Prometheus registry when collecting metrics.
//
// NOTE: Part of the prometheus.Collector interface.
func (s *StateCollector) Collect(ch chan<- prometheus.Metric) {
// Lock for read
s.mutex.RLock()
defer s.mutex.RUnlock()

// We have set both a startTime and endTime, calculate the difference and emit a metric.
if !s.startTime.IsZero() && !s.endTime.IsZero() {
timeToStartInSecs := s.endTime.Sub(s.startTime).Seconds()
ch <- prometheus.MustNewConstMetric(
s.timeToStartDesc, prometheus.GaugeValue, timeToStartInSecs,
)
}
}

0 comments on commit 66eba87

Please sign in to comment.