skeleton, poll, heartbeat, configs, healthz, dockerfile
This commit is contained in:
commit
3718a7cbe9
3
op-ufm/op-ufm/.gitignore
vendored
Normal file
3
op-ufm/op-ufm/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bin
|
||||
|
||||
config.toml
|
30
op-ufm/op-ufm/Dockerfile
Normal file
30
op-ufm/op-ufm/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
FROM golang:1.20.4-alpine3.18 as builder
|
||||
|
||||
ARG GITCOMMIT=docker
|
||||
ARG GITDATE=docker
|
||||
ARG GITVERSION=docker
|
||||
|
||||
RUN apk add make jq git gcc musl-dev linux-headers
|
||||
|
||||
COPY ./ufm /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN make ufm
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
COPY ./ufm/entrypoint.sh /bin/entrypoint.sh
|
||||
|
||||
RUN apk update && \
|
||||
apk add ca-certificates && \
|
||||
chmod +x /bin/entrypoint.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
VOLUME /etc/ufm
|
||||
|
||||
COPY --from=builder /app/bin/ufm /bin/ufm
|
||||
|
||||
ENTRYPOINT ["/bin/entrypoint.sh"]
|
||||
CMD ["/bin/ufm", "/etc/ufm/config.toml"]
|
23
op-ufm/op-ufm/README.md
Normal file
23
op-ufm/op-ufm/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# OP User Facing Monitoring
|
||||
|
||||
This project simulates a synthetic user interacting with a OP Stack chain.
|
||||
|
||||
It is intended to be used as a tool for monitoring
|
||||
the health of the network by measuring end-to-end transaction latency.
|
||||
|
||||
|
||||
## Metrics
|
||||
|
||||
* Round-trip duration time to get transaction receipt (from creation timestamp)
|
||||
|
||||
* First-seen duration time (from creation timestamp)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Run `make ufm` to build the binary. No additional dependencies are necessary.
|
||||
|
||||
Copy `example.config.toml` to `config.toml` and edit the file to configure the service.
|
||||
|
||||
Start the service with `ufm config.toml`.
|
||||
|
80
op-ufm/op-ufm/cmd/main.go
Normal file
80
op-ufm/op-ufm/cmd/main.go
Normal file
@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"op-ufm/pkg/config"
|
||||
"op-ufm/pkg/service"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
var (
|
||||
GitVersion = ""
|
||||
GitCommit = ""
|
||||
GitDate = ""
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Root().SetHandler(
|
||||
log.LvlFilterHandler(
|
||||
log.LvlInfo,
|
||||
log.StreamHandler(os.Stdout, log.JSONFormat()),
|
||||
),
|
||||
)
|
||||
|
||||
log.Info("initializing", "version", GitVersion, "commit", GitCommit, "date", GitDate)
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
log.Crit("must specify a config file on the command line")
|
||||
}
|
||||
cfg := initConfig(os.Args[1])
|
||||
|
||||
ctx := context.Background()
|
||||
svc := service.New(cfg)
|
||||
svc.Start(ctx)
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
||||
recvSig := <-sig
|
||||
log.Info("caught signal, shutting down", "signal", recvSig)
|
||||
|
||||
svc.Shutdown()
|
||||
}
|
||||
|
||||
func initConfig(cfgFile string) *config.Config {
|
||||
cfg, err := config.New(cfgFile)
|
||||
if err != nil {
|
||||
log.Crit("error reading config file", "file", cfgFile, "err", err)
|
||||
}
|
||||
|
||||
// update log level from config
|
||||
logLevel, err := log.LvlFromString(cfg.LogLevel)
|
||||
if err != nil {
|
||||
logLevel = log.LvlInfo
|
||||
if cfg.LogLevel != "" {
|
||||
log.Warn("invalid server.log_level set: " + cfg.LogLevel)
|
||||
}
|
||||
}
|
||||
log.Root().SetHandler(
|
||||
log.LvlFilterHandler(
|
||||
logLevel,
|
||||
log.StreamHandler(os.Stdout, log.JSONFormat()),
|
||||
),
|
||||
)
|
||||
|
||||
// readable parsed config
|
||||
jsonCfg, _ := json.MarshalIndent(cfg, "", " ")
|
||||
fmt.Printf("%s", string(jsonCfg))
|
||||
|
||||
err = cfg.Validate()
|
||||
if err != nil {
|
||||
log.Crit("invalid config", "err", err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
6
op-ufm/op-ufm/entrypoint.sh
Normal file
6
op-ufm/op-ufm/entrypoint.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Updating CA certificates."
|
||||
update-ca-certificates
|
||||
echo "Running CMD."
|
||||
exec "$@"
|
60
op-ufm/op-ufm/example.config.toml
Normal file
60
op-ufm/op-ufm/example.config.toml
Normal file
@ -0,0 +1,60 @@
|
||||
# Log level.
|
||||
# Possible values: trace | debug | info | warn | error | crit
|
||||
# Default: debug
|
||||
log_level = "debug"
|
||||
|
||||
[signer_service]
|
||||
# URL to the signer service
|
||||
url = "http://localhost:1234"
|
||||
|
||||
[healthz]
|
||||
# Whether or not to enable healthz endpoint
|
||||
enabled = true
|
||||
# Host for the healthz endpoint to listen on
|
||||
host = "0.0.0.0"
|
||||
# Port for the above.
|
||||
port = 8080
|
||||
|
||||
[metrics]
|
||||
# Whether or not to enable Prometheus metrics
|
||||
enabled = true
|
||||
# Host for the Prometheus metrics endpoint to listen on.
|
||||
host = "0.0.0.0"
|
||||
# Port for the above.
|
||||
port = 9761
|
||||
|
||||
[wallets.default]
|
||||
# OP Stack Chain ID
|
||||
# see https://community.optimism.io/docs/useful-tools/networks/
|
||||
chain_id = 420
|
||||
# Signer method to use
|
||||
# Possible values: signer | static
|
||||
signer_method = "signer"
|
||||
# Address used to send transactions
|
||||
address="0x0123"
|
||||
# For static signer method, the private key to use
|
||||
# private_key=""
|
||||
|
||||
[providers.p1]
|
||||
# URL to the RPC provider
|
||||
url = "http://localhost:8551"
|
||||
# Read only providers are only used to check for transactions
|
||||
read_only = true
|
||||
# Interval to poll the provider for expected transactions
|
||||
read_interval = "1s"
|
||||
# Interval to submit new transactions to the provider
|
||||
send_interval = "5s"
|
||||
# Wallet to be used for sending transactions
|
||||
wallet = "default"
|
||||
|
||||
[providers.p2]
|
||||
# URL to the RPC provider
|
||||
url = "http://localhost:8552"
|
||||
# Read only providers are only used to check for transactions
|
||||
read_only = false
|
||||
# Interval to poll the provider for expected transactions
|
||||
read_interval = "2s"
|
||||
# Interval to submit new transactions to the provider
|
||||
send_interval = "3s"
|
||||
# Wallet to be used for sending transactions
|
||||
wallet = "default"
|
26
op-ufm/op-ufm/go.mod
Normal file
26
op-ufm/op-ufm/go.mod
Normal file
@ -0,0 +1,26 @@
|
||||
module op-ufm
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
github.com/ethereum/go-ethereum v1.12.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/rs/cors v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.39.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
43
op-ufm/op-ufm/go.sum
Normal file
43
op-ufm/op-ufm/go.sum
Normal file
@ -0,0 +1,43 @@
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
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/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0=
|
||||
github.com/ethereum/go-ethereum v1.12.0/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8=
|
||||
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI=
|
||||
github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
128
op-ufm/op-ufm/pkg/config/config.go
Normal file
128
op-ufm/op-ufm/pkg/config/config.go
Normal file
@ -0,0 +1,128 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
LogLevel string `toml:"log_level"`
|
||||
|
||||
Signer SignerServiceConfig `toml:"signer_service"`
|
||||
Metrics MetricsConfig `toml:"metrics"`
|
||||
Healthz HealthzConfig `toml:"healthz"`
|
||||
|
||||
Wallets map[string]*WalletConfig `toml:"wallets"`
|
||||
Providers map[string]*ProviderConfig `toml:"providers"`
|
||||
}
|
||||
|
||||
type SignerServiceConfig struct {
|
||||
URL string `toml:"url"`
|
||||
}
|
||||
|
||||
type MetricsConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
Host string `toml:"host"`
|
||||
Port int `toml:"port"`
|
||||
}
|
||||
|
||||
type HealthzConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
Host string `toml:"host"`
|
||||
Port int `toml:"port"`
|
||||
}
|
||||
|
||||
type WalletConfig struct {
|
||||
// default: 420 (Optimism Goerli)
|
||||
ChainID uint `toml:"chain_id"`
|
||||
|
||||
// signer | static, default: signer
|
||||
SignerMethod string `toml:"signer_method"`
|
||||
|
||||
// for static signing
|
||||
Address string `toml:"address"`
|
||||
PrivateKey string `toml:"private_key"`
|
||||
}
|
||||
|
||||
type ProviderConfig struct {
|
||||
URL string `toml:"url"`
|
||||
Wallet string `toml:"wallet"`
|
||||
ReadOnly bool `toml:"read_only"`
|
||||
ReadInterval TOMLDuration `toml:"read_interval"`
|
||||
SendInterval TOMLDuration `toml:"send_interval"`
|
||||
}
|
||||
|
||||
func New(file string) (*Config, error) {
|
||||
cfg := &Config{}
|
||||
if _, err := toml.DecodeFile(file, cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if c.Metrics.Enabled {
|
||||
if c.Metrics.Host == "" || c.Metrics.Port == 0 {
|
||||
return errors.New("metrics is enabled but host or port are missing")
|
||||
}
|
||||
}
|
||||
if c.Healthz.Enabled {
|
||||
if c.Healthz.Host == "" || c.Healthz.Port == 0 {
|
||||
return errors.New("healthz is enabled but host or port are missing")
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Wallets) == 0 {
|
||||
return errors.New("at least one wallet must be set")
|
||||
}
|
||||
|
||||
if len(c.Providers) == 0 {
|
||||
return errors.New("at least one provider must be set")
|
||||
}
|
||||
|
||||
for name, wallet := range c.Wallets {
|
||||
if wallet.ChainID == 0 {
|
||||
return errors.Errorf("wallet [%s] chain_id is missing", name)
|
||||
}
|
||||
if wallet.SignerMethod != "signer" && wallet.SignerMethod != "static" {
|
||||
return errors.Errorf("wallet [%s] signer_method is invalid", name)
|
||||
}
|
||||
if wallet.SignerMethod == "signer" {
|
||||
if c.Signer.URL == "" {
|
||||
return errors.New("signer url is missing")
|
||||
}
|
||||
}
|
||||
if wallet.SignerMethod == "static" {
|
||||
if wallet.PrivateKey == "" {
|
||||
return errors.Errorf("wallet [%s] private_key is missing", name)
|
||||
}
|
||||
}
|
||||
if wallet.Address == "" {
|
||||
return errors.Errorf("wallet [%s] address is missing", name)
|
||||
}
|
||||
}
|
||||
|
||||
for name, provider := range c.Providers {
|
||||
if provider.URL == "" {
|
||||
return errors.Errorf("provider [%s] url is missing", name)
|
||||
}
|
||||
if provider.ReadInterval == 0 {
|
||||
return errors.Errorf("provider [%s] read interval is missing", name)
|
||||
}
|
||||
if provider.SendInterval == 0 {
|
||||
return errors.Errorf("provider [%s] send interval is missing", name)
|
||||
}
|
||||
if provider.Wallet == "" {
|
||||
return errors.Errorf("provider [%s] wallet is missing", name)
|
||||
}
|
||||
if _, ok := c.Wallets[provider.Wallet]; !ok {
|
||||
return errors.Errorf("provider [%s] has an invalid wallet [%s]", name, provider.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
if c.LogLevel == "" {
|
||||
c.LogLevel = "debug"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
15
op-ufm/op-ufm/pkg/config/toml_duration.go
Normal file
15
op-ufm/op-ufm/pkg/config/toml_duration.go
Normal file
@ -0,0 +1,15 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type TOMLDuration time.Duration
|
||||
|
||||
func (t *TOMLDuration) UnmarshalText(b []byte) error {
|
||||
d, err := time.ParseDuration(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*t = TOMLDuration(d)
|
||||
return nil
|
||||
}
|
17
op-ufm/op-ufm/pkg/provider/handlers.go
Normal file
17
op-ufm/op-ufm/pkg/provider/handlers.go
Normal file
@ -0,0 +1,17 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// Heartbeat poll for expected transactions
|
||||
func (p *Provider) Heartbeat(ctx context.Context) {
|
||||
log.Debug("heartbeat", "provider", p.name)
|
||||
}
|
||||
|
||||
// Roundtrip send a new transaction to measure round trip latency
|
||||
func (p *Provider) Roundtrip(ctx context.Context) {
|
||||
log.Debug("roundtrip", "provider", p.name)
|
||||
}
|
57
op-ufm/op-ufm/pkg/provider/provider.go
Normal file
57
op-ufm/op-ufm/pkg/provider/provider.go
Normal file
@ -0,0 +1,57 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"op-ufm/pkg/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
name string
|
||||
config *config.ProviderConfig
|
||||
cancelFunc context.CancelFunc
|
||||
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(name string, cfg *config.ProviderConfig) *Provider {
|
||||
p := &Provider{
|
||||
name: name,
|
||||
config: cfg,
|
||||
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Provider) Start(ctx context.Context) {
|
||||
providerCtx, cancelFunc := context.WithCancel(ctx)
|
||||
p.cancelFunc = cancelFunc
|
||||
schedule(providerCtx, time.Duration(p.config.ReadInterval), p.Heartbeat)
|
||||
if !p.config.ReadOnly {
|
||||
schedule(providerCtx, time.Duration(p.config.SendInterval), p.Roundtrip)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Shutdown() {
|
||||
if p.cancelFunc != nil {
|
||||
p.cancelFunc()
|
||||
}
|
||||
}
|
||||
|
||||
func schedule(ctx context.Context, interval time.Duration, handler func(ctx context.Context)) {
|
||||
go func() {
|
||||
for {
|
||||
timer := time.NewTimer(interval)
|
||||
handler(ctx)
|
||||
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
47
op-ufm/op-ufm/pkg/service/healthz.go
Normal file
47
op-ufm/op-ufm/pkg/service/healthz.go
Normal file
@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
type Healthz struct {
|
||||
ctx context.Context
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func (h *Healthz) Start(ctx context.Context, host string, port int) {
|
||||
go func() {
|
||||
hdlr := mux.NewRouter()
|
||||
hdlr.HandleFunc("/healthz", h.Handle).Methods("GET")
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
c := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
})
|
||||
server := &http.Server{
|
||||
Handler: c.Handler(hdlr),
|
||||
Addr: addr,
|
||||
}
|
||||
h.server = server
|
||||
h.ctx = ctx
|
||||
err := h.server.ListenAndServe()
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Crit("error starting healthz server", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (h *Healthz) Shutdown() error {
|
||||
return h.server.Shutdown(h.ctx)
|
||||
}
|
||||
|
||||
func (h *Healthz) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("OK"))
|
||||
}
|
51
op-ufm/op-ufm/pkg/service/service.go
Normal file
51
op-ufm/op-ufm/pkg/service/service.go
Normal file
@ -0,0 +1,51 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"op-ufm/pkg/config"
|
||||
"op-ufm/pkg/provider"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Config *config.Config
|
||||
Healthz *Healthz
|
||||
Providers map[string]*provider.Provider
|
||||
}
|
||||
|
||||
func New(cfg *config.Config) *Service {
|
||||
s := &Service{
|
||||
Config: cfg,
|
||||
Healthz: &Healthz{},
|
||||
Providers: make(map[string]*provider.Provider, len(cfg.Providers)),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Service) Start(ctx context.Context) {
|
||||
log.Info("service starting")
|
||||
if s.Config.Healthz.Enabled {
|
||||
s.Healthz.Start(ctx, s.Config.Healthz.Host, s.Config.Healthz.Port)
|
||||
log.Info("healthz started")
|
||||
}
|
||||
for name, providerConfig := range s.Config.Providers {
|
||||
s.Providers[name] = provider.New(name, providerConfig)
|
||||
s.Providers[name].Start(ctx)
|
||||
log.Info("provider started", "provider", name)
|
||||
}
|
||||
log.Info("service started")
|
||||
}
|
||||
|
||||
func (s *Service) Shutdown() {
|
||||
log.Info("service shutting down")
|
||||
if s.Config.Healthz.Enabled {
|
||||
s.Healthz.Shutdown()
|
||||
log.Info("healthz stopped")
|
||||
}
|
||||
for name, provider := range s.Providers {
|
||||
provider.Shutdown()
|
||||
log.Info("provider stopped", "provider", name)
|
||||
}
|
||||
log.Info("service stopped")
|
||||
}
|
Loading…
Reference in New Issue
Block a user