diff --git a/.goreleaser.yml b/.goreleaser.yml index 5e060fa..7b8f3e9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,7 +5,7 @@ env: builds: - id: darwin - main: ./cmd/feeder + main: . binary: pricefeeder hooks: pre: @@ -35,7 +35,7 @@ builds: - CC=oa64-clang - id: linux - main: ./cmd/feeder + main: . binary: pricefeeder hooks: pre: diff --git a/Dockerfile b/Dockerfile index befce8b..221f32d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ RUN go mod download COPY . . RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg \ - go build -o ./build/feeder ./cmd/feeder/ + go build -o ./build/feeder . FROM gcr.io/distroless/static:nonroot diff --git a/Makefile b/Makefile index e8f2198..08260f9 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,10 @@ test: go test ./... run: - go run ./cmd/feeder/main.go + go run ./main.go run-debug: - go run ./cmd/feeder/main.go -debug true + go run ./main.go -debug true ############################################################################### ### Build ### @@ -45,3 +45,14 @@ release: -e GITHUB_TOKEN=${GITHUB_TOKEN} \ goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ release --rm-dist + +release-snapshot: + docker run \ + --rm \ + --platform=linux/amd64 \ + -v "$(CURDIR)":/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + -e CGO_ENABLED=1 \ + -e GITHUB_TOKEN=${GITHUB_TOKEN} \ + goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ + release --rm-dist --snapshot diff --git a/README.md b/README.md index a7ed18f..8a60f1b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This would allow you to run `pricefeeder` using a local instance of the network. ```bash git clone git@github.com:NibiruChain/nibiru.git cd nibiru -git checkout v1.0.0 +git checkout v1.0.3 make localnet ``` diff --git a/cmd/feeder/main.go b/cmd/feeder/main.go deleted file mode 100644 index 8b5c08b..0000000 --- a/cmd/feeder/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "flag" - "os" - "os/signal" - - "github.com/NibiruChain/nibiru/app" - "github.com/rs/zerolog" - - "github.com/NibiruChain/pricefeeder/config" - "github.com/NibiruChain/pricefeeder/feeder" - "github.com/NibiruChain/pricefeeder/feeder/eventstream" - "github.com/NibiruChain/pricefeeder/feeder/priceposter" - "github.com/NibiruChain/pricefeeder/feeder/priceprovider" -) - -func setupLogger() zerolog.Logger { - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - debug := flag.Bool("debug", false, "sets log level to debug") - flag.Parse() - // Default level is INFO, unless debug flag is present - zerolog.SetGlobalLevel(zerolog.InfoLevel) - if *debug { - zerolog.SetGlobalLevel(zerolog.DebugLevel) - } - - return zerolog.New(os.Stderr).With().Timestamp().Logger() -} - -func main() { - logger := setupLogger() - app.SetPrefixes(app.AccountAddressPrefix) - - c := config.MustGet() - - eventStream := eventstream.Dial(c.WebsocketEndpoint, c.GRPCEndpoint, c.EnableTLS, logger) - priceProvider := priceprovider.NewAggregatePriceProvider(c.ExchangesToPairToSymbolMap, c.DataSourceConfigMap, logger) - kb, valAddr, feederAddr := config.GetAuth(c.FeederMnemonic) - - if c.ValidatorAddr != nil { - valAddr = *c.ValidatorAddr - } - pricePoster := priceposter.Dial(c.GRPCEndpoint, c.ChainID, c.EnableTLS, kb, valAddr, feederAddr, logger) - - f := feeder.NewFeeder(eventStream, priceProvider, pricePoster, logger) - f.Run() - defer f.Close() - - handleInterrupt(logger, f) - - select {} -} - -// handleInterrupt listens for SIGINT and gracefully shuts down the feeder. -func handleInterrupt(logger zerolog.Logger, f *feeder.Feeder) { - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - - go func() { - <-interrupt - logger.Info().Msg("shutting down gracefully") - f.Close() - os.Exit(1) - }() -} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..29b1a10 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,83 @@ +package cmd + +import ( + "flag" + "fmt" + "net/http" + "os" + "os/signal" + + "github.com/NibiruChain/nibiru/app" + "github.com/NibiruChain/pricefeeder/config" + "github.com/NibiruChain/pricefeeder/feeder" + "github.com/NibiruChain/pricefeeder/feeder/eventstream" + "github.com/NibiruChain/pricefeeder/feeder/priceposter" + "github.com/NibiruChain/pricefeeder/feeder/priceprovider" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/zerolog" + "github.com/spf13/cobra" +) + +func setupLogger() zerolog.Logger { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + debug := flag.Bool("debug", false, "sets log level to debug") + flag.Parse() + // Default level is INFO, unless debug flag is present + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + return zerolog.New(os.Stderr).With().Timestamp().Logger() +} + +// handleInterrupt listens for SIGINT and gracefully shuts down the feeder. +func handleInterrupt(logger zerolog.Logger, f *feeder.Feeder) { + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + go func() { + <-interrupt + logger.Info().Msg("shutting down gracefully") + f.Close() + os.Exit(1) + }() +} + +var rootCmd = &cobra.Command{ + Use: "pricefeeder", + Short: "Pricefeeder daemon for posting prices to Nibiru Chain", + Run: func(cmd *cobra.Command, args []string) { + logger := setupLogger() + app.SetPrefixes(app.AccountAddressPrefix) + + c := config.MustGet() + + eventStream := eventstream.Dial(c.WebsocketEndpoint, c.GRPCEndpoint, c.EnableTLS, logger) + priceProvider := priceprovider.NewAggregatePriceProvider(c.ExchangesToPairToSymbolMap, c.DataSourceConfigMap, logger) + kb, valAddr, feederAddr := config.GetAuth(c.FeederMnemonic) + + if c.ValidatorAddr != nil { + valAddr = *c.ValidatorAddr + } + pricePoster := priceposter.Dial(c.GRPCEndpoint, c.ChainID, c.EnableTLS, kb, valAddr, feederAddr, logger) + + f := feeder.NewFeeder(eventStream, priceProvider, pricePoster, logger) + f.Run() + defer f.Close() + + handleInterrupt(logger, f) + + http.Handle("/metrics", promhttp.Handler()) + http.ListenAndServe(":8080", nil) + + select {} + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..3c28b22 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("v1.0.3") + }, +} diff --git a/feeder/priceposter/client.go b/feeder/priceposter/client.go index 263ee04..b412c76 100644 --- a/feeder/priceposter/client.go +++ b/feeder/priceposter/client.go @@ -7,6 +7,7 @@ import ( "github.com/NibiruChain/nibiru/app" oracletypes "github.com/NibiruChain/nibiru/x/oracle/types" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -14,6 +15,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" txservice "github.com/cosmos/cosmos-sdk/types/tx" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -102,6 +105,12 @@ func (c *Client) Whoami() sdk.ValAddress { return c.validator } +var pricePosterCounter = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Name: "prices_posted_total", + Help: "The total number of price update txs sent to the chain, by success status", +}, []string{"success"}) + func (c *Client) SendPrices(vp types.VotingPeriod, prices []types.Price) { logger := c.logger.With().Uint64("voting-period-height", vp.Height).Logger() @@ -112,11 +121,13 @@ func (c *Client) SendPrices(vp types.VotingPeriod, prices []types.Price) { resp, err := vote(ctx, newPrevote, c.previousPrevote, c.validator, c.feeder, c.deps, logger) if err != nil { logger.Err(err).Msg("prevote failed") + pricePosterCounter.WithLabelValues("false").Inc() return } c.previousPrevote = newPrevote logger.Info().Str("tx-hash", resp.TxHash).Msg("successfully forwarded prices") + pricePosterCounter.WithLabelValues("true").Inc() } func (c *Client) Close() { diff --git a/feeder/priceprovider/aggregateprovider.go b/feeder/priceprovider/aggregateprovider.go index 6098f25..521bbd8 100644 --- a/feeder/priceprovider/aggregateprovider.go +++ b/feeder/priceprovider/aggregateprovider.go @@ -4,7 +4,10 @@ import ( "encoding/json" "github.com/NibiruChain/nibiru/x/common/asset" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/rs/zerolog" ) @@ -37,6 +40,12 @@ func NewAggregatePriceProvider( } } +var aggregatePriceProvider = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: metrics.PrometheusNamespace, + Name: "aggregate_prices_total", + Help: "The total number prices provided by the aggregate price provider, by pair, source, and success status", +}, []string{"pair", "source", "success"}) + // GetPrice fetches the first available and correct price from the wrapped PriceProviders. // Iteration is exhaustive and random. // If no correct PriceResponse is found, then an invalid PriceResponse is returned. @@ -46,12 +55,14 @@ func (a AggregatePriceProvider) GetPrice(pair asset.Pair) types.Price { for _, p := range a.providers { price := p.GetPrice(pair) if price.Valid { + aggregatePriceProvider.WithLabelValues(pair.String(), price.SourceName, "true").Inc() return price } } // if we reach here no valid symbols were found a.logger.Warn().Str("pair", pair.String()).Msg("no valid price found") + aggregatePriceProvider.WithLabelValues(pair.String(), "missing", "false").Inc() return types.Price{ SourceName: "missing", Pair: pair, diff --git a/feeder/priceprovider/sources/binance.go b/feeder/priceprovider/sources/binance.go index 0ddb7aa..6f84fb2 100644 --- a/feeder/priceprovider/sources/binance.go +++ b/feeder/priceprovider/sources/binance.go @@ -6,6 +6,7 @@ import ( "net/http" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -36,12 +37,16 @@ func BinancePriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (r url := "https://api.binance.us/api/v3/ticker/price?symbols=%5B" + BinanceSymbolCsv(symbols) + "%5D" resp, err := http.Get(url) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Binance") + metrics.PriceSourceCounter.WithLabelValues(Binance, "false").Inc() return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Binance") + metrics.PriceSourceCounter.WithLabelValues(Binance, "false").Inc() return nil, err } @@ -49,6 +54,8 @@ func BinancePriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (r err = json.Unmarshal(b, &tickers) if err != nil { + logger.Err(err).Msg("failed to unmarshal response body from Binance") + metrics.PriceSourceCounter.WithLabelValues(Binance, "false").Inc() return nil, err } @@ -57,6 +64,7 @@ func BinancePriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (r rawPrices[types.Symbol(ticker.Symbol)] = ticker.Price logger.Debug().Msgf("fetched price for %s on data source %s: %f", ticker.Symbol, Binance, ticker.Price) } + metrics.PriceSourceCounter.WithLabelValues(Binance, "true").Inc() return rawPrices, nil } diff --git a/feeder/priceprovider/sources/bitfinex.go b/feeder/priceprovider/sources/bitfinex.go index d4e135a..f117267 100644 --- a/feeder/priceprovider/sources/bitfinex.go +++ b/feeder/priceprovider/sources/bitfinex.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -35,18 +36,24 @@ func BitfinexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) ( var url string = "https://api-pub.bitfinex.com/v2/tickers?symbols=" + BitfinexSymbolCsv(symbols) resp, err := http.Get(url) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Bitfinex") + metrics.PriceSourceCounter.WithLabelValues(Bitfinex, "false").Inc() return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Bitfinex") + metrics.PriceSourceCounter.WithLabelValues(Bitfinex, "false").Inc() return nil, err } var tickers []ticker err = json.Unmarshal(b, &tickers) if err != nil { + logger.Err(err).Msg("failed to unmarshal response body from Bitfinex") + metrics.PriceSourceCounter.WithLabelValues(Bitfinex, "false").Inc() return nil, err } @@ -62,5 +69,7 @@ func BitfinexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) ( logger.Debug().Msg(fmt.Sprintf("fetched price for %s on data source %s: %f", symbol, Bitfinex, lastPrice)) } + metrics.PriceSourceCounter.WithLabelValues(Bitfinex, "true").Inc() + return rawPrices, nil } diff --git a/feeder/priceprovider/sources/bybit.go b/feeder/priceprovider/sources/bybit.go index a09aaba..1792ca4 100644 --- a/feeder/priceprovider/sources/bybit.go +++ b/feeder/priceprovider/sources/bybit.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -33,18 +34,24 @@ func BybitPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (raw resp, err := http.Get(url) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Bybit") + metrics.PriceSourceCounter.WithLabelValues(Bybit, "false").Inc() return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Bybit") + metrics.PriceSourceCounter.WithLabelValues(Bybit, "false").Inc() return nil, err } var response BybitResponse err = json.Unmarshal(b, &response) if err != nil { + logger.Err(err).Msg("failed to unmarshal response body from Bybit") + metrics.PriceSourceCounter.WithLabelValues(Bybit, "false").Inc() return nil, err } @@ -63,5 +70,6 @@ func BybitPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (raw } } logger.Debug().Msgf("fetched prices for %s on data source %s: %v", symbols, Bybit, rawPrices) + metrics.PriceSourceCounter.WithLabelValues(Bybit, "true").Inc() return rawPrices, nil } diff --git a/feeder/priceprovider/sources/coingecko.go b/feeder/priceprovider/sources/coingecko.go index eed18b3..74f0b3a 100644 --- a/feeder/priceprovider/sources/coingecko.go +++ b/feeder/priceprovider/sources/coingecko.go @@ -8,6 +8,7 @@ import ( "net/url" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -31,25 +32,34 @@ func CoingeckoPriceUpdate(sourceConfig json.RawMessage) types.FetchPricesFunc { return func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { c, err := extractConfig(sourceConfig) if err != nil { + logger.Err(err).Msg("failed to extract coingecko config") + metrics.PriceSourceCounter.WithLabelValues(Coingecko, "false").Inc() return nil, err } res, err := http.Get(buildURL(symbols, c)) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Coingecko") + metrics.PriceSourceCounter.WithLabelValues(Coingecko, "false").Inc() return nil, err } defer res.Body.Close() response, err := io.ReadAll(res.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Coingecko") + metrics.PriceSourceCounter.WithLabelValues(Coingecko, "false").Inc() return nil, err } rawPrices, err := extractPricesFromResponse(symbols, response, logger) if err != nil { + logger.Err(err).Msg("failed to extract prices from Coingecko response") + metrics.PriceSourceCounter.WithLabelValues(Coingecko, "false").Inc() return nil, err } + metrics.PriceSourceCounter.WithLabelValues(Coingecko, "true").Inc() return rawPrices, nil } } diff --git a/feeder/priceprovider/sources/coinmarketcap.go b/feeder/priceprovider/sources/coinmarketcap.go index f6dd8c3..deb5db6 100644 --- a/feeder/priceprovider/sources/coinmarketcap.go +++ b/feeder/priceprovider/sources/coinmarketcap.go @@ -9,6 +9,7 @@ import ( "net/url" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -44,31 +45,42 @@ func CoinmarketcapPriceUpdate(coinmarketcapConfig json.RawMessage) types.FetchPr return func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { config, err := getConfig(coinmarketcapConfig) if err != nil { + logger.Err(err).Msg("failed to extract coinmarketcap config") + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "false").Inc() return nil, err } req, err := buildReq(symbols, config) if err != nil { + logger.Err(err).Msg("failed to build request for Coinmarketcap") + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "false").Inc() return nil, err } client := &http.Client{} res, err := client.Do(req) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Coinmarketcap") + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "false").Inc() return nil, err } defer res.Body.Close() response, err := io.ReadAll(res.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Coinmarketcap") + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "false").Inc() return nil, err } rawPrices, err := getPricesFromResponse(symbols, response, logger) if err != nil { + logger.Err(err).Msg("failed to extract prices from Coinmarketcap response") + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "false").Inc() return nil, err } + metrics.PriceSourceCounter.WithLabelValues(CoinMarketCap, "true").Inc() return rawPrices, nil } } diff --git a/feeder/priceprovider/sources/gateio.go b/feeder/priceprovider/sources/gateio.go index 9d8c85b..fa69b27 100644 --- a/feeder/priceprovider/sources/gateio.go +++ b/feeder/priceprovider/sources/gateio.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -24,18 +25,24 @@ func GateIoPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (ra url := "https://api.gateio.ws/api/v4/spot/tickers" resp, err := http.Get(url) if err != nil { + logger.Err(err).Msg("failed to fetch prices from GateIo") + metrics.PriceSourceCounter.WithLabelValues(GateIo, "false").Inc() return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from GateIo") + metrics.PriceSourceCounter.WithLabelValues(GateIo, "false").Inc() return nil, err } var tickers []map[string]interface{} err = json.Unmarshal(b, &tickers) if err != nil { + logger.Err(err).Msg("failed to unmarshal response body from GateIo") + metrics.PriceSourceCounter.WithLabelValues(GateIo, "false").Inc() return nil, err } @@ -56,5 +63,6 @@ func GateIoPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (ra logger.Debug().Msg(fmt.Sprintf("fetched price for %s on data source %s: %f", symbol, GateIo, price)) } + metrics.PriceSourceCounter.WithLabelValues(GateIo, "true").Inc() return rawPrices, nil } diff --git a/feeder/priceprovider/sources/okex.go b/feeder/priceprovider/sources/okex.go index 7435bbf..de6fce3 100644 --- a/feeder/priceprovider/sources/okex.go +++ b/feeder/priceprovider/sources/okex.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/NibiruChain/pricefeeder/metrics" "github.com/NibiruChain/pricefeeder/types" "github.com/rs/zerolog" ) @@ -34,18 +35,24 @@ func OkexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawP resp, err := http.Get(url) if err != nil { + logger.Err(err).Msg("failed to fetch prices from Okex") + metrics.PriceSourceCounter.WithLabelValues(Okex, "false").Inc() return nil, err } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { + logger.Err(err).Msg("failed to read response body from Okex") + metrics.PriceSourceCounter.WithLabelValues(Okex, "false").Inc() return nil, err } var response OkexResponse err = json.Unmarshal(b, &response) if err != nil { + logger.Err(err).Msg("failed to unmarshal response body from Okex") + metrics.PriceSourceCounter.WithLabelValues(Okex, "false").Inc() return nil, err } @@ -66,5 +73,7 @@ func OkexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawP rawPrices[symbol] = price logger.Debug().Msg(fmt.Sprintf("fetched price for %s on data source %s: %f", symbol, Okex, price)) } + + metrics.PriceSourceCounter.WithLabelValues(Okex, "true").Inc() return rawPrices, nil } diff --git a/go.mod b/go.mod index a59a757..3ede7a0 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/jarcoal/httpmock v1.2.0 github.com/joho/godotenv v1.4.0 github.com/rs/zerolog v1.30.0 + github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.58.3 ) @@ -150,7 +151,6 @@ require ( github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.16.0 // indirect diff --git a/main.go b/main.go new file mode 100644 index 0000000..45b4d9c --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/NibiruChain/pricefeeder/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/metrics/README.md b/metrics/README.md new file mode 100644 index 0000000..72c8a0a --- /dev/null +++ b/metrics/README.md @@ -0,0 +1,30 @@ +# Pricefeeder Metrics + +## Available metrics + +### `fetched_prices_total` + +The total number of prices fetched from the data sources. This metric is incremented every time the price feeder fetches prices from the data sources. + +**labels**: + +- `source`: The data source from which the price was fetched, e.g. `Bybit`. +- `success`: The result of the fetch operation. Possible values are 'true' and 'false'. + +### `aggregate_prices_total` + +The total number of times the `AggregatePriceProvider` is called to return a price. It randomly selects a source for each pair from its map of price providers. This metric is incremented every time the `AggregatePriceProvider` is called. + +**labels**: + +- `pair`: The pair for which the price was aggregated. +- `source`: The data source from which the price was fetched, e.g. `Bybit`. +- `success`: The result of the fetch operation. Possible values are 'true' and 'false'. + +### `prices_posted_total` + +The total number of txs sent to the on-chain oracle module. This metric is incremented every time the price feeder posts a price to the on-chain oracle module. + +**labels**: + +- `success`: The result of the post operation. Possible values are 'true' and 'false'. diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 0000000..7913221 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,14 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +const PrometheusNamespace = "pricefeeder" + +var PriceSourceCounter = promauto.NewCounterVec(prometheus.CounterOpts{ + Namespace: PrometheusNamespace, + Name: "fetched_prices_total", + Help: "The total number prices fetched, by source and success status", +}, []string{"source", "success"})