Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Manifest Node Exporter

[![CircleCI](https://dl.circleci.com/status-badge/img/gh/liftedinit/manifest-node-exporter/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/liftedinit/manifest-node-exporter/tree/main)

A Prometheus exporter for collecting metrics from Manifest blockchain nodes.

## Installation

Download the latest release from the [releases page](https://github.com/liftedinit/manifest-node-exporter/releases)

## Quick Start

```bash
manifest-node-exporter serve grpc-address [flags]
```

where `grpc-address` is the address of the `manifest-ledger` gRPC server.

The exporter will start a Prometheus metrics server on `0.0.0.0:2112` by default.

## Global Flags

| Flag | Description |
|---------------------|-------------------------------------------------------------------------------------------|
| `-h`, `--help` | help for manifest-node-exporter |
| `-l`, `--logLevel` | Set the log level. Available levels: `debug`, `info`, `warn`, `error`. Default is `info`. |

## Serve Flags

| Flag | Description |
|---------------------|-------------------------------------------------------------------------------------------|
| `-h`, `--help` | help for serve |
| `--insecure` | Skip TLS verification for gRPC connection. Default is `false`. |
| `--listen-address` | Address to listen on for Prometheus metrics. Default is `0.0.0.0:2112`. |

## Metrics

| Metric Name | Description |
|-------------------------------------|---------------------------------------------------------------------------|
| `manifest_tokenomics_denom_info` | Information about the token denominations (symbol, denom, name, display). |
| `manifest_tokenomics_total_supply` | Total supply for a given token. |
| `manifest_tokenomics_token_count` | The number of different tokens hosted on the Manifest blockchain. |
| `manifest_tokenomics_denom_grpc_up` | Whether the gRPC query for the token denomination was successful. |
| `manifest_tokenomics_count_grpc_up` | Whether the gRPC query for the token count was successful. |
3 changes: 1 addition & 2 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ func validateGrpcAddress(grpcAddr string) error {

// handleInterrupt handles interrupt signals for graceful shutdown.
func handleInterrupt(cancel context.CancelFunc) {
// Handle interrupt signals for graceful shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
Expand All @@ -126,7 +125,7 @@ func handleInterrupt(cancel context.CancelFunc) {
}

func init() {
serveCmd.Flags().String("listen-address", ":2112", "Address to listen on")
serveCmd.Flags().String("listen-address", "0.0.0.0:2112", "Address to listen on")
serveCmd.Flags().Bool("insecure", false, "Skip TLS certificate verification (INSECURE)")
serveCmd.Flags().Uint("max-concurrency", 100, "Maximum request concurrency (advanced)")
serveCmd.Flags().Uint("max-retries", 3, "Maximum number of retries for failed requests")
Expand Down
8 changes: 0 additions & 8 deletions pkg/collectors/grpc/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ func RegisterCollectors(grpcClient *pkg.GRPCClient) ([]prometheus.Collector, err
return nil, fmt.Errorf("cannot register collectors with a nil or unconnected gRPC client")
}

// Use the DefaultGrpcRegistry defined in common.go to create collectors
collectors, err := DefaultGrpcRegistry.CreateGrpcCollectors(grpcClient)
if err != nil {
// Error already logged by CreateGrpcCollectors if a factory failed
Expand All @@ -79,17 +78,10 @@ func RegisterCollectors(grpcClient *pkg.GRPCClient) ([]prometheus.Collector, err
if err := prometheus.DefaultRegisterer.Register(collector); err != nil {
var alreadyRegistered prometheus.AlreadyRegisteredError
if errors.As(err, &alreadyRegistered) {
// This is often benign during development or restarts, log as Info or Debug
slog.Debug("Collector already registered with Prometheus, skipping registration.", "collector_type", collectorType)
// We might still want to include it in the returned list if it was successfully *created*
// Depending on desired behavior, you could fetch the existing collector:
// registeredCollectors = append(registeredCollectors, alreadyRegistered.ExistingCollector)
skippedCount++
} else {
// This is a more serious registration error
slog.Error("Failed to register collector with Prometheus", "collector_type", collectorType, "error", err)
// Decide if you want to fail entirely or just skip this collector
// Failing fast is often safer.
return registeredCollectors, fmt.Errorf("failed to register collector type %s: %w", collectorType, err)
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions pkg/collectors/grpc/token_count.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ func NewTokenCountCollector(client *pkg.GRPCClient) *TokenCountCollector {
grpcClient: client,
initialError: initialError,
tokenCountDesc: prometheus.NewDesc(
prometheus.BuildFQName("manifest", "tokenomics", "token_number"),
prometheus.BuildFQName("manifest", "tokenomics", "token_count"),
"Total number of denominations, including native, IBC and factory tokens.",
[]string{},
prometheus.Labels{"source": "grpc"},
),
upDesc: prometheus.NewDesc(
prometheus.BuildFQName("manifest", "tokenomics", "supply_grpc_up"),
prometheus.BuildFQName("manifest", "tokenomics", "count_grpc_up"),
"Whether the gRPC query was successful.",
nil,
prometheus.Labels{"source": "grpc", "queries": "DenomsMetadata"},
Expand Down
19 changes: 4 additions & 15 deletions pkg/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,11 @@ import (
)

type ServeConfig struct {
ListenAddress string `mapstructure:"listen_address"`
Insecure bool `mapstructure:"insecure"`
MaxConcurrency uint `mapstructure:"max_concurrency"`
MaxRetries uint `mapstructure:"max_retries"`
ListenAddress string `mapstructure:"listen_address"`
Insecure bool `mapstructure:"insecure"`
}

func (c ServeConfig) Validate() error {
if c.MaxConcurrency == 0 {
return fmt.Errorf("max-concurrency must be greater than 0")
}
if c.MaxRetries == 0 {
return fmt.Errorf("max-retries must be greater than 0")
}

host, port, err := net.SplitHostPort(c.ListenAddress)
if err != nil {
return fmt.Errorf("invalid prometheus-addr format, expected host:port: %w", err)
Expand All @@ -40,9 +31,7 @@ func (c ServeConfig) Validate() error {

func LoadServeConfig() ServeConfig {
return ServeConfig{
ListenAddress: viper.GetString("listen-address"),
Insecure: viper.GetBool("insecure"),
MaxConcurrency: viper.GetUint("max-concurrency"),
MaxRetries: viper.GetUint("max-retries"),
ListenAddress: viper.GetString("listen-address"),
Insecure: viper.GetBool("insecure"),
}
}
7 changes: 1 addition & 6 deletions pkg/metrics_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,10 @@ func (s *MetricsServer) Start() <-chan error {
slog.Info("Starting Prometheus metrics server...", "address", s.listenAddr)

go func() {
// ListenAndServe blocks until the server stops.
err := s.httpServer.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
// Only send non-nil errors that aren't the expected ErrServerClosed
errChan <- fmt.Errorf("prometheus metrics server failed: %w", err)
}
// Close the channel only if an error occurred, otherwise leave it open.
// The caller uses context cancellation for shutdown signal, not channel closing.
// close(errChan) // Don't close here on normal shutdown
}()

// Give a brief moment to allow ListenAndServe to start or fail early
Expand All @@ -77,5 +72,5 @@ func (s *MetricsServer) Shutdown(ctx context.Context) error {
} else {
slog.Error("Error during metrics server shutdown.", "error", err)
}
return err // Return the error for the caller
return err
}
2 changes: 1 addition & 1 deletion tests/e2e_serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestE2EServe(t *testing.T) {
}{
{"manifest_tokenomics_denom_info", `{denom="udummy",display="DUMMY_DISPLAY",name="Dummy_Name",source="grpc",symbol="Dummy_Symbol"}`},
{"manifest_tokenomics_total_supply", `{denom="udummy",source="grpc"} 10`},
{"manifest_tokenomics_token_number", `{source="grpc"} 102`},
{"manifest_tokenomics_token_count", `{source="grpc"} 102`},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
Expand Down