-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: refactor into cmd package * feat: implement node height * fix: docker build * fix: apply feedback - Returning errors instead of fatalF - Using CMDConfig - Removed usage of metric.type - Removed mutex - Added todos - Using limited reader - Parsing uint64 * chore: rename package to sl-exporter * fix: use is.ReadFile
- Loading branch information
Showing
11 changed files
with
307 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ | |
!go.mod | ||
!go.sum | ||
!*.go | ||
!cmd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package cmd | ||
|
||
import ( | ||
"flag" | ||
log "github.com/sirupsen/logrus" | ||
"gopkg.in/yaml.v2" | ||
"os" | ||
"time" | ||
) | ||
|
||
type Config struct { | ||
Metrics map[string]Metric `yaml:"metrics"` | ||
} | ||
|
||
type Metric struct { | ||
Description string `yaml:"description"` | ||
Labels []string `yaml:"labels"` | ||
Samples []Sample `yaml:"samples,omitempty"` | ||
Chains []ChainWithRPCs `yaml:"chains,omitempty"` | ||
} | ||
|
||
type ChainWithRPCs struct { | ||
Name string `yaml:"name"` | ||
RPCs []string `yaml:"rpcs"` | ||
} | ||
|
||
type Sample struct { | ||
Labels []string `yaml:"labels"` | ||
Value float64 `yaml:"value"` | ||
} | ||
|
||
type CMDConfig struct { | ||
ConfigFile string | ||
Bind string | ||
Interval time.Duration | ||
LogLevel string | ||
} | ||
|
||
var cmdConfig CMDConfig | ||
|
||
func init() { | ||
flag.StringVar(&cmdConfig.ConfigFile, "config", "config.yaml", "configuration file") | ||
flag.StringVar(&cmdConfig.Bind, "bind", "localhost:9100", "bind") | ||
flag.DurationVar(&cmdConfig.Interval, "interval", 15*time.Second, "duration interval") | ||
flag.StringVar(&cmdConfig.LogLevel, "loglevel", "info", "Log level (debug, info, warn, error)") | ||
flag.Parse() | ||
|
||
level, err := log.ParseLevel(cmdConfig.LogLevel) | ||
if err != nil { | ||
log.Fatalf("Invalid log level: %v", err) | ||
} | ||
log.SetLevel(level) | ||
|
||
log.Debugf("Config File: %s\n", cmdConfig.ConfigFile) | ||
log.Debugf("Interval: %s\n", cmdConfig.Interval) | ||
log.Debugf("Bind: %s\n", cmdConfig.Bind) | ||
} | ||
|
||
// readConfig reads config.yaml from disk | ||
func readConfig(filename string) (*Config, error) { | ||
data, err := os.ReadFile(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var config Config | ||
if err := yaml.Unmarshal(data, &config); err != nil { | ||
return nil, err | ||
} | ||
return &config, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"github.com/prometheus/client_golang/prometheus" | ||
log "github.com/sirupsen/logrus" | ||
"net/url" | ||
) | ||
|
||
// registerMetrics iterates config metrics and passes them to relevant handler | ||
func registerMetrics(config *Config, registry *prometheus.Registry) error { | ||
for metricName, metric := range config.Metrics { | ||
var collector prometheus.Collector | ||
|
||
if len(metric.Samples) > 0 { | ||
collector = sampleMetrics(metric, metricName) | ||
} else if len(metric.Chains) > 0 { | ||
collector = rpcMetrics(metric, metricName) | ||
} else { | ||
return fmt.Errorf("unsupported metric: %s", metricName) | ||
} | ||
|
||
if err := registry.Register(collector); err != nil { | ||
return fmt.Errorf("error registering %s: %v", metricName, err) | ||
} | ||
log.Infof("Register collector - %s", metricName) | ||
} | ||
return nil | ||
} | ||
|
||
// sampleMetrics handles static gauge samples | ||
func sampleMetrics(metric Metric, metricName string) *prometheus.GaugeVec { | ||
gaugeVec := prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{Name: prometheus.BuildFQName(namespace, subsystem, metricName), Help: metric.Description}, | ||
metric.Labels, | ||
) | ||
for _, sample := range metric.Samples { | ||
gaugeVec.WithLabelValues(sample.Labels...).Set(sample.Value) | ||
} | ||
return gaugeVec | ||
} | ||
|
||
// rpcMetrics handles dynamic gauge metrics for public_rpc_node_height | ||
func rpcMetrics(metric Metric, metricName string) *prometheus.GaugeVec { | ||
gaugeVec := prometheus.NewGaugeVec( | ||
prometheus.GaugeOpts{Name: prometheus.BuildFQName(namespace, subsystem, metricName), Help: metric.Description}, | ||
metric.Labels, | ||
) | ||
|
||
for _, chain := range metric.Chains { | ||
for _, rpc := range chain.RPCs { | ||
// Fetch and set the metric value for the rpc node | ||
value, err := fetchRPCNodeHeight(rpc) | ||
if err != nil { | ||
log.Errorf("Error fetching height for rpc %s: %v", rpc, err) | ||
continue | ||
} | ||
gaugeVec.WithLabelValues(chain.Name, urlHost(rpc)).Set(float64(value)) | ||
} | ||
} | ||
return gaugeVec | ||
} | ||
|
||
// urlHost extracts host from given rpc url | ||
func urlHost(rpc string) string { | ||
parsedURL, err := url.Parse(rpc) | ||
if err != nil { | ||
log.Warnf("Error parsing URL: %v\n", err) | ||
return rpc | ||
} | ||
return parsedURL.Hostname() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/prometheus/client_golang/prometheus" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
"github.com/prometheus/common/version" | ||
"net/http" | ||
) | ||
|
||
const ( | ||
collector = "sl_exporter" | ||
// Todo (nour): should we add namespace and subsystem | ||
namespace = "" | ||
subsystem = "" | ||
) | ||
|
||
var ( | ||
metricsRegistry *prometheus.Registry | ||
) | ||
|
||
func updateRegistry(config *Config) (*prometheus.Registry, error) { | ||
// Create sampleMetrics new registry for the updated metrics | ||
newRegistry := prometheus.NewRegistry() | ||
|
||
// Register build_info metric | ||
if err := newRegistry.Register(version.NewCollector(collector)); err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := registerMetrics(config, newRegistry); err != nil { | ||
return nil, err | ||
} | ||
|
||
return newRegistry, nil | ||
} | ||
|
||
func metricsHandler() http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
handler := promhttp.HandlerFor(metricsRegistry, promhttp.HandlerOpts{}) | ||
handler.ServeHTTP(w, r) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/prometheus/client_golang/prometheus" | ||
log "github.com/sirupsen/logrus" | ||
"net/http" | ||
"time" | ||
) | ||
|
||
func Execute() { | ||
// Initialize metricsRegistry | ||
metricsRegistry = prometheus.NewRegistry() | ||
|
||
// Start background goroutine to re-evaluate the config and update the registry at `interval` | ||
go func() { | ||
ticker := time.NewTicker(cmdConfig.Interval) | ||
defer ticker.Stop() | ||
|
||
for { | ||
select { | ||
case <-ticker.C: | ||
config, err := readConfig(cmdConfig.ConfigFile) | ||
if err != nil { | ||
log.Errorf("Error reading config file %s: %v", cmdConfig.ConfigFile, err) | ||
continue | ||
} | ||
if updatedRegistry, err := updateRegistry(config); err != nil { | ||
log.Errorf("error updating registery: %v", err) | ||
} else { | ||
metricsRegistry = updatedRegistry | ||
} | ||
} | ||
} | ||
}() | ||
|
||
http.Handle("/metrics", metricsHandler()) | ||
log.Infof("Starting Prometheus metrics server - %s", cmdConfig.Bind) | ||
if err := http.ListenAndServe(cmdConfig.Bind, nil); err != nil { | ||
log.Fatalf("Failed to start http server: %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
log "github.com/sirupsen/logrus" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
) | ||
|
||
type SyncInfo struct { | ||
LatestBlockHeight string `json:"latest_block_height"` | ||
} | ||
|
||
type Result struct { | ||
SyncInfo SyncInfo `json:"sync_info"` | ||
} | ||
|
||
type Response struct { | ||
Result Result `json:"result"` | ||
} | ||
|
||
// fetchRPCNodeHeight fetches node height from the rpcURL | ||
func fetchRPCNodeHeight(rpcURL string) (uint64, error) { | ||
// Make a GET request to the REST endpoint | ||
resp, err := http.Get(rpcURL + "/status") | ||
if err != nil { | ||
return 0, fmt.Errorf("error making GET request: %v", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
// Read the response body | ||
limitedReader := &io.LimitedReader{R: resp.Body, N: 4 * 1024} | ||
body, err := io.ReadAll(limitedReader) | ||
if err != nil { | ||
return 0, fmt.Errorf("error reading response body: %v", err) | ||
} | ||
|
||
// Unmarshal JSON data into Response struct | ||
var response Response | ||
err = json.Unmarshal(body, &response) | ||
if err != nil { | ||
return 0, fmt.Errorf("error unmarshaling JSON data: %v", err) | ||
} | ||
|
||
// Extract the latest_block_height as a number | ||
latestBlockHeightStr := response.Result.SyncInfo.LatestBlockHeight | ||
latestBlockHeight, err := strconv.ParseUint(latestBlockHeightStr, 10, 64) | ||
if err != nil { | ||
return 0, fmt.Errorf("error converting latest_block_height to a number: %v", err) | ||
} | ||
|
||
log.Debugf("Latest block height [%s]: %d\n", rpcURL, latestBlockHeight) | ||
|
||
return latestBlockHeight, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
module sl-exporter | ||
module github.com/strangelove-ventures/sl-exporter | ||
|
||
go 1.20 | ||
|
||
|
Oops, something went wrong.