From e4c2bf642d850279ccf3f6abbd09cfbf012e2e3e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:12:50 -0700 Subject: [PATCH] feat(x/metrics): add module for emiting custom chain metrics (backport #1668) (#1674) * feat(x/metrics): add module for emiting custom chain metrics (#1668) * initialize x/metrics with metrics collection * include global labels in x/metrics metrics * add x/metrics spec * add x/metrics test coverage * update changelog (cherry picked from commit 9a0aed7626582a50ac08bd69d8f43b98de8ae196) # Conflicts: # CHANGELOG.md # app/app.go # go.mod * fix merge conflicts, add docker-build command --------- Co-authored-by: Robert Pirtle --- .tool-versions | 1 + CHANGELOG.md | 50 +++++++++++- Makefile | 3 + app/app.go | 8 ++ cmd/kava/cmd/app.go | 2 + go.mod | 4 +- x/metrics/abci.go | 12 +++ x/metrics/abci_test.go | 45 +++++++++++ x/metrics/module.go | 132 ++++++++++++++++++++++++++++++++ x/metrics/spec/README.md | 36 +++++++++ x/metrics/types/keys.go | 6 ++ x/metrics/types/metrics.go | 89 +++++++++++++++++++++ x/metrics/types/metrics_test.go | 72 +++++++++++++++++ 13 files changed, 456 insertions(+), 4 deletions(-) create mode 100644 .tool-versions create mode 100644 x/metrics/abci.go create mode 100644 x/metrics/abci_test.go create mode 100644 x/metrics/module.go create mode 100644 x/metrics/spec/README.md create mode 100644 x/metrics/types/keys.go create mode 100644 x/metrics/types/metrics.go create mode 100644 x/metrics/types/metrics_test.go diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000000..bb46032eca --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.18.10 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d875b2457..81dc2f55f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,49 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +- (metrics) [#1668] Adds non-state breaking x/metrics module for custom telemetry. + +## [v0.19.2](https://github.com/Kava-Labs/kava/releases/tag/v0.19.2) +- Adds ethermint patch for finalized json rpc support & historical eth_call queries + +## [v0.19.1](https://github.com/Kava-Labs/kava/releases/tag/v0.19.1) + +- [#1384] Adds total supply queries for earn, savings, liquid (#1384) + +## [v0.19.0](https://github.com/Kava-Labs/kava/releases/tag/v0.19.0) + +* Adds liquid module for converting staked kava to bkava staking derivative tokens +* Adds earn module to support automated yield strategies +* Implements the Kava Infrastructure Rewards proposal (https://www.mintscan.io/kava/proposals/97) +* Adds support for signing cosmos messages using Metamask via eip712 + + +## [v0.18.2](https://github.com/Kava-Labs/kava/releases/tag/v0.18.2) + +* Update to go 1.18, cosmos v0.45.9, and tendermint v0.34.21 +* Update ci & docker to go 1.18 +* Config updates for iavl changes and broadcast mode flag +* Ensure fast node is disable if config value is not set in order to + avoid fast node upgrade for nodes that do not update their app.toml +* Update to new circle ci image instead of using legacy image +* Remove previous ics23 package -- now used directly from cosmos-sdk repo + +## [v0.18.1](https://github.com/Kava-Labs/kava/releases/tag/v0.18.1) + +Patch for IBC Security Advisory Dragonberry: https://forum.cosmos.network/t/ibc-security-advisory-dragonberry/7702 + +## [v0.18.0](https://github.com/Kava-Labs/kava/releases/tag/v0.18.0) + +Security upgrade patch release for kava 10. + +## [v0.17.3](https://github.com/Kava-Labs/kava/releases/tag/v0.17.3) + +Release of Kava EVM & the first binary with chain id `kava_2222-10`. + +See Release for full details: +* [v0.17.3](https://github.com/Kava-Labs/kava/releases/tag/v0.17.3) +* [v0.17.1](https://github.com/Kava-Labs/kava/releases/tag/v0.17.1) + ### State Machine Breaking [\#1158](https://github.com/Kava-Labs/kava/pull/1158) Split existing auction `bid_duration` parameter into `forward_bid_duration` and `reverse_bid_duration` @@ -43,12 +86,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### State Machine Breaking -[\#1152](https://github.com/Kava-Labs/kava/pull/1152) Fix MultiSpend Proposal With Async Upgrade Time +[\#1152](https://github.com/Kava-Labs/kava/pull/1152) Fix MultiSpend Proposal With Async Upgrade Time ## [v0.16.0](https://github.com/Kava-Labs/kava/releases/tag/v0.16.0) ### State Machine Breaking -[\#1106](https://github.com/Kava-Labs/kava/pull/1106) Upgrades app to cosmos-sdk v0.44.x and adds IBC and ICS-20 modules. +[\#1106](https://github.com/Kava-Labs/kava/pull/1106) Upgrades app to cosmos-sdk v0.44.x and adds IBC and ICS-20 modules. ## [v0.13.0] @@ -154,3 +197,6 @@ Bump tendermint version to 0.32.10 to address [cosmos security advisory Lavender ### Improvements [\#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run large-scale simulations remotely using aws-batch + +[#1668]: https://github.com/Kava-Labs/kava/pull/1668 +[#1384]: https://github.com/Kava-Labs/kava/pull/1384 diff --git a/Makefile b/Makefile index d788aa8abf..3af7bb4003 100644 --- a/Makefile +++ b/Makefile @@ -229,6 +229,9 @@ format: ### Localnet ### ############################################################################### +docker-build: + DOCKER_BUILDKIT=1 $(DOCKER) build -t kava/kava:local . + build-docker-local-kava: @$(MAKE) -C networks/local diff --git a/app/app.go b/app/app.go index d853d1d9ae..39674cef0c 100644 --- a/app/app.go +++ b/app/app.go @@ -129,6 +129,8 @@ import ( "github.com/kava-labs/kava/x/liquid" liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper" liquidtypes "github.com/kava-labs/kava/x/liquid/types" + metrics "github.com/kava-labs/kava/x/metrics" + metricstypes "github.com/kava-labs/kava/x/metrics/types" pricefeed "github.com/kava-labs/kava/x/pricefeed" pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper" pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" @@ -202,6 +204,7 @@ var ( liquid.AppModuleBasic{}, earn.AppModuleBasic{}, router.AppModuleBasic{}, + metrics.AppModuleBasic{}, ) // module account permissions @@ -245,6 +248,7 @@ type Options struct { MempoolAuthAddresses []sdk.AccAddress EVMTrace string EVMMaxGasWanted uint64 + TelemetryOptions metricstypes.TelemetryOptions } // DefaultOptions is a sensible default Options value. @@ -739,10 +743,12 @@ func NewApp( liquid.NewAppModule(app.liquidKeeper), earn.NewAppModule(app.earnKeeper, app.accountKeeper, app.bankKeeper), router.NewAppModule(app.routerKeeper), + metrics.NewAppModule(options.TelemetryOptions), ) // Warning: Some begin blockers must run before others. Ensure the dependencies are understood before modifying this list. app.mm.SetOrderBeginBlockers( + metricstypes.ModuleName, // Upgrade begin blocker runs migrations on the first block after an upgrade. It should run before any other module. upgradetypes.ModuleName, // Capability begin blocker runs non state changing initialization. @@ -829,6 +835,7 @@ func NewApp( liquidtypes.ModuleName, earntypes.ModuleName, routertypes.ModuleName, + metricstypes.ModuleName, ) // Warning: Some init genesis methods must run before others. Ensure the dependencies are understood before modifying this list @@ -869,6 +876,7 @@ func NewApp( validatorvestingtypes.ModuleName, liquidtypes.ModuleName, routertypes.ModuleName, + metricstypes.ModuleName, ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/cmd/kava/cmd/app.go b/cmd/kava/cmd/app.go index 755d620c3f..317ad8e3d2 100644 --- a/cmd/kava/cmd/app.go +++ b/cmd/kava/cmd/app.go @@ -23,6 +23,7 @@ import ( "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app/params" + metricstypes "github.com/kava-labs/kava/x/metrics/types" ) const ( @@ -93,6 +94,7 @@ func (ac appCreator) newApp( MempoolAuthAddresses: mempoolAuthAddresses, EVMTrace: cast.ToString(appOpts.Get(ethermintflags.EVMTracer)), EVMMaxGasWanted: cast.ToUint64(appOpts.Get(ethermintflags.EVMMaxTxGasWanted)), + TelemetryOptions: metricstypes.TelemetryOptionsFromAppOpts(appOpts), }, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(strings.Replace(cast.ToString(appOpts.Get(server.FlagMinGasPrices)), ";", ",", -1)), diff --git a/go.mod b/go.mod index 1a65547d1d..6d040c8eb6 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,13 @@ require ( github.com/cosmos/cosmos-sdk v0.45.10 github.com/cosmos/ibc-go/v3 v3.3.1 github.com/ethereum/go-ethereum v1.10.16 + github.com/go-kit/kit v0.12.0 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/prometheus/client_golang v1.12.2 github.com/spf13/cast v1.5.0 github.com/spf13/cobra v1.6.0 github.com/stretchr/testify v1.8.0 @@ -61,7 +63,6 @@ require ( github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect - github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-ole/go-ole v1.2.5 // indirect @@ -112,7 +113,6 @@ require ( github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect diff --git a/x/metrics/abci.go b/x/metrics/abci.go new file mode 100644 index 0000000000..14a77d717f --- /dev/null +++ b/x/metrics/abci.go @@ -0,0 +1,12 @@ +package metrics + +import ( + "github.com/kava-labs/kava/x/metrics/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BeginBlocker publishes metrics at the start of each block. +func BeginBlocker(ctx sdk.Context, metrics *types.Metrics) { + metrics.LatestBlockHeight.Set(float64(ctx.BlockHeight())) +} diff --git a/x/metrics/abci_test.go b/x/metrics/abci_test.go new file mode 100644 index 0000000000..5ab542eea5 --- /dev/null +++ b/x/metrics/abci_test.go @@ -0,0 +1,45 @@ +package metrics_test + +import ( + "testing" + + kitmetrics "github.com/go-kit/kit/metrics" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/metrics" + "github.com/kava-labs/kava/x/metrics/types" +) + +type MockGauge struct { + value float64 +} + +func (mg *MockGauge) With(labelValues ...string) kitmetrics.Gauge { return mg } +func (mg *MockGauge) Set(value float64) { mg.value = value } +func (*MockGauge) Add(_ float64) {} + +func ctxWithHeight(height int64) sdk.Context { + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates() + return tApp.NewContext(false, tmproto.Header{Height: height}) +} + +func TestBeginBlockEmitsLatestHeight(t *testing.T) { + gauge := MockGauge{} + myMetrics := &types.Metrics{ + LatestBlockHeight: &gauge, + } + + metrics.BeginBlocker(ctxWithHeight(1), myMetrics) + require.EqualValues(t, 1, gauge.value) + + metrics.BeginBlocker(ctxWithHeight(100), myMetrics) + require.EqualValues(t, 100, gauge.value) + + metrics.BeginBlocker(ctxWithHeight(17e6), myMetrics) + require.EqualValues(t, 17e6, gauge.value) +} diff --git a/x/metrics/module.go b/x/metrics/module.go new file mode 100644 index 0000000000..e2223c2335 --- /dev/null +++ b/x/metrics/module.go @@ -0,0 +1,132 @@ +package metrics + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/x/metrics/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic app module basics object +type AppModuleBasic struct{} + +// RegisterRESTRoutes implements module.AppModuleBasic. +func (AppModuleBasic) RegisterRESTRoutes(client.Context, *mux.Router) {} + +// Name returns the module name +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec register module codec +// Deprecated: unused but necessary to fulfill AppModuleBasic interface +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} + +// DefaultGenesis default genesis state +func (AppModuleBasic) DefaultGenesis(_ codec.JSONCodec) json.RawMessage { + return []byte("{}") +} + +// ValidateGenesis module validate genesis +func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, _ json.RawMessage) error { + return nil +} + +// RegisterInterfaces implements InterfaceModule.RegisterInterfaces +func (a AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd returns the root tx command for the module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd returns no root query command for the module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +//____________________________________________________________________________ + +// AppModule app module type +type AppModule struct { + AppModuleBasic + metrics *types.Metrics +} + +// RegisterRESTRoutes implements module.AppModule. +func (AppModule) RegisterRESTRoutes(client.Context, *mux.Router) {} + +// NewAppModule creates a new AppModule object +func NewAppModule(telemetryOpts types.TelemetryOptions) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + metrics: types.NewMetrics(telemetryOpts), + } +} + +// Name module name +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// RegisterInvariants register module invariants +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route module message route name +// Deprecated: unused but necessary to fulfill AppModule interface +func (am AppModule) Route() sdk.Route { return sdk.Route{} } + +// QuerierRoute module querier route name +// Deprecated: unused but necessary to fulfill AppModule interface +func (AppModule) QuerierRoute() string { return types.ModuleName } + +// LegacyQuerierHandler returns no sdk.Querier. +// Deprecated: unused but necessary to fulfill AppModule interface +func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier { + return nil +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) {} + +// InitGenesis module init-genesis +func (am AppModule) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, _ json.RawMessage) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ExportGenesis module export genesis +func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// BeginBlock module begin-block +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.metrics) +} + +// EndBlock module end-block +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/metrics/spec/README.md b/x/metrics/spec/README.md new file mode 100644 index 0000000000..648ce38afc --- /dev/null +++ b/x/metrics/spec/README.md @@ -0,0 +1,36 @@ + + +# `metrics` + + +## Abstract + +`x/metrics` is a stateless module that does not affect consensus. It captures chain metrics and emits them when the `instrumentation.prometheus` option is enabled in `config.toml`. + +## Precision + +The metrics emitted by `x/metrics` are `float64`s. They use `github.com/go-kit/kit/metrics` Prometheus gauges. Cosmos-sdk's `telemetry` package was not used because, at the time of writing, it only supports `float32`s and so does not maintain accurate representations of ints larger than ~16.8M. With `float64`s, integers may be accurately represented up to ~9e15. + +## Metrics + +The following metrics are defined: +* `cometbft_blocksync_latest_block_height` - this emulates the blocksync `latest_block_height` metric in CometBFT v0.38+. The `cometbft` namespace comes from the `instrumentation.namespace` config.toml value. + +## Metric Labels + +All metrics emitted have the labels defined in app.toml's `telemetry.global-labels` field. This is the same field used by cosmos-sdk's `telemetry` package. + +example: +```toml +# app.toml +[telemetry] +global-labels = [ + ["chain_id", "kava_2222-10"], + ["my_label", "my_value"], +] +``` diff --git a/x/metrics/types/keys.go b/x/metrics/types/keys.go new file mode 100644 index 0000000000..c7a9577aba --- /dev/null +++ b/x/metrics/types/keys.go @@ -0,0 +1,6 @@ +package types + +const ( + // Name of the module + ModuleName = "metrics" +) diff --git a/x/metrics/types/metrics.go b/x/metrics/types/metrics.go new file mode 100644 index 0000000000..7c01e4745a --- /dev/null +++ b/x/metrics/types/metrics.go @@ -0,0 +1,89 @@ +package types + +import ( + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/discard" + prometheus "github.com/go-kit/kit/metrics/prometheus" + stdprometheus "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cast" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" +) + +// TelemetryOptions defines the app configurations for the x/metrics module +type TelemetryOptions struct { + // CometBFT config value for instrumentation.prometheus (config.toml) + PrometheusEnabled bool + // Namespace for CometBFT metrics. Used to emulate CometBFT metrics. + CometBFTMetricsNamespace string + // A list of keys and values used as labels on all metrics + GlobalLabelsAndValues []string +} + +// TelemetryOptionsFromAppOpts creates the TelemetryOptions from server AppOptions +func TelemetryOptionsFromAppOpts(appOpts servertypes.AppOptions) TelemetryOptions { + prometheusEnabled := cast.ToBool(appOpts.Get("instrumentation.prometheus")) + if !prometheusEnabled { + return TelemetryOptions{ + GlobalLabelsAndValues: []string{}, + } + } + + // parse the app.toml global-labels into a slice of alternating label & value strings + // the value is expected to be a list of [label, value] tuples. + rawLabels := cast.ToSlice(appOpts.Get("telemetry.global-labels")) + globalLabelsAndValues := make([]string, 0, len(rawLabels)*2) + for _, gl := range rawLabels { + l := cast.ToStringSlice(gl) + globalLabelsAndValues = append(globalLabelsAndValues, l[0], l[1]) + } + + return TelemetryOptions{ + PrometheusEnabled: true, + CometBFTMetricsNamespace: cast.ToString(appOpts.Get("instrumentation.namespace")), + GlobalLabelsAndValues: globalLabelsAndValues, + } +} + +// Metrics contains metrics exposed by this module. +// They use go-kit metrics like CometBFT as opposed to using cosmos-sdk telemetry +// because the sdk's telemetry only supports float32s, whereas go-kit prometheus +// metrics correctly handle float64s (and thus a larger number of int64s) +type Metrics struct { + // The height of the latest block. + // This gauges exactly emulates the default blocksync metric in CometBFT v0.38+ + // It should be removed when kava has been updated to CometBFT v0.38+. + // see https://github.com/cometbft/cometbft/blob/v0.38.0-rc3/blocksync/metrics.gen.go + LatestBlockHeight metrics.Gauge +} + +// NewMetrics creates a new Metrics object based on whether or not prometheus instrumentation is enabled. +func NewMetrics(opts TelemetryOptions) *Metrics { + if opts.PrometheusEnabled { + return PrometheusMetrics(opts) + } + return NoopMetrics() +} + +// PrometheusMetrics returns the gauges for when prometheus instrumentation is enabled. +func PrometheusMetrics(opts TelemetryOptions) *Metrics { + labels := []string{} + for i := 0; i < len(opts.GlobalLabelsAndValues); i += 2 { + labels = append(labels, opts.GlobalLabelsAndValues[i]) + } + return &Metrics{ + LatestBlockHeight: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: opts.CometBFTMetricsNamespace, + Subsystem: "blocksync", + Name: "latest_block_height", + Help: "The height of the latest block.", + }, labels).With(opts.GlobalLabelsAndValues...), + } +} + +// NoopMetrics are a do-nothing placeholder used when prometheus instrumentation is not enabled. +func NoopMetrics() *Metrics { + return &Metrics{ + LatestBlockHeight: discard.NewGauge(), + } +} diff --git a/x/metrics/types/metrics_test.go b/x/metrics/types/metrics_test.go new file mode 100644 index 0000000000..2af68d2520 --- /dev/null +++ b/x/metrics/types/metrics_test.go @@ -0,0 +1,72 @@ +package types_test + +import ( + "testing" + + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/prometheus" + "github.com/kava-labs/kava/x/metrics/types" + "github.com/stretchr/testify/require" +) + +func isPrometheusGauge(g metrics.Gauge) bool { + _, ok := g.(*prometheus.Gauge) + return ok +} + +var ( + disabledOpts = types.TelemetryOptions{ + PrometheusEnabled: false, + } + enabledOpts = types.TelemetryOptions{ + PrometheusEnabled: true, + CometBFTMetricsNamespace: "cometbft", + GlobalLabelsAndValues: []string{"label1", "value1", "label2", "value2"}, + } +) + +func TestNewMetrics_DisabledVsEnabled(t *testing.T) { + myMetrics := types.NewMetrics(disabledOpts) + require.False(t, isPrometheusGauge(myMetrics.LatestBlockHeight)) + + myMetrics = types.NewMetrics(enabledOpts) + require.True(t, isPrometheusGauge(myMetrics.LatestBlockHeight)) +} + +type MockAppOpts struct { + store map[string]interface{} +} + +func (mao *MockAppOpts) Get(key string) interface{} { + return mao.store[key] +} + +func TestTelemetryOptionsFromAppOpts(t *testing.T) { + appOpts := MockAppOpts{store: make(map[string]interface{})} + + // test disabled functionality + appOpts.store["instrumentation.prometheus"] = false + + opts := types.TelemetryOptionsFromAppOpts(&appOpts) + require.False(t, opts.PrometheusEnabled) + + // test enabled functionality + appOpts.store["instrumentation.prometheus"] = true + appOpts.store["instrumentation.namespace"] = "magic" + appOpts.store["telemetry.global-labels"] = []interface{}{} + + opts = types.TelemetryOptionsFromAppOpts(&appOpts) + require.True(t, opts.PrometheusEnabled) + require.Equal(t, "magic", opts.CometBFTMetricsNamespace) + require.Len(t, opts.GlobalLabelsAndValues, 0) + + appOpts.store["telemetry.global-labels"] = []interface{}{ + []interface{}{"label1", "value1"}, + []interface{}{"label2", "value2"}, + } + opts = types.TelemetryOptionsFromAppOpts(&appOpts) + require.True(t, opts.PrometheusEnabled) + require.Equal(t, "magic", opts.CometBFTMetricsNamespace) + require.Len(t, opts.GlobalLabelsAndValues, 4) + require.Equal(t, enabledOpts.GlobalLabelsAndValues, opts.GlobalLabelsAndValues) +}