Skip to content

Commit

Permalink
Merge pull request ethereum-optimism#1210 from ethereum-optimism/bwil…
Browse files Browse the repository at this point in the history
…son/op_exporter

Add op_exporter for sequencer metrics and health endpoint
  • Loading branch information
snario authored Jul 7, 2021
2 parents e7e802f + aa06ba6 commit dc27dea
Show file tree
Hide file tree
Showing 11 changed files with 863 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ jobs:
push: true
tags: ethereumoptimism/l2geth:${{ needs.release.outputs.l2geth }}

- name: Publish op_exporter
uses: docker/build-push-action@v2
with:
context: .
file: ./ops/docker/Dockerfile.op_exporter
push: true
tags: ethereumoptimism/op_exporter:${{ needs.release.outputs.l2geth }}

- name: Publish rpc-proxy
uses: docker/build-push-action@v2
with:
Expand Down
2 changes: 2 additions & 0 deletions go/op_exporter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
op_exporter
.env
21 changes: 21 additions & 0 deletions go/op_exporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SHELL := /bin/bash

VERSION := `git describe --abbrev=0`
GITCOMMIT := `git rev-parse HEAD`
BUILDDATE := `date +%Y-%m-%d`
BUILDUSER := `whoami`

LDFLAGSSTRING :=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.Version=$(VERSION)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.BuildDate=$(BUILDDATE)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.BuildUser=$(BUILDUSER)

LDFLAGS :=-ldflags "$(LDFLAGSSTRING)"

.PHONY: all build

all: build

# Build binary
build:
CGO_ENABLED=0 go build $(LDFLAGS)
27 changes: 27 additions & 0 deletions go/op_exporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# op_exporter

A prometheus exporter to collect information from an Optimistic Ethereum node and serve metrics for collection

## Usage

```
make build && ./op_exporter --rpc.provider="https://kovan-sequencer.optimism.io" --label.network="kovan"
```

## Health endpoint `/health`

Returns json describing the health of the sequencer based on the time since a block height update.

```
$ curl http://localhost:9100/health
{ "healthy": "false" }
```

## Metrics endpoint `/metrics`

```
# HELP op_gasPrice Gas price.
# TYPE op_gasPrice gauge
op_gasPrice{layer="layer1",network="kovan"} 6.9e+09
op_gasPrice{layer="layer2",network="kovan"} 1
```
34 changes: 34 additions & 0 deletions go/op_exporter/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"github.com/prometheus/client_golang/prometheus"
)

//Define the metrics we wish to expose
var (
gasPrice = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "op_gasPrice",
Help: "Gas price."},
[]string{"network", "layer"},
)
blockNumber = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "op_blocknumber",
Help: "Current block number."},
[]string{"network", "layer"},
)
healthySequencer = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "op_healthy_sequencer",
Help: "Is the sequencer healthy?"},
[]string{"network"},
)
)

func init() {
//Register metrics with prometheus
prometheus.MustRegister(gasPrice)
prometheus.MustRegister(blockNumber)
prometheus.MustRegister(healthySequencer)
}
11 changes: 11 additions & 0 deletions go/op_exporter/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/ethereum-optimism/optimism/go/op_exporter

go 1.16

require (
github.com/ethereum/go-ethereum v1.10.4
github.com/prometheus/client_golang v1.4.0
github.com/sirupsen/logrus v1.4.2
github.com/ybbus/jsonrpc v2.1.2+incompatible
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
541 changes: 541 additions & 0 deletions go/op_exporter/go.sum

Large diffs are not rendered by default.

174 changes: 174 additions & 0 deletions go/op_exporter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package main

import (
"fmt"
"net/http"
"os"
"sync"
"time"

"github.com/ethereum-optimism/optimism/go/op_exporter/version"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"github.com/ybbus/jsonrpc"
"gopkg.in/alecthomas/kingpin.v2"
)

var (
listenAddress = kingpin.Flag(
"web.listen-address",
"Address on which to expose metrics and web interface.",
).Default(":9100").String()
rpcProvider = kingpin.Flag(
"rpc.provider",
"Address for RPC provider.",
).Default("http://127.0.0.1:8545").String()
networkLabel = kingpin.Flag(
"label.network",
"Label to apply to the metrics to identify the network.",
).Default("mainnet").String()
versionFlag = kingpin.Flag(
"version",
"Display binary version.",
).Default("False").Bool()
unhealthyTimePeriod = kingpin.Flag(
"wait.minutes",
"Number of minutes to wait for the next block before marking provider unhealthy.",
).Default("10").Int()
//unhealthyTimePeriod = time.Minute * 10
)

type healthCheck struct {
mu *sync.RWMutex
height uint64
healthy bool
updateTime time.Time
}

func healthHandler(health *healthCheck) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
health.mu.RLock()
defer health.mu.RUnlock()
w.Write([]byte(fmt.Sprintf(`{ "healthy": "%t" }`, health.healthy)))
}
}

func main() {
kingpin.HelpFlag.Short('h')
kingpin.Parse()
if *versionFlag {
fmt.Printf("(version=%s, gitcommit=%s)\n", version.Version, version.GitCommit)
fmt.Printf("(go=%s, user=%s, date=%s)\n", version.GoVersion, version.BuildUser, version.BuildDate)
os.Exit(0)
}
log.Infoln("exporter config", *listenAddress, *rpcProvider, *networkLabel)
log.Infoln("Starting op_exporter", version.Info())
log.Infoln("Build context", version.BuildContext())

health := healthCheck{
mu: new(sync.RWMutex),
height: 0,
healthy: false,
updateTime: time.Now(),
}
http.Handle("/metrics", promhttp.Handler())
http.Handle("/health", healthHandler(&health))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>OP Exporter</title></head>
<body>
<h1>OP Exporter</h1>
<p><a href="/metrics">Metrics</a></p>
<p><a href="/health">Health</a></p>
</body>
</html>`))
})
go getRollupGasPrices()
go getBlockNumber(&health)
log.Infoln("Listening on", *listenAddress)
if err := http.ListenAndServe(*listenAddress, nil); err != nil {
log.Fatal(err)
}

}

func getBlockNumber(health *healthCheck) {
rpcClient := jsonrpc.NewClientWithOpts(*rpcProvider, &jsonrpc.RPCClientOpts{})
var blockNumberResponse *string
for {
if err := rpcClient.CallFor(&blockNumberResponse, "eth_blockNumber"); err != nil {
health.mu.Lock()
health.healthy = false
health.mu.Unlock()
log.Warnln("Error calling eth_blockNumber, setting unhealthy", err)
} else {
log.Infoln("Got block number: ", *blockNumberResponse)
health.mu.Lock()
currentHeight, err := hexutil.DecodeUint64(*blockNumberResponse)
blockNumber.WithLabelValues(
*networkLabel, "layer2").Set(float64(currentHeight))
if err != nil {
log.Warnln("Error decoding block height", err)
continue
}
lastHeight := health.height
// If the currentHeight is the same as the lastHeight, check that
// the unhealthyTimePeriod has passed and update health.healthy
if currentHeight == lastHeight {
currentTime := time.Now()
lastTime := health.updateTime
log.Warnln(fmt.Sprintf("Heights are the same, %v, %v", currentTime, lastTime))
if lastTime.Add(time.Duration(*unhealthyTimePeriod) * time.Minute).Before(currentTime) {
health.healthy = false
log.Warnln("Heights are the same for the unhealthyTimePeriod, setting unhealthy")
}
} else {
log.Warnln("New block height detected, setting healthy")
health.height = currentHeight
health.updateTime = time.Now()
health.healthy = true
}
if health.healthy {
healthySequencer.WithLabelValues(
*networkLabel).Set(1)
} else {
healthySequencer.WithLabelValues(
*networkLabel).Set(0)
}

health.mu.Unlock()
}
time.Sleep(time.Duration(30) * time.Second)
}
}

func getRollupGasPrices() {
rpcClient := jsonrpc.NewClientWithOpts(*rpcProvider, &jsonrpc.RPCClientOpts{})
var rollupGasPrices *GetRollupGasPrices
for {
if err := rpcClient.CallFor(&rollupGasPrices, "rollup_gasPrices"); err != nil {
log.Warnln("Error calling rollup_gasPrices", err)
} else {
l1GasPriceString := rollupGasPrices.L1GasPrice
l1GasPrice, err := hexutil.DecodeUint64(l1GasPriceString)
if err != nil {
log.Warnln("Error converting gasPrice " + l1GasPriceString)
}
gasPrice.WithLabelValues(
*networkLabel, "layer1").Set(float64(l1GasPrice))
l2GasPriceString := rollupGasPrices.L2GasPrice
l2GasPrice, err := hexutil.DecodeUint64(l2GasPriceString)
if err != nil {
log.Warnln("Error converting gasPrice " + l2GasPriceString)
}
gasPrice.WithLabelValues(
*networkLabel, "layer2").Set(float64(l2GasPrice))
log.Infoln("Got L1 gas string: ", l1GasPriceString)
log.Infoln("Got L1 gas prices: ", l1GasPrice)
log.Infoln("Got L2 gas string: ", l2GasPriceString)
log.Infoln("Got L2 gas prices: ", l2GasPrice)
}
time.Sleep(time.Duration(30) * time.Second)
}
}
11 changes: 11 additions & 0 deletions go/op_exporter/rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

// GetRollupGasPrices returns the rpc `rollup_gasPrices` status
type GetRollupGasPrices struct {
L1GasPrice string `json:"l1GasPrice"`
L2GasPrice string `json:"l2GasPrice"`
}

type GetBlockNumber struct {
BlockNumber string `json:"result"`
}
22 changes: 22 additions & 0 deletions go/op_exporter/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package version

import (
"fmt"
"runtime"
)

var (
Version string
GitCommit string
BuildUser string
BuildDate string
GoVersion = runtime.Version()
)

func Info() string {
return fmt.Sprintf("(version=%s, gitcommit=%s)", Version, GitCommit)
}

func BuildContext() string {
return fmt.Sprintf("(go=%s, user=%s, date=%s)", GoVersion, BuildUser, BuildDate)
}
12 changes: 12 additions & 0 deletions ops/docker/Dockerfile.op_exporter
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM golang:1.16 as builder

ADD ./go/op_exporter /app/
WORKDIR /app/
RUN make build

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/op_exporter /usr/local/bin/
ENTRYPOINT ["op_exporter"]
CMD ["--help"]

0 comments on commit dc27dea

Please sign in to comment.