diff --git a/monitoring/metrics.go b/monitoring/metrics.go new file mode 100644 index 000000000..94c8126d4 --- /dev/null +++ b/monitoring/metrics.go @@ -0,0 +1,76 @@ +//go:build monitoring +// +build monitoring + +package monitoring + +import ( + "sync" + + "github.com/lightningnetwork/lnd/lncfg" +) + +// MetricGroupCreator is a factory function that initializes a metric group. +type MetricGroupCreator func(cfg *lncfg.Prometheus) (MetricGroup, error) + +// Global registry for all metric groups. +var ( + // metricGroups is a global variable of all registered metrics + // protected by the mutex below. All new MetricGroups should add + // themselves to this map within the init() method of their file. + metricGroups = make(map[string]MetricGroupCreator) + + // activeGroups is a global map of all active metric groups. This can + // be used by some of the "static' package level methods to look up the + // target metric group to export observations. + activeMetrics = make(map[string]MetricGroup) + + metricsMtx sync.Mutex +) + +// MetricGroup is the primary interface for metric groups. +type MetricGroup interface { + // Name returns the name of the metric group. + Name() string + + // RegisterMetrics registers all metrics within the group. + RegisterMetrics() error + + // ShouldRegister indicates whether this groups metrics should actually + // be registered. + ShouldRegister(cfg *lncfg.Prometheus) bool +} + +// RegisterMetricGroup adds a new metric group to the registry. +func RegisterMetricGroup(name string, creator MetricGroupCreator) { + metricsMtx.Lock() + defer metricsMtx.Unlock() + metricGroups[name] = creator +} + +// InitializeMetrics initializes and registers all active metric groups. +func InitializeMetrics(cfg *lncfg.Prometheus) error { + metricsMtx.Lock() + defer metricsMtx.Unlock() + + for name, creator := range metricGroups { + // We'll pass the configuration struct to permit conditional + // metric registration in the future. + group, err := creator(cfg) + if err != nil { + return err + } + + // Check whether this metric group should be registered. + if !group.ShouldRegister(cfg) { + continue + } + + if err := group.RegisterMetrics(); err != nil { + return err + } + + activeMetrics[name] = group + } + + return nil +} diff --git a/monitoring/monitoring_off.go b/monitoring/monitoring_off.go index cb8ca4543..95e3f9824 100644 --- a/monitoring/monitoring_off.go +++ b/monitoring/monitoring_off.go @@ -23,3 +23,13 @@ func ExportPrometheusMetrics(_ *grpc.Server, _ lncfg.Prometheus) error { return fmt.Errorf("lnd must be built with the monitoring tag to " + "enable exporting Prometheus metrics") } + +// IncrementPaymentCount increments a counter tracking the number of payments +// made by lnd when monitoring is enabled. This method no-ops as monitoring is +// disabled. +func IncrementPaymentCount() {} + +// IncrementHTLCAttemptCount increments a counter tracking the number of HTLC +// attempts made by lnd when monitoring is enabled. This method no-ops as +// monitoring is disabled. +func IncrementHTLCAttemptCount() {} diff --git a/monitoring/monitoring_on.go b/monitoring/monitoring_on.go index aaf2dfd97..a2cad40e6 100644 --- a/monitoring/monitoring_on.go +++ b/monitoring/monitoring_on.go @@ -27,12 +27,14 @@ func GetPromInterceptors() ([]grpc.UnaryServerInterceptor, []grpc.StreamServerIn return unaryInterceptors, streamInterceptors } -// ExportPrometheusMetrics sets server options, registers gRPC metrics and -// launches the Prometheus exporter on the specified address. +// ExportPrometheusMetrics sets server options, registers gRPC metrics, +// and launches the Prometheus exporter on the specified address. func ExportPrometheusMetrics(grpcServer *grpc.Server, cfg lncfg.Prometheus) error { + var metricErr error started.Do(func() { log.Infof("Prometheus exporter started on %v/metrics", cfg.Listen) + // Register gRPC Prometheus interceptors. grpc_prometheus.Register(grpcServer) // Enable the histograms which can allow plotting latency @@ -43,11 +45,21 @@ func ExportPrometheusMetrics(grpcServer *grpc.Server, cfg lncfg.Prometheus) erro grpc_prometheus.EnableHandlingTimeHistogram() } + // Initialize additional metric groups (e.g., payment tracking). + err := InitializeMetrics(&cfg) + if err != nil { + log.Warnf("Failed to initialize additional metrics: %v", + err) + + metricErr = err + } + + // Start the Prometheus HTTP handler. http.Handle("/metrics", promhttp.Handler()) go func() { http.ListenAndServe(cfg.Listen, nil) }() }) - return nil + return metricErr } diff --git a/monitoring/payments_metrics.go b/monitoring/payments_metrics.go new file mode 100644 index 000000000..a7d502316 --- /dev/null +++ b/monitoring/payments_metrics.go @@ -0,0 +1,79 @@ +//go:build monitoring +// +build monitoring + +package monitoring + +import ( + "github.com/lightningnetwork/lnd/lncfg" + "github.com/prometheus/client_golang/prometheus" +) + +const paymentsMetricGroupName string = "payments" + +// paymentMetrics tracks payment-related Prometheus metrics. +type paymentMetrics struct { + totalPayments prometheus.Counter + totalHTLCAttempts prometheus.Counter +} + +// NewPaymentMetrics creates a new instance of payment metrics. +func NewPaymentMetrics(cfg *lncfg.Prometheus) (MetricGroup, error) { + return &paymentMetrics{ + totalPayments: prometheus.NewCounter(prometheus.CounterOpts{ + Name: "lnd_total_payments", + Help: "Total number of payments initiated", + }), + totalHTLCAttempts: prometheus.NewCounter(prometheus.CounterOpts{ + Name: "lnd_total_htlc_attempts", + Help: "Total number of HTLC attempts", + }), + }, nil +} + +// Name returns the metric group name. +func (p *paymentMetrics) Name() string { + return paymentsMetricGroupName +} + +// RegisterMetrics registers payment-related Prometheus metrics. +func (p *paymentMetrics) RegisterMetrics() error { + err := prometheus.Register(p.totalPayments) + if err != nil { + return err + } + return prometheus.Register(p.totalHTLCAttempts) +} + +// ShouldRegister indicates whether the payments related metrics should be +// registered with prometheus. +func (p *paymentMetrics) ShouldRegister(cfg *lncfg.Prometheus) bool { + // TODO: Can condition this on application config. + return true +} + +// IncrementPaymentCount increments the counter tracking the number of payments +// made by lnd when monitoring is enabled and the metric is configured +func IncrementPaymentCount() { + group, ok := activeMetrics[paymentsMetricGroupName].(*paymentMetrics) + if !ok { + return + } + + group.totalPayments.Inc() +} + +// IncrementHTLCAttemptCount increments the counter tracking the number of HTLC +// attempts made by lnd when monitoring is enabled and the metric is configured. +func IncrementHTLCAttemptCount() { + group, ok := activeMetrics[paymentsMetricGroupName].(*paymentMetrics) + if !ok { + return + } + + group.totalHTLCAttempts.Inc() +} + +// Register the payments metric group. +func init() { + RegisterMetricGroup(paymentsMetricGroupName, NewPaymentMetrics) +}