From f5fe721ad50b62133310c442bca3a2f46d3f009f Mon Sep 17 00:00:00 2001 From: Pablo Castellano Date: Sun, 2 Jun 2024 13:55:50 +0200 Subject: [PATCH] Swarm batch prometheus exporter --- .gitignore | 1 + Makefile | 2 + README.md | 23 ++++++++++ go.mod | 14 ++++++ go.sum | 16 +++++++ main.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 182 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32d03f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +swarm-exporter diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6945e37 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +build: + go build -o swarm-exporter diff --git a/README.md b/README.md new file mode 100644 index 0000000..c6fa6cd --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Swarm batch prometheus exporter + +This is a simple prometheus exporter to expose Stamp batch metrics for Ethereum Swarm. + +## Usage + +1. Compile + +``` +make build +``` + +2. Run + +``` +./swarm-exporter +``` + +3. Scrape + +``` +curl http://localhost:1640 +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9dbfe3f --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module swarm-batch-exporter + +go 1.21.4 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.17.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a1f2a68 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/main.go b/main.go new file mode 100644 index 0000000..bd29794 --- /dev/null +++ b/main.go @@ -0,0 +1,126 @@ +package main + +import ( + "encoding/json" + "net/http" + "log" + "math" + "time" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +type APIResponse struct { + Stamps []Stamp `json:"stamps"` +} + +type Stamp struct { + BatchID string `json:"batchID"` + Utilization int `json:"utilization"` + Usable bool `json:"usable"` + Label string `json:"label"` + Depth int `json:"depth"` + Amount string `json:"amount"` + BucketDepth int `json:"bucketDepth"` + BlockNumber int `json:"blockNumber"` + ImmutableFlag bool `json:"immutableFlag"` + Exists bool `json:"exists"` + BatchTTL int `json:"batchTTL"` +} + +// Declare the metrics +var ( + utilizationMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_utilization", + Help: "Stamp batch utilization.", + }, []string{"batchID", "label"}) + ttlMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_ttl", + Help: "Stamp batch TTL.", + }, []string{"batchID", "label"}) + depthMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_depth", + Help: "Stamp batch depth.", + }, []string{"batchID", "label"}) + amountMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_amount", + Help: "Stamp batch amount.", + }, []string{"batchID", "label"}) + capacityMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_capacity_bytes", + Help: "Stamp batch total capacity in bytes.", + }, []string{"batchID", "label"}) + availabilityMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_available_bytes", + Help: "Stamp batch available capacity in bytes.", + }, []string{"batchID", "label"}) + usageMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: "swarm_stamp_batch_usage_percentage", + Help: "Stamp batch usage percentage.", + }, []string{"batchID", "label"}) +) + +func init() { + prometheus.MustRegister(utilizationMetric) + prometheus.MustRegister(ttlMetric) + prometheus.MustRegister(depthMetric) + prometheus.MustRegister(amountMetric) + prometheus.MustRegister(capacityMetric) + prometheus.MustRegister(availabilityMetric) + prometheus.MustRegister(usageMetric) +} + +func GetStampMaximumCapacityBytes(depth int) float64 { + return math.Pow(2, float64(depth)) * 4096 +} + +func GetStampUsage(utilization, depth, bucketDepth int) float64 { + return float64(utilization) / math.Pow(2, float64(depth-bucketDepth)) + +} + +func fetchMetrics() { + resp, err := http.Get("http://localhost:1633/stamps") + if err != nil { + log.Println("Error fetching data:", err) + return + } + defer resp.Body.Close() + + var apiResponse APIResponse + if err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil { + log.Println("Error decoding JSON data:", err) + return + } + + // Update Prometheus metrics + for _, stamp := range apiResponse.Stamps { + utilizationMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(float64(stamp.Utilization)) + ttlMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(float64(stamp.BatchTTL)) + depthMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(float64(stamp.Depth)) + //amountMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(float64(stamp.Amount)) + + capacity := GetStampMaximumCapacityBytes(stamp.Depth) + capacityMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(capacity) + + stampUsage := GetStampUsage(stamp.Utilization, stamp.Depth, stamp.BucketDepth) + usageMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(stampUsage) + + available := int(stampUsage * capacity) + availabilityMetric.With(prometheus.Labels{"batchID": stamp.BatchID, "label": stamp.Label}).Set(float64(available)) + } +} + +func main() { + // Setup fetchMetrics to run periodically + go func() { + for { + fetchMetrics() + time.Sleep(5 * time.Minute) // Adjust fetch interval as needed + } + }() + + // Setup HTTP server + http.Handle("/metrics", promhttp.Handler()) + log.Fatal(http.ListenAndServe(":1640", nil)) +}