Skip to content

Commit 5a38420

Browse files
collectors: collect payment and attempt counts
collect raw payment and attempt count information from lnd using the TrackPayments RPC.
1 parent 98a89e9 commit 5a38420

File tree

5 files changed

+201
-9
lines changed

5 files changed

+201
-9
lines changed

collectors/log.go

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ var (
2020

2121
// htlcLogger is a logger for lndmon's htlc collector.
2222
htlcLogger = build.NewSubLogger("HTLC", backendLog.Logger)
23+
24+
// paymentLogger is a logger for lndmon's payments monitor.
25+
paymentLogger = build.NewSubLogger("PMNT", backendLog.Logger)
2326
)
2427

2528
// initLogRotator initializes the logging rotator to write logs to logFile and

collectors/payments_collector.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package collectors
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync"
7+
8+
"github.com/lightninglabs/lndclient"
9+
"github.com/lightningnetwork/lnd/lnrpc"
10+
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
11+
"github.com/prometheus/client_golang/prometheus"
12+
)
13+
14+
var (
15+
totalPayments = prometheus.NewCounter(
16+
prometheus.CounterOpts{
17+
Name: "lnd_total_payments",
18+
Help: "Total number of payments initiated",
19+
},
20+
)
21+
totalHTLCAttempts = prometheus.NewCounter(
22+
prometheus.CounterOpts{
23+
Name: "lnd_total_htlc_attempts",
24+
Help: "Total number of HTLC attempts across all payments",
25+
},
26+
)
27+
paymentAttempts = prometheus.NewHistogram(
28+
prometheus.HistogramOpts{
29+
Name: "lnd_payment_attempts_per_payment",
30+
Help: "Histogram tracking the number of attempts per payment",
31+
Buckets: prometheus.ExponentialBucketsRange(1, 2, 10),
32+
},
33+
)
34+
)
35+
36+
// paymentsMonitor listens for payments and updates Prometheus metrics.
37+
type paymentsMonitor struct {
38+
client routerrpc.RouterClient
39+
40+
ctx context.Context
41+
42+
errChan chan error
43+
44+
// quit is closed to signal that we need to shutdown.
45+
quit chan struct{}
46+
47+
wg sync.WaitGroup
48+
}
49+
50+
// newPaymentsMonitor creates a new payments monitor and ensures the context
51+
// includes macaroon authentication.
52+
func newPaymentsMonitor(lnd *lndclient.LndServices,
53+
errChan chan error) (*paymentsMonitor, error) {
54+
55+
// Attach macaroon authentication for the router service.
56+
ctx := context.Background()
57+
ctx, err := lnd.WithMacaroonAuthForService(
58+
ctx, lndclient.RouterServiceMac,
59+
)
60+
if err != nil {
61+
return nil, fmt.Errorf("failed to get macaroon-authenticated "+
62+
"context: %w", err)
63+
}
64+
65+
return &paymentsMonitor{
66+
client: routerrpc.NewRouterClient(lnd.ClientConn),
67+
errChan: errChan,
68+
quit: make(chan struct{}),
69+
ctx: ctx,
70+
}, nil
71+
}
72+
73+
// start subscribes to `TrackPayments` and updates Prometheus metrics.
74+
func (p *paymentsMonitor) start() error {
75+
// Use the stored authenticated context.
76+
ctx, cancel := context.WithCancel(p.ctx)
77+
78+
paymentLogger.Info("Starting payments monitor...")
79+
80+
stream, err := p.client.TrackPayments(
81+
ctx, &routerrpc.TrackPaymentsRequest{
82+
// NOTE: We only need to know the final result of the
83+
// payment and all attempts.
84+
NoInflightUpdates: true,
85+
})
86+
if err != nil {
87+
paymentLogger.Errorf("Failed to subscribe to TrackPayments: %v",
88+
err)
89+
90+
cancel()
91+
92+
return err
93+
}
94+
95+
p.wg.Add(1)
96+
go func() {
97+
defer func() {
98+
cancel()
99+
p.wg.Done()
100+
}()
101+
102+
for {
103+
select {
104+
case <-p.quit:
105+
return
106+
107+
default:
108+
payment, err := stream.Recv()
109+
if err != nil {
110+
paymentLogger.Errorf("Error receiving "+
111+
"payment update: %v", err)
112+
113+
p.errChan <- err
114+
return
115+
}
116+
processPaymentUpdate(payment)
117+
}
118+
}
119+
}()
120+
121+
return nil
122+
}
123+
124+
// stop cancels the payments monitor subscription.
125+
func (p *paymentsMonitor) stop() {
126+
paymentLogger.Info("Stopping payments monitor...")
127+
128+
close(p.quit)
129+
p.wg.Wait()
130+
}
131+
132+
// collectors returns all of the collectors that the htlc monitor uses.
133+
func (p *paymentsMonitor) collectors() []prometheus.Collector {
134+
return []prometheus.Collector{
135+
totalPayments, totalHTLCAttempts, paymentAttempts,
136+
}
137+
}
138+
139+
// processPaymentUpdate updates Prometheus metrics based on received payments.
140+
//
141+
// NOTE: It is expected that this receive the *final* payment update with the
142+
// complete list of all htlc attempts made for this payment.
143+
func processPaymentUpdate(payment *lnrpc.Payment) {
144+
totalPayments.Inc()
145+
attemptCount := len(payment.Htlcs)
146+
147+
totalHTLCAttempts.Add(float64(attemptCount))
148+
paymentAttempts.Observe(float64(attemptCount))
149+
150+
paymentLogger.Debugf("Payment %s updated: %d attempts",
151+
payment.PaymentHash, attemptCount)
152+
}

collectors/prometheus.go

+40-7
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ type PrometheusExporter struct {
3232

3333
monitoringCfg *MonitoringConfig
3434

35-
htlcMonitor *htlcMonitor
35+
htlcMonitor *htlcMonitor
36+
paymentsMonitor *paymentsMonitor
3637

3738
// collectors is the exporter's active set of collectors.
3839
collectors []prometheus.Collector
@@ -72,6 +73,9 @@ type MonitoringConfig struct {
7273
// DisableHtlc disables collection of HTLCs metrics
7374
DisableHtlc bool
7475

76+
// DisablePayments disables collection of payment metrics
77+
DisablePayments bool
78+
7579
// ProgramStartTime stores a best-effort estimate of when lnd/lndmon was
7680
// started.
7781
ProgramStartTime time.Time
@@ -100,6 +104,15 @@ func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices,
100104

101105
htlcMonitor := newHtlcMonitor(lnd.Router, errChan)
102106

107+
// Create payments monitor.
108+
paymentsMonitor, err := newPaymentsMonitor(lnd, errChan)
109+
if err != nil {
110+
paymentLogger.Errorf("Failed to initialize payment monitor: %v",
111+
err)
112+
113+
return nil
114+
}
115+
103116
chanCollector := NewChannelsCollector(
104117
lnd.Client, errChan, quitChan, monitoringCfg,
105118
)
@@ -117,19 +130,26 @@ func NewPrometheusExporter(cfg *PrometheusConfig, lnd *lndclient.LndServices,
117130
collectors = append(collectors, htlcMonitor.collectors()...)
118131
}
119132

133+
if !monitoringCfg.DisablePayments {
134+
collectors = append(
135+
collectors, paymentsMonitor.collectors()...,
136+
)
137+
}
138+
120139
if !monitoringCfg.DisableGraph {
121140
collectors = append(
122141
collectors, NewGraphCollector(lnd.Client, errChan),
123142
)
124143
}
125144

126145
return &PrometheusExporter{
127-
cfg: cfg,
128-
lnd: lnd,
129-
monitoringCfg: monitoringCfg,
130-
collectors: collectors,
131-
htlcMonitor: htlcMonitor,
132-
errChan: errChan,
146+
cfg: cfg,
147+
lnd: lnd,
148+
monitoringCfg: monitoringCfg,
149+
collectors: collectors,
150+
htlcMonitor: htlcMonitor,
151+
paymentsMonitor: paymentsMonitor,
152+
errChan: errChan,
133153
}
134154
}
135155

@@ -165,6 +185,15 @@ func (p *PrometheusExporter) Start() error {
165185
}
166186
}
167187

188+
// Start the payment monitor goroutine. This will subscribe to receive
189+
// update for all payments made by lnd and update our payments related
190+
// metrics.
191+
if !p.monitoringCfg.DisablePayments {
192+
if err := p.paymentsMonitor.start(); err != nil {
193+
return err
194+
}
195+
}
196+
168197
// Finally, we'll launch the HTTP server that Prometheus will use to
169198
// scape our metrics.
170199
go func() {
@@ -199,6 +228,10 @@ func (p *PrometheusExporter) Stop() {
199228
if !p.monitoringCfg.DisableHtlc {
200229
p.htlcMonitor.stop()
201230
}
231+
232+
if !p.monitoringCfg.DisablePayments {
233+
p.paymentsMonitor.stop()
234+
}
202235
}
203236

204237
// Errors returns an error channel that any failures experienced by its

config.go

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ type config struct {
5454

5555
// DisableHtlc disables the collection of HTLCs metrics.
5656
DisableHtlc bool `long:"disablehtlc" description:"Do not collect HTLCs metrics"`
57+
58+
// DisablePayments disables the collection of payments metrics.
59+
DisablePayments bool `long:"disablepayments" description:"Do not collect payments metrics"`
5760
}
5861

5962
var defaultConfig = config{

lndmon.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ func start() error {
6363
defer lnd.Close()
6464

6565
monitoringCfg := collectors.MonitoringConfig{
66-
DisableGraph: cfg.DisableGraph,
67-
DisableHtlc: cfg.DisableHtlc,
66+
DisableGraph: cfg.DisableGraph,
67+
DisableHtlc: cfg.DisableHtlc,
68+
DisablePayments: cfg.DisablePayments,
6869
}
6970
if cfg.PrimaryNode != "" {
7071
primaryNode, err := route.NewVertexFromStr(cfg.PrimaryNode)

0 commit comments

Comments
 (0)