diff --git a/.gitattributes b/.gitattributes index 66cf55801745..f13ccebea0d7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -client/lcd/swagger-ui/* linguist-vendored +client/docs/swagger-ui/* linguist-vendored diff --git a/CHANGELOG.md b/CHANGELOG.md index dadcf175e0ef..c2a01ffd495f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,66 @@ respectively, and the latter defines the height interval in which versions are d * (x/auth) [\#5950](https://github.com/cosmos/cosmos-sdk/pull/5950) Fix `IncrementSequenceDecorator` to use is `IsReCheckTx` instead of `IsCheckTx` to allow account sequence incrementing. +### Client Breaking + +* (api) [\#6426](https://github.com/cosmos/cosmos-sdk/pull/6426) The ability to start an out-of-process API REST server has now been removed. Instead, the API server is now started in-process along with the application and Tendermint. Configuration options have been added to `app.toml` to enable/disable the API server along with additional HTTP server options. + +## [v0.39.1] + +* (x/auth) [\#6861](https://github.com/cosmos/cosmos-sdk/pull/6861) Remove public key Bech32 encoding for all account types for JSON serialization, instead relying on direct Amino encoding. In addition, JSON serialization utilizes Amino instead of the Go stdlib, so integers are treated as strings. +* (client) [\#6853](https://github.com/cosmos/cosmos-sdk/pull/6853) Add --unsafe-cors flag. + +## [v0.39.0] + +### Improvements + +* (deps) Bump Tendermint version to [v0.33.6](https://github.com/tendermint/tendermint/releases/tag/v0.33.6) +* (deps) Bump IAVL version to [v0.14.0](https://github.com/cosmos/iavl/releases/tag/v0.14.0) +* (client) [\#5585](https://github.com/cosmos/cosmos-sdk/pull/5585) `CLIContext` additions: + * Introduce `QueryABCI` that returns the full `abci.ResponseQuery` with inclusion Merkle proofs. + * Added `prove` flag for Merkle proof verification. +* (x/staking) [\#6791)](https://github.com/cosmos/cosmos-sdk/pull/6791) Close {UBDQueue,RedelegationQueu}Iterator once used. + +### API Breaking Changes + +* (baseapp) [\#5837](https://github.com/cosmos/cosmos-sdk/issues/5837) Transaction simulation now returns a `SimulationResponse` which contains the `GasInfo` and `Result` from the execution. + +### Client Breaking Changes + +* (x/auth) [\#6745](https://github.com/cosmos/cosmos-sdk/issues/6745) Remove BaseAccount's custom JSON {,un}marshalling. + +### Bug Fixes + +* (store) [\#6475](https://github.com/cosmos/cosmos-sdk/pull/6475) Revert IAVL pruning functionality introduced in +[v0.13.0](https://github.com/cosmos/iavl/releases/tag/v0.13.0), +where the IAVL no longer keeps states in-memory in which it flushes periodically. IAVL now commits and +flushes every state to disk as it did pre-v0.13.0. The SDK's multi-store will track and ensure the proper +heights are pruned. The operator can set the pruning options via a `pruning` config via the CLI or +through `app.toml`. The `pruning` flag exposes `default|everything|nothing|custom` as options -- +see docs for further details. If the operator chooses `custom`, they may provide granular pruning +options `pruning-keep-recent`, `pruning-keep-every`, and `pruning-interval`. The former two options +dictate how many recent versions are kept on disk and the offset of what versions are kept after that +respectively, and the latter defines the height interval in which versions are deleted in a batch. +**Note, there are some client-facing API breaking changes with regard to IAVL, stores, and pruning settings.** +* (x/distribution) [\#6210](https://github.com/cosmos/cosmos-sdk/pull/6210) Register `MsgFundCommunityPool` in distribution amino codec. +* (types) [\#5741](https://github.com/cosmos/cosmos-sdk/issues/5741) Prevent `ChainAnteDecorators()` from panicking when empty `AnteDecorator` slice is supplied. +* (baseapp) [\#6306](https://github.com/cosmos/cosmos-sdk/issues/6306) Prevent events emitted by the antehandler from being persisted between transactions. +* (client/keys) [\#5091](https://github.com/cosmos/cosmos-sdk/issues/5091) `keys parse` does not honor client app's configuration. +* (x/bank) [\#6674](https://github.com/cosmos/cosmos-sdk/pull/6674) Create account if recipient does not exist on handing `MsgMultiSend`. +* (x/auth) [\#6287](https://github.com/cosmos/cosmos-sdk/pull/6287) Fix nonce stuck when sending multiple transactions from an account in a same block. + +## [v0.38.5] - 2020-07-02 + +### Improvements + +* (tendermint) Bump Tendermint version to [v0.33.6](https://github.com/tendermint/tendermint/releases/tag/v0.33.6). + +## [v0.38.4] - 2020-05-21 + +### Bug Fixes + +* (x/auth) [\#5950](https://github.com/cosmos/cosmos-sdk/pull/5950) Fix `IncrementSequenceDecorator` to use is `IsReCheckTx` instead of `IsCheckTx` to allow account sequence incrementing. + ## [v0.38.3] - 2020-04-09 * (tendermint) Bump Tendermint version to [v0.33.3](https://github.com/tendermint/tendermint/releases/tag/v0.33.3). diff --git a/Makefile b/Makefile index 9d857bc8ca7c..c5f77e6eea95 100644 --- a/Makefile +++ b/Makefile @@ -180,10 +180,10 @@ lint: golangci-lint go mod verify .PHONY: lint -format: tools - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs gofmt -w -s - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs misspell -w - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/lcd/statik/statik.go" | xargs goimports -w -local github.com/cosmos/cosmos-sdk +format: + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs gofmt -w -s + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs misspell -w + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' | xargs goimports -w -local github.com/cosmos/cosmos-sdk .PHONY: format benchmark: diff --git a/baseapp/abci.go b/baseapp/abci.go index 0e16574dde87..6d1e3e987341 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -6,10 +6,12 @@ import ( "sort" "strings" "syscall" + "time" abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -99,6 +101,8 @@ func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery { // BeginBlock implements the ABCI application interface. func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { + defer telemetry.MeasureSince(time.Now(), "abci", "begin_block") + if app.cms.TracingEnabled() { app.cms.SetTracingContext(sdk.TraceContext( map[string]interface{}{"blockHeight": req.Header.Height}, @@ -143,6 +147,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg // EndBlock implements the ABCI interface. func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { + defer telemetry.MeasureSince(time.Now(), "abci", "end_block") + if app.deliverState.ms.TracingEnabled() { app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore) } @@ -161,6 +167,8 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc // will contain releveant error information. Regardless of tx execution outcome, // the ResponseCheckTx will contain relevant gas execution context. func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + defer telemetry.MeasureSince(time.Now(), "abci", "check_tx") + tx, err := app.txDecoder(req.Tx) if err != nil { return sdkerrors.ResponseCheckTx(err, 0, 0, app.trace) @@ -199,13 +207,26 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { // Regardless of tx execution outcome, the ResponseDeliverTx will contain relevant // gas execution context. func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + defer telemetry.MeasureSince(time.Now(), "abci", "deliver_tx") + tx, err := app.txDecoder(req.Tx) if err != nil { return sdkerrors.ResponseDeliverTx(err, 0, 0, app.trace) } + gInfo := sdk.GasInfo{} + resultStr := "successful" + + defer func() { + telemetry.IncrCounter(1, "tx", "count") + telemetry.IncrCounter(1, "tx", resultStr) + telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used") + telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") + }() + gInfo, result, err := app.runTx(runTxModeDeliver, req.Tx, tx) if err != nil { + resultStr = "failed" return sdkerrors.ResponseDeliverTx(err, gInfo.GasWanted, gInfo.GasUsed, app.trace) } @@ -226,6 +247,8 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx // against that height and gracefully halt if it matches the latest committed // height. func (app *BaseApp) Commit() (res abci.ResponseCommit) { + defer telemetry.MeasureSince(time.Now(), "abci", "commit") + header := app.deliverState.ctx.BlockHeader() // Write the DeliverTx state which is cache-wrapped and commit the MultiStore. @@ -292,6 +315,8 @@ func (app *BaseApp) halt() { // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. func (app *BaseApp) Query(req abci.RequestQuery) abci.ResponseQuery { + defer telemetry.MeasureSince(time.Now(), "abci", "query") + path := splitPath(req.Path) if len(path) == 0 { sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "no query path provided")) diff --git a/client/lcd/statik/init.go b/client/docs/statik/init.go similarity index 68% rename from client/lcd/statik/init.go rename to client/docs/statik/init.go index 9633aeb291c7..7d91b40fcd37 100644 --- a/client/lcd/statik/init.go +++ b/client/docs/statik/init.go @@ -1,3 +1,3 @@ package statik -//This just for fixing the error in importing empty github.com/cosmos/cosmos-sdk/client/lcd/statik +//This just for fixing the error in importing empty github.com/cosmos/cosmos-sdk/client/docs/statik diff --git a/client/lcd/statik/statik.go b/client/docs/statik/statik.go similarity index 100% rename from client/lcd/statik/statik.go rename to client/docs/statik/statik.go diff --git a/client/lcd/swagger-ui/favicon-16x16.png b/client/docs/swagger-ui/favicon-16x16.png similarity index 100% rename from client/lcd/swagger-ui/favicon-16x16.png rename to client/docs/swagger-ui/favicon-16x16.png diff --git a/client/lcd/swagger-ui/favicon-32x32.png b/client/docs/swagger-ui/favicon-32x32.png similarity index 100% rename from client/lcd/swagger-ui/favicon-32x32.png rename to client/docs/swagger-ui/favicon-32x32.png diff --git a/client/lcd/swagger-ui/index.html b/client/docs/swagger-ui/index.html similarity index 100% rename from client/lcd/swagger-ui/index.html rename to client/docs/swagger-ui/index.html diff --git a/client/lcd/swagger-ui/oauth2-redirect.html b/client/docs/swagger-ui/oauth2-redirect.html similarity index 100% rename from client/lcd/swagger-ui/oauth2-redirect.html rename to client/docs/swagger-ui/oauth2-redirect.html diff --git a/client/lcd/swagger-ui/swagger-ui-bundle.js b/client/docs/swagger-ui/swagger-ui-bundle.js similarity index 100% rename from client/lcd/swagger-ui/swagger-ui-bundle.js rename to client/docs/swagger-ui/swagger-ui-bundle.js diff --git a/client/lcd/swagger-ui/swagger-ui-standalone-preset.js b/client/docs/swagger-ui/swagger-ui-standalone-preset.js similarity index 100% rename from client/lcd/swagger-ui/swagger-ui-standalone-preset.js rename to client/docs/swagger-ui/swagger-ui-standalone-preset.js diff --git a/client/lcd/swagger-ui/swagger-ui.css b/client/docs/swagger-ui/swagger-ui.css similarity index 100% rename from client/lcd/swagger-ui/swagger-ui.css rename to client/docs/swagger-ui/swagger-ui.css diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml similarity index 100% rename from client/lcd/swagger-ui/swagger.yaml rename to client/docs/swagger-ui/swagger.yaml diff --git a/client/flags/flags.go b/client/flags/flags.go index 746b512b0697..b3603a3d8d0d 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -40,35 +40,31 @@ const ( // List of CLI flags const ( - FlagHome = tmcli.HomeFlag - FlagUseLedger = "ledger" - FlagChainID = "chain-id" - FlagNode = "node" - FlagHeight = "height" - FlagGasAdjustment = "gas-adjustment" - FlagTrustNode = "trust-node" - FlagFrom = "from" - FlagName = "name" - FlagAccountNumber = "account-number" - FlagSequence = "sequence" - FlagMemo = "memo" - FlagFees = "fees" - FlagGasPrices = "gas-prices" - FlagBroadcastMode = "broadcast-mode" - FlagDryRun = "dry-run" - FlagGenerateOnly = "generate-only" - FlagIndentResponse = "indent" - FlagListenAddr = "laddr" - FlagMaxOpenConnections = "max-open" - FlagRPCReadTimeout = "read-timeout" - FlagRPCWriteTimeout = "write-timeout" - FlagOutputDocument = "output-document" // inspired by wget -O - FlagSkipConfirmation = "yes" - FlagProve = "prove" - FlagKeyringBackend = "keyring-backend" - FlagPage = "page" - FlagLimit = "limit" - FlagUnsafeCORS = "unsafe-cors" + FlagHome = tmcli.HomeFlag + FlagUseLedger = "ledger" + FlagChainID = "chain-id" + FlagNode = "node" + FlagHeight = "height" + FlagGasAdjustment = "gas-adjustment" + FlagTrustNode = "trust-node" + FlagFrom = "from" + FlagName = "name" + FlagAccountNumber = "account-number" + FlagSequence = "sequence" + FlagMemo = "memo" + FlagFees = "fees" + FlagGasPrices = "gas-prices" + FlagBroadcastMode = "broadcast-mode" + FlagDryRun = "dry-run" + FlagGenerateOnly = "generate-only" + FlagOffline = "offline" + FlagIndentResponse = "indent" + FlagOutputDocument = "output-document" // inspired by wget -O + FlagSkipConfirmation = "yes" + FlagProve = "prove" + FlagKeyringBackend = "keyring-backend" + FlagPage = "page" + FlagLimit = "limit" ) // LineBreak can be included in a command list to provide a blank line @@ -135,18 +131,6 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { return cmds } -// RegisterRestServerFlags registers the flags required for rest server -func RegisterRestServerFlags(cmd *cobra.Command) *cobra.Command { - cmd = GetCommands(cmd)[0] - cmd.Flags().String(FlagListenAddr, "tcp://localhost:1317", "The address for the server to listen on") - cmd.Flags().Uint(FlagMaxOpenConnections, 1000, "The number of maximum open connections") - cmd.Flags().Uint(FlagRPCReadTimeout, 10, "The RPC read timeout (in seconds)") - cmd.Flags().Uint(FlagRPCWriteTimeout, 10, "The RPC write timeout (in seconds)") - cmd.Flags().Bool(FlagUnsafeCORS, false, "Allows CORS requests from all domains. For development purposes only, use it at your own risk.") - - return cmd -} - // Gas flag parsing functions // GasSetting encapsulates the possible values passed through the --gas flag. diff --git a/client/lcd/root.go b/client/lcd/root.go deleted file mode 100644 index e1f551458bfd..000000000000 --- a/client/lcd/root.go +++ /dev/null @@ -1,120 +0,0 @@ -package lcd - -import ( - "fmt" - "net" - "net/http" - "os" - "time" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/rakyll/statik/fs" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/log" - tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server" - - // unnamed import of statik for swagger UI support - _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" -) - -const FlagAllowCORS = "cors" - -// RestServer represents the Light Client Rest server -type RestServer struct { - Mux *mux.Router - CliCtx context.CLIContext - - log log.Logger - listener net.Listener -} - -// NewRestServer creates a new rest server instance -func NewRestServer(cdc *codec.Codec) *RestServer { - r := mux.NewRouter() - cliCtx := context.NewCLIContext().WithCodec(cdc) - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "rest-server") - - return &RestServer{ - Mux: r, - CliCtx: cliCtx, - log: logger, - } -} - -// Start starts the rest server -func (rs *RestServer) Start(listenAddr string, maxOpen int, readTimeout, writeTimeout uint, cors bool) (err error) { - server.TrapSignal(func() { - err := rs.listener.Close() - rs.log.Error("error closing listener", "err", err) - }) - - cfg := tmrpcserver.DefaultConfig() - cfg.MaxOpenConnections = maxOpen - cfg.ReadTimeout = time.Duration(readTimeout) * time.Second - cfg.WriteTimeout = time.Duration(writeTimeout) * time.Second - - rs.listener, err = tmrpcserver.Listen(listenAddr, cfg) - if err != nil { - return - } - rs.log.Info( - fmt.Sprintf( - "Starting application REST service (chain-id: %q)...", - viper.GetString(flags.FlagChainID), - ), - ) - - var h http.Handler = rs.Mux - if cors { - allowAllCORS := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"})) - h = allowAllCORS(h) - } - - return tmrpcserver.Serve(rs.listener, h, rs.log, cfg) -} - -// ServeCommand will start the application REST service as a blocking process. It -// takes a codec to create a RestServer object and a function to register all -// necessary routes. -func ServeCommand(cdc *codec.Codec, registerRoutesFn func(*RestServer)) *cobra.Command { - cmd := &cobra.Command{ - Use: "rest-server", - Short: "Start LCD (light-client daemon), a local REST server", - RunE: func(cmd *cobra.Command, args []string) (err error) { - rs := NewRestServer(cdc) - - registerRoutesFn(rs) - rs.registerSwaggerUI() - - // Start the rest server and return error if one exists - err = rs.Start( - viper.GetString(flags.FlagListenAddr), - viper.GetInt(flags.FlagMaxOpenConnections), - uint(viper.GetInt(flags.FlagRPCReadTimeout)), - uint(viper.GetInt(flags.FlagRPCWriteTimeout)), - viper.GetBool(flags.FlagUnsafeCORS), - ) - - return err - }, - } - - cmd.Flags().Bool(FlagAllowCORS, false, "Allows CORS requests from all domains") - return flags.RegisterRestServerFlags(cmd) -} - -func (rs *RestServer) registerSwaggerUI() { - statikFS, err := fs.New() - if err != nil { - panic(err) - } - staticServer := http.FileServer(statikFS) - rs.Mux.PathPrefix("/swagger-ui/").Handler(http.StripPrefix("/swagger-ui/", staticServer)) -} diff --git a/docs/architecture/adr-013-metrics.md b/docs/architecture/adr-013-metrics.md new file mode 100644 index 000000000000..cf27d89648ce --- /dev/null +++ b/docs/architecture/adr-013-metrics.md @@ -0,0 +1,157 @@ +# ADR 013: Observability + +## Changelog + +- 20-01-2020: Initial Draft + +## Status + +Proposed + +## Context + +Telemetry is paramount into debugging and understanding what the application is doing and how it is +performing. We aim to expose metrics from modules and other core parts of the Cosmos SDK. + +In addition, we should aim to support multiple configurable sinks that an operator may choose from. +By default, when telemetry is enabled, the application should track and expose metrics that are +stored in-memory. The operator may choose to enable additional sinks, where we support only +[Prometheus](https://prometheus.io/) for now, as it's battle-tested, simple to setup, open source, +and is rich with ecosystem tooling. + +We must also aim to integrate metrics into the Cosmos SDK in the most seamless way possible such that +metrics may be added or removed at will and without much friction. To do this, we will use the +[go-metrics](https://github.com/armon/go-metrics) library. + +Finally, operators may enable telemetry along with specific configuration options. If enabled, metrics +will be exposed via `/metrics?format={text|prometheus}` via the API server. + +## Decision + +We will add an additional configuration block to `app.toml` that defines telemetry settings: + +```toml +############################################################################### +### Telemetry Configuration ### +############################################################################### + +[telemetry] + +# Prefixed with keys to separate services +service-name = {{ .Telemetry.ServiceName }} + +# Enabled enables the application telemetry functionality. When enabled, +# an in-memory sink is also enabled by default. Operators may also enabled +# other sinks such as Prometheus. +enabled = {{ .Telemetry.Enabled }} + +# Enable prefixing gauge values with hostname +enable-hostname = {{ .Telemetry.EnableHostname }} + +# Enable adding hostname to labels +enable-hostname-label = {{ .Telemetry.EnableHostnameLabel }} + +# Enable adding service to labels +enable-service-label = {{ .Telemetry.EnableServiceLabel }} + +# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. +prometheus-retention-time = {{ .Telemetry.PrometheusRetentionTime }} +``` + +The given configuration allows for two sinks -- in-memory and Prometheus. We create a `Metrics` +type that performs all the bootstrapping for the operator, so capturing metrics becomes seamless. + +```go +// Metrics defines a wrapper around application telemetry functionality. It allows +// metrics to be gathered at any point in time. When creating a Metrics object, +// internally, a global metrics is registered with a set of sinks as configured +// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a +// dump of formatted recent metrics will be sent to STDERR. +type Metrics struct { + memSink *metrics.InmemSink + prometheusEnabled bool +} + +// Gather collects all registered metrics and returns a GatherResponse where the +// metrics are encoded depending on the type. Metrics are either encoded via +// Prometheus or JSON if in-memory. +func (m *Metrics) Gather(format string) (GatherResponse, error) { + switch format { + case FormatPrometheus: + return m.gatherPrometheus() + + case FormatText: + return m.gatherGeneric() + + case FormatDefault: + return m.gatherGeneric() + + default: + return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) + } +} +``` + +In addition, `Metrics` allows us to gather the current set of metrics at any given point in time. An +operator may also choose to send a signal, SIGUSR1, to dump and print formatted metrics to STDERR. + +During an application's bootstrapping and construction phase, if `Telemetry.Enabled` is `true`, the +API server will create an instance of a reference to `Metrics` object and will register a metrics +handler accordingly. + +```go +func (s *Server) Start(cfg config.Config) error { + // ... + + if cfg.Telemetry.Enabled { + m, err := telemetry.New(cfg.Telemetry) + if err != nil { + return err + } + + s.metrics = m + s.registerMetrics() + } + + // ... +} + +func (s *Server) registerMetrics() { + metricsHandler := func(w http.ResponseWriter, r *http.Request) { + format := strings.TrimSpace(r.FormValue("format")) + + gr, err := s.metrics.Gather(format) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) + return + } + + w.Header().Set("Content-Type", gr.ContentType) + _, _ = w.Write(gr.Metrics) + } + + s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET") +} +``` + +Application developers may track counters, gauges, summaries, and key/value metrics. There is no +additional lifting required by modules to leverage profiling metrics. To do so, it's as simple as: + +```go +func (k BaseKeeper) MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error { + defer metrics.MeasureSince(time.Now(), "MintCoins") + // ... +} +``` + +## Consequences + +### Positive + +- Exposure into the performance and behavior of an application + +### Negative + +### Neutral + +## References diff --git a/docs/core/README.md b/docs/core/README.md index 6bfd2feb3fb7..61a47ff0f841 100644 --- a/docs/core/README.md +++ b/docs/core/README.md @@ -15,6 +15,7 @@ This repository contains reference documentation on the core conepts of the Cosm 5. [Store](./store.md) 6. [Encoding](./encoding.md) 7. [Events](./events.md) -8. [Object-Capabilities](./ocap.md) +8. [Telemetry](./telemetry.md) +9. [Object-Capabilities](./ocap.md) -After reading about the core concepts, head on to the [Building Modules documentation](../building-modules/README.md) to learn more about the process of building modules. \ No newline at end of file +After reading about the core concepts, head on to the [Building Modules documentation](../building-modules/README.md) to learn more about the process of building modules. diff --git a/docs/core/ocap.md b/docs/core/ocap.md index c3a6cf08ee53..adf77d0b0249 100644 --- a/docs/core/ocap.md +++ b/docs/core/ocap.md @@ -1,5 +1,5 @@ # Object-Capability Model @@ -72,4 +72,4 @@ gaia app. ## Next -Learn about [building modules](../building-modules/intro.md) {hide} \ No newline at end of file +Learn about [building modules](../building-modules/intro.md) {hide} diff --git a/docs/core/telemetry.md b/docs/core/telemetry.md new file mode 100644 index 000000000000..bcf64d2f8c35 --- /dev/null +++ b/docs/core/telemetry.md @@ -0,0 +1,118 @@ + + +# Telemetry + +The Cosmos SDK enables operators and developers to gain insight into the performance and behavior of +their application through the use of the `telemetry` package. The Cosmos SDK currently supports +enabling in-memory and prometheus as telemetry sinks. This allows the ability to query for and scrape +metrics from a single exposed API endpoint -- `/metrics?format={text|prometheus}`, the default being +`text`. + +If telemetry is enabled via configuration, a single global metrics collector is registered via the +[go-metrics](https://github.com/armon/go-metrics) library. This allows emitting and collecting +metrics through simple API calls. + +Example: + +```go +func EndBlocker(ctx sdk.Context, k keeper.Keeper) { + defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + + // ... +} +``` + +Developers may use the `telemetry` package directly, which provides wrappers around metric APIs +that include adding useful labels, or they must use the `go-metrics` library directly. It is preferable +to add as much context and adequate dimensionality to metrics as possible, so the `telemetry` package +is advised. Regardless of the package or method used, the Cosmos SDK supports the following metrics +types: + +* gauges +* summaries +* counters + +## Labels + +Certain components of modules will have their name automatically added as a label (e.g. `BeginBlock`). +Operators may also supply the application with a global set of labels that will be applied to all +metrics emitted using the `telemetry` package (e.g. chain-id). Global labels are supplied as a list +of [name, value] tuples. + +Example: + +```toml +global-labels = [ + ["chain_id", "chain-OfXo4V"], +] +``` + +## Cardinality + +Cardinality is key, specifically label and key cardinality. Cardinality is how many unique values of +something there are. So there is naturally a tradeoff between granularity and how much stress is put +on the telemetry sink in terms of indexing, scrape, and query performance. + +Developers should take care to support metrics with enough dimensionality and granularity to be +useful, but not increase the cardinality beyond the sink's limits. A general rule of thumb is to not +exceed a cardinality of 10. + +Consider the following examples with enough granularity and adequate cardinality: + +* begin/end blocker time +* tx gas used +* block gas used +* amount of tokens minted +* amount of accounts created + +The following examples expose too much cardinality and may not even prove to be useful: + +* transfers between accounts with amount +* voting/deposit amount from unique addresses + +## Supported Metrics + +| Metric | Description | Unit | Type | +| :------------------------------ | :------------------------------------------------------------------------------------- | :----------- | :------ | +| `tx_count` | Total number of txs processed via `DeliverTx` | tx | counter | +| `tx_successful` | Total number of successful txs processed via `DeliverTx` | tx | counter | +| `tx_failed` | Total number of failed txs processed via `DeliverTx` | tx | counter | +| `tx_gas_used` | The total amount of gas used by a tx | gas | gauge | +| `tx_gas_wanted` | The total amount of gas requested by a tx | gas | gauge | +| `tx_msg_send` | The total amount of tokens sent in a `MsgSend` (per denom) | token | gauge | +| `tx_msg_withdraw_reward` | The total amount of tokens withdrawn in a `MsgWithdrawDelegatorReward` (per denom) | token | gauge | +| `tx_msg_withdraw_commission` | The total amount of tokens withdrawn in a `MsgWithdrawValidatorCommission` (per denom) | token | gauge | +| `tx_msg_delegate` | The total amount of tokens delegated in a `MsgDelegate` | consensus power | gauge | +| `tx_msg_begin_unbonding` | The total amount of tokens undelegated in a `MsgUndelegate` | consensus power | gauge | +| `tx_msg_begin_begin_redelegate` | The total amount of tokens redelegated in a `MsgBeginRedelegate` | consensus power | gauge | +| `new_account` | Total number of new accounts created | account | counter | +| `gov_proposal` | Total number of governance proposals | proposal | counter | +| `gov_vote` | Total number of governance votes for a proposal | vote | counter | +| `gov_deposit` | Total number of governance deposits for a proposal | deposit | counter | +| `staking_delegate` | Total number of delegations | delegation | counter | +| `staking_undelegate` | Total number of undelegations | undelegation | counter | +| `staking_redelegate` | Total number of redelegations | redelegation | counter | +| `abci_check_tx` | Duration of ABCI `CheckTx` | ms | summary | +| `abci_deliver_tx` | Duration of ABCI `DeliverTx` | ms | summary | +| `abci_commit` | Duration of ABCI `Commit` | ms | summary | +| `abci_query` | Duration of ABCI `Query` | ms | summary | +| `abci_begin_block` | Duration of ABCI `BeginBlock` | ms | summary | +| `abci_end_block` | Duration of ABCI `EndBlock` | ms | summary | +| `begin_blocker` | Duration of `BeginBlock` for a given module | ms | summary | +| `end_blocker` | Duration of `EndBlock` for a given module | ms | summary | +| `store_iavl_get` | Duration of an IAVL `Store#Get` call | ms | summary | +| `store_iavl_set` | Duration of an IAVL `Store#Set` call | ms | summary | +| `store_iavl_has` | Duration of an IAVL `Store#Has` call | ms | summary | +| `store_iavl_delete` | Duration of an IAVL `Store#Delete` call | ms | summary | +| `store_iavl_commit` | Duration of an IAVL `Store#Commit` call | ms | summary | +| `store_iavl_query` | Duration of an IAVL `Store#Query` call | ms | summary | +| `store_gaskv_get` | Duration of a GasKV `Store#Get` call | ms | summary | +| `store_gaskv_set` | Duration of a GasKV `Store#Set` call | ms | summary | +| `store_gaskv_has` | Duration of a GasKV `Store#Has` call | ms | summary | +| `store_gaskv_delete` | Duration of a GasKV `Store#Delete` call | ms | summary | +| `store_cachekv_get` | Duration of a CacheKV `Store#Get` call | ms | summary | +| `store_cachekv_set` | Duration of a CacheKV `Store#Set` call | ms | summary | +| `store_cachekv_write` | Duration of a CacheKV `Store#Write` call | ms | summary | +| `store_cachekv_delete` | Duration of a CacheKV `Store#Delete` call | ms | summary | diff --git a/go.mod b/go.mod index 659186cada44..50850c96774c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ go 1.13 module github.com/cosmos/cosmos-sdk require ( - github.com/99designs/keyring v1.1.3 + github.com/99designs/keyring v1.1.5 + github.com/armon/go-metrics v0.3.3 github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d github.com/bgentry/speakeasy v0.1.0 github.com/btcsuite/btcd v0.20.1-beta @@ -17,8 +18,10 @@ require ( github.com/mattn/go-isatty v0.0.12 github.com/pelletier/go-toml v1.6.0 github.com/pkg/errors v0.9.1 - github.com/rakyll/statik v0.1.6 - github.com/spf13/afero v1.2.1 // indirect + github.com/prometheus/client_golang v1.6.0 + github.com/prometheus/common v0.10.0 + github.com/rakyll/statik v0.1.7 + github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v1.0.0 github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index a6c0d7e27218..674266af0348 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,13 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY= -github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= +github.com/99designs/keyring v1.1.5 h1:wLv7QyzYpFIyMSwOADq1CLTF9KbjbBfcnfmOGJ64aO4= +github.com/99designs/keyring v1.1.5/go.mod h1:7hsVvt2qXgtadGevGJ4ujg+u8m6SpJ5TpHqTozIPqf0= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -26,7 +27,10 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.3 h1:a9F4rlj7EWWrbj7BYw8J8+x+ZZkJeqzNyRk8hdPF+ro= +github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -61,6 +65,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -203,14 +209,18 @@ github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIv github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -289,6 +299,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -316,7 +328,10 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= @@ -337,8 +352,11 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A= +github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -353,15 +371,19 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= +github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= -github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= @@ -386,8 +408,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= -github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -435,6 +457,7 @@ github.com/tendermint/tm-db v0.5.1 h1:H9HDq8UEA7Eeg13kdYckkgwwkQLBnJGgX4PgLJRhie github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -523,11 +546,14 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/server/api/server.go b/server/api/server.go new file mode 100644 index 000000000000..c9c0c6f9b6b4 --- /dev/null +++ b/server/api/server.go @@ -0,0 +1,109 @@ +package api + +import ( + "fmt" + "net" + "net/http" + "os" + "strings" + "time" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "github.com/rakyll/statik/fs" + "github.com/tendermint/tendermint/libs/log" + tmrpcserver "github.com/tendermint/tendermint/rpc/jsonrpc/server" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/server/config" + "github.com/cosmos/cosmos-sdk/telemetry" + "github.com/cosmos/cosmos-sdk/types/rest" + + // unnamed import of statik for swagger UI support + _ "github.com/cosmos/cosmos-sdk/client/docs/statik" +) + +// Server defines the server's API interface. +type Server struct { + Router *mux.Router + ClientCtx context.CLIContext + + logger log.Logger + metrics *telemetry.Metrics + listener net.Listener +} + +func New(clientCtx context.CLIContext) *Server { + return &Server{ + Router: mux.NewRouter(), + ClientCtx: clientCtx, + logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "api-server"), + } +} + +// Start starts the API server. Internally, the API server leverages Tendermint's +// JSON RPC server. Configuration options are provided via config.APIConfig +// and are delegated to the Tendermint JSON RPC server. The process is +// non-blocking, so an external signal handler must be used. +func (s *Server) Start(cfg config.Config) error { + if cfg.API.Swagger { + s.registerSwaggerUI() + } + + if cfg.Telemetry.Enabled { + m, err := telemetry.New(cfg.Telemetry) + if err != nil { + return err + } + + s.metrics = m + s.registerMetrics() + } + + tmCfg := tmrpcserver.DefaultConfig() + tmCfg.MaxOpenConnections = int(cfg.API.MaxOpenConnections) + tmCfg.ReadTimeout = time.Duration(cfg.API.RPCReadTimeout) * time.Second + tmCfg.WriteTimeout = time.Duration(cfg.API.RPCWriteTimeout) * time.Second + tmCfg.MaxBodyBytes = int64(cfg.API.RPCMaxBodyBytes) + + listener, err := tmrpcserver.Listen(cfg.API.Address, tmCfg) + if err != nil { + return err + } + + s.listener = listener + var h http.Handler = s.Router + + if cfg.API.EnableUnsafeCORS { + return tmrpcserver.Serve(s.listener, handlers.CORS()(h), s.logger, tmCfg) + } + + return tmrpcserver.Serve(s.listener, s.Router, s.logger, tmCfg) +} + +func (s *Server) registerSwaggerUI() { + statikFS, err := fs.New() + if err != nil { + panic(err) + } + + staticServer := http.FileServer(statikFS) + s.Router.PathPrefix("/").Handler(staticServer) +} + +func (s *Server) registerMetrics() { + metricsHandler := func(w http.ResponseWriter, r *http.Request) { + format := strings.TrimSpace(r.FormValue("format")) + + gr, err := s.metrics.Gather(format) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to gather metrics: %s", err)) + return + } + + w.Header().Set("Content-Type", gr.ContentType) + _, _ = w.Write(gr.Metrics) + } + + s.Router.HandleFunc("/metrics", metricsHandler).Methods("GET") +} diff --git a/server/config/config.go b/server/config/config.go index f046bbe56535..56c425b27360 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -4,7 +4,11 @@ import ( "fmt" "strings" + "github.com/spf13/viper" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -41,9 +45,44 @@ type BaseConfig struct { InterBlockCache bool `mapstructure:"inter-block-cache"` } +// APIConfig defines the API listener configuration. +type APIConfig struct { + // Enable defines if the API server should be enabled. + Enable bool `mapstructure:"enable"` + + // Swagger defines if swagger documentation should automatically be registered. + Swagger bool `mapstructure:"swagger"` + + // EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk) + EnableUnsafeCORS bool `mapstructure:"enabled-unsafe-cors"` + + // Address defines the API server to listen on + Address string `mapstructure:"address"` + + // MaxOpenConnections defines the number of maximum open connections + MaxOpenConnections uint `mapstructure:"max-open-connections"` + + // RPCReadTimeout defines the Tendermint RPC read timeout (in seconds) + RPCReadTimeout uint `mapstructure:"rpc-read-timeout"` + + // RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds) + RPCWriteTimeout uint `mapstructure:"rpc-write-timeout"` + + // RPCMaxBodyBytes defines the Tendermint maximum response body (in bytes) + RPCMaxBodyBytes uint `mapstructure:"rpc-max-body-bytes"` + + // TODO: TLS/Proxy configuration. + // + // Ref: https://github.com/cosmos/cosmos-sdk/issues/6420 +} + // Config defines the server's top level configuration type Config struct { BaseConfig `mapstructure:",squash"` + + // Telemetry defines the application telemetry configuration + Telemetry telemetry.Config `mapstructure:"telemetry"` + API APIConfig `mapstructure:"api"` } // SetMinGasPrices sets the validator's minimum gas prices. @@ -84,5 +123,59 @@ func DefaultConfig() *Config { PruningKeepEvery: "0", PruningInterval: "0", }, + Telemetry: telemetry.Config{ + Enabled: false, + GlobalLabels: [][]string{}, + }, + API: APIConfig{ + Enable: false, + Swagger: false, + Address: "tcp://0.0.0.0:1317", + MaxOpenConnections: 1000, + RPCReadTimeout: 10, + RPCMaxBodyBytes: 1000000, + }, + } +} + +// GetConfig returns a fully parsed Config object. +func GetConfig() Config { + globalLabelsRaw := viper.Get("telemetry.global-labels").([]interface{}) + globalLabels := make([][]string, 0, len(globalLabelsRaw)) + for _, glr := range globalLabelsRaw { + labelsRaw := glr.([]interface{}) + if len(labelsRaw) == 2 { + globalLabels = append(globalLabels, []string{labelsRaw[0].(string), labelsRaw[1].(string)}) + } + } + + return Config{ + BaseConfig: BaseConfig{ + MinGasPrices: viper.GetString("minimum-gas-prices"), + InterBlockCache: viper.GetBool("inter-block-cache"), + Pruning: viper.GetString("pruning"), + PruningKeepEvery: viper.GetString("pruning-keep-every"), + PruningKeepRecent: viper.GetString("pruning-keep-recent"), + PruningInterval: viper.GetString("pruning-keep-interval"), + HaltHeight: viper.GetUint64("halt-height"), + HaltTime: viper.GetUint64("halt-time"), + }, + Telemetry: telemetry.Config{ + ServiceName: viper.GetString("telemetry.service-name"), + Enabled: viper.GetBool("telemetry.enabled"), + EnableHostname: viper.GetBool("telemetry.enable-hostname"), + EnableHostnameLabel: viper.GetBool("telemetry.enable-hostname-label"), + EnableServiceLabel: viper.GetBool("telemetry.enable-service-label"), + PrometheusRetentionTime: viper.GetInt64("telemetry.prometheus-retention-time"), + GlobalLabels: globalLabels, + }, + API: APIConfig{ + Address: viper.GetString("api.address"), + MaxOpenConnections: viper.GetUint("api.max-open-connections"), + RPCReadTimeout: viper.GetUint("api.rpc-read-timeout"), + RPCWriteTimeout: viper.GetUint("api.rpc-write-timeout"), + RPCMaxBodyBytes: viper.GetUint("api.rpc-max-body-bytes"), + EnableUnsafeCORS: viper.GetBool("api.enabled-unsafe-cors"), + }, } } diff --git a/server/config/toml.go b/server/config/toml.go index 6fb3f166f976..4521257f4c16 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -11,7 +11,9 @@ import ( const defaultConfigTemplate = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml -##### main base config options ##### +############################################################################### +### Base Configuration ### +############################################################################### # The minimum gas prices a validator is willing to accept for processing a # transaction. A transaction's fees must meet the minimum of any denomination @@ -44,6 +46,71 @@ halt-time = {{ .BaseConfig.HaltTime }} # InterBlockCache enables inter-block caching. inter-block-cache = {{ .BaseConfig.InterBlockCache }} + +############################################################################### +### Telemetry Configuration ### +############################################################################### + +[telemetry] + +# Prefixed with keys to separate services +service-name = "{{ .Telemetry.ServiceName }}" + +# Enabled enables the application telemetry functionality. When enabled, +# an in-memory sink is also enabled by default. Operators may also enabled +# other sinks such as Prometheus. +enabled = {{ .Telemetry.Enabled }} + +# Enable prefixing gauge values with hostname +enable-hostname = {{ .Telemetry.EnableHostname }} + +# Enable adding hostname to labels +enable-hostname-label = {{ .Telemetry.EnableHostnameLabel }} + +# Enable adding service to labels +enable-service-label = {{ .Telemetry.EnableServiceLabel }} + +# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. +prometheus-retention-time = {{ .Telemetry.PrometheusRetentionTime }} + +# GlobalLabels defines a global set of name/value label tuples applied to all +# metrics emitted using the wrapper functions defined in telemetry package. +# +# Example: +# [["chain_id", "cosmoshub-1"]] +global-labels = [{{ range $k, $v := .Telemetry.GlobalLabels }} + ["{{index $v 0 }}", "{{ index $v 1}}"],{{ end }} +] + +############################################################################### +### API Configuration ### +############################################################################### + +[api] + +# Enable defines if the API server should be enabled. +enable = {{ .API.Enable }} + +# Swagger defines if swagger documentation should automatically be registered. +swagger = {{ .API.Swagger }} + +# Address defines the API server to listen on +address = "{{ .API.Address }}" + +# MaxOpenConnections defines the number of maximum open connections +max-open-connections = {{ .API.MaxOpenConnections }} + +# RPCReadTimeout defines the Tendermint RPC read timeout (in seconds) +rpc-read-timeout = {{ .API.RPCReadTimeout }} + +# RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds) +rpc-write-timeout = {{ .API.RPCWriteTimeout }} + +# RPCMaxBodyBytes defines the Tendermint maximum response body (in bytes) +rpc-max-body-bytes = {{ .API.RPCMaxBodyBytes }} + +# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk) +enabled-unsafe-cors = {{ .API.EnableUnsafeCORS }} ` var configTemplate *template.Template diff --git a/server/constructors.go b/server/constructors.go index 341cb34eba27..06b28ac37ea6 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -11,13 +11,23 @@ import ( tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/cosmos/cosmos-sdk/server/api" sdk "github.com/cosmos/cosmos-sdk/types" ) type ( + // Application defines an application interface that wraps abci.Application. + // The interface defines the necessary contracts to be implemented in order + // to fully bootstrap and start an application. + Application interface { + abci.Application + + RegisterAPIRoutes(*api.Server) + } + // AppCreator is a function that allows us to lazily initialize an // application using various configurations. - AppCreator func(log.Logger, dbm.DB, io.Writer) abci.Application + AppCreator func(log.Logger, dbm.DB, io.Writer) Application // AppExporter is a function that dumps all app state to // JSON-serializable structure and returns the current validator set. diff --git a/server/start.go b/server/start.go index ff1725b671fb..b2b61e00bb26 100644 --- a/server/start.go +++ b/server/start.go @@ -16,7 +16,12 @@ import ( "github.com/tendermint/tendermint/p2p" pvm "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/rpc/client/local" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" storetypes "github.com/cosmos/cosmos-sdk/store/types" ) @@ -41,7 +46,7 @@ const ( // StartCmd runs the service passed in, either stand-alone or in-process with // Tendermint. -func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { +func StartCmd(ctx *Context, cdc *codec.Codec, appCreator AppCreator) *cobra.Command { cmd := &cobra.Command{ Use: "start", Short: "Run the full node", @@ -79,7 +84,7 @@ which accepts a path for the resulting pprof file. ctx.Logger.Info("starting ABCI with Tendermint") - _, err := startInProcess(ctx, appCreator) + err := startInProcess(ctx, cdc, appCreator) return err }, } @@ -154,45 +159,72 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error { select {} } -func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { +func startInProcess(ctx *Context, cdc *codec.Codec, appCreator AppCreator) error { cfg := ctx.Config home := cfg.RootDir traceWriterFile := viper.GetString(flagTraceStore) db, err := openDB(home) if err != nil { - return nil, err + return err } traceWriter, err := openTraceWriter(traceWriterFile) if err != nil { - return nil, err + return err } app := appCreator(ctx.Logger, db, traceWriter) nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) if err != nil { - return nil, err + return err } + genDocProvider := node.DefaultGenesisDocProviderFunc(cfg) + // create & start tendermint node tmNode, err := node.NewNode( cfg, pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()), nodeKey, proxy.NewLocalClientCreator(app), - node.DefaultGenesisDocProviderFunc(cfg), + genDocProvider, node.DefaultDBProvider, node.DefaultMetricsProvider(cfg.Instrumentation), ctx.Logger.With("module", "node"), ) if err != nil { - return nil, err + return err } if err := tmNode.Start(); err != nil { - return nil, err + return err + } + + if viper.GetBool("api.enable") { + genDoc, err := genDocProvider() + if err != nil { + return err + } + + // TODO: Since this is running in process, do we need to provide a verifier + // and set TrustNode=false? If so, we need to add additional logic that + // waits for a block to be committed first before starting the API server. + ctx := context.CLIContext{ + HomeDir: home, + }. + WithChainID(genDoc.ChainID). + WithCodec(cdc). + WithClient(local.New(tmNode)). + WithTrustNode(true) + + apiSrv := api.New(ctx) + app.RegisterAPIRoutes(apiSrv) + + if err := apiSrv.Start(config.GetConfig()); err != nil { + return err + } } var cpuProfileCleanup func() @@ -200,12 +232,12 @@ func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) { if cpuProfile := viper.GetString(flagCPUProfile); cpuProfile != "" { f, err := os.Create(cpuProfile) if err != nil { - return nil, err + return err } ctx.Logger.Info("starting CPU profiler", "profile", cpuProfile) if err := pprof.StartCPUProfile(f); err != nil { - return nil, err + return err } cpuProfileCleanup = func() { diff --git a/server/util.go b/server/util.go index 656b4c56dbc1..8ad71ccbcead 100644 --- a/server/util.go +++ b/server/util.go @@ -117,9 +117,8 @@ func interceptLoadConfig() (conf *cfg.Config, err error) { // add server commands func AddCommands( - ctx *Context, cdc *codec.Codec, - rootCmd *cobra.Command, - appCreator AppCreator, appExport AppExporter) { + ctx *Context, cdc *codec.Codec, rootCmd *cobra.Command, appCreator AppCreator, appExport AppExporter, +) { rootCmd.PersistentFlags().String("log_level", ctx.Config.LogLevel, "Log level") @@ -137,7 +136,7 @@ func AddCommands( ) rootCmd.AddCommand( - StartCmd(ctx, appCreator), + StartCmd(ctx, cdc, appCreator), UnsafeResetAllCmd(ctx), flags.LineBreak, tendermintCmd, diff --git a/store/cachekv/store.go b/store/cachekv/store.go index fa9b42d60002..2a752f8a2c88 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -6,12 +6,14 @@ import ( "io" "sort" "sync" + "time" tmkv "github.com/tendermint/tendermint/libs/kv" dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/store/tracekv" "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" ) // If value is nil but deleted is false, it means the parent doesn't have the @@ -51,6 +53,7 @@ func (store *Store) GetStoreType() types.StoreType { func (store *Store) Get(key []byte) (value []byte) { store.mtx.Lock() defer store.mtx.Unlock() + defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "get") types.AssertValidKey(key) @@ -69,6 +72,7 @@ func (store *Store) Get(key []byte) (value []byte) { func (store *Store) Set(key []byte, value []byte) { store.mtx.Lock() defer store.mtx.Unlock() + defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "set") types.AssertValidKey(key) types.AssertValidValue(value) @@ -86,9 +90,9 @@ func (store *Store) Has(key []byte) bool { func (store *Store) Delete(key []byte) { store.mtx.Lock() defer store.mtx.Unlock() + defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "delete") types.AssertValidKey(key) - store.setCacheValue(key, nil, true, true) } @@ -96,6 +100,7 @@ func (store *Store) Delete(key []byte) { func (store *Store) Write() { store.mtx.Lock() defer store.mtx.Unlock() + defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "write") // We need a copy of all of the keys. // Not the best, but probably not a bottleneck depending. diff --git a/store/gaskv/store.go b/store/gaskv/store.go index 3b611a4ed114..4e89fd083966 100644 --- a/store/gaskv/store.go +++ b/store/gaskv/store.go @@ -2,8 +2,10 @@ package gaskv import ( "io" + "time" "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" ) var _ types.KVStore = &Store{} @@ -34,6 +36,8 @@ func (gs *Store) GetStoreType() types.StoreType { // Implements KVStore. func (gs *Store) Get(key []byte) (value []byte) { + defer telemetry.MeasureSince(time.Now(), "store", "gaskv", "get") + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, types.GasReadCostFlatDesc) value = gs.parent.Get(key) @@ -45,6 +49,8 @@ func (gs *Store) Get(key []byte) (value []byte) { // Implements KVStore. func (gs *Store) Set(key []byte, value []byte) { + defer telemetry.MeasureSince(time.Now(), "store", "gaskv", "set") + types.AssertValidValue(value) gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, types.GasWriteCostFlatDesc) // TODO overflow-safe math? @@ -54,12 +60,14 @@ func (gs *Store) Set(key []byte, value []byte) { // Implements KVStore. func (gs *Store) Has(key []byte) bool { + defer telemetry.MeasureSince(time.Now(), "store", "gaskv", "has") gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc) return gs.parent.Has(key) } // Implements KVStore. func (gs *Store) Delete(key []byte) { + defer telemetry.MeasureSince(time.Now(), "store", "gaskv", "delete") // charge gas to prevent certain attack vectors even though space is being freed gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, types.GasDeleteDesc) gs.parent.Delete(key) diff --git a/store/iavl/store.go b/store/iavl/store.go index f5af4099bc0b..c951a7e94475 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -3,6 +3,7 @@ package iavl import ( "io" "sync" + "time" "github.com/tendermint/iavl" abci "github.com/tendermint/tendermint/abci/types" @@ -13,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/cachekv" "github.com/cosmos/cosmos-sdk/store/tracekv" "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -91,6 +93,8 @@ func (st *Store) GetImmutable(version int64) (*Store, error) { // Commit commits the current store state and returns a CommitID with the new // version and hash. func (st *Store) Commit() types.CommitID { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "commit") + hash, version, err := st.tree.SaveVersion() if err != nil { panic(err) @@ -138,23 +142,27 @@ func (st *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.Ca // Implements types.KVStore. func (st *Store) Set(key, value []byte) { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "set") types.AssertValidValue(value) st.tree.Set(key, value) } // Implements types.KVStore. func (st *Store) Get(key []byte) []byte { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "get") _, value := st.tree.Get(key) return value } // Implements types.KVStore. func (st *Store) Has(key []byte) (exists bool) { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "has") return st.tree.Has(key) } // Implements types.KVStore. func (st *Store) Delete(key []byte) { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "delete") st.tree.Remove(key) } @@ -215,6 +223,8 @@ func getHeight(tree Tree, req abci.RequestQuery) int64 { // if you care to have the latest data to see a tx results, you must // explicitly set the height you want to see func (st *Store) Query(req abci.RequestQuery) (res abci.ResponseQuery) { + defer telemetry.MeasureSince(time.Now(), "store", "iavl", "query") + if len(req.Data) == 0 { return sdkerrors.QueryResult(sdkerrors.Wrap(sdkerrors.ErrTxDecode, "query cannot be zero length")) } diff --git a/telemetry/metrics.go b/telemetry/metrics.go new file mode 100644 index 000000000000..f402e929090f --- /dev/null +++ b/telemetry/metrics.go @@ -0,0 +1,171 @@ +package telemetry + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "github.com/armon/go-metrics" + metricsprom "github.com/armon/go-metrics/prometheus" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/expfmt" +) + +// globalLabels defines the set of global labels that will be applied to all +// metrics emitted using the telemetry package function wrappers. +var globalLabels = []metrics.Label{} + +// Metrics supported format types. +const ( + FormatDefault = "" + FormatPrometheus = "prometheus" + FormatText = "text" +) + +// Config defines the configuration options for application telemetry. +type Config struct { + // Prefixed with keys to separate services + ServiceName string `mapstructure:"service-name"` + + // Enabled enables the application telemetry functionality. When enabled, + // an in-memory sink is also enabled by default. Operators may also enabled + // other sinks such as Prometheus. + Enabled bool `mapstructure:"enabled"` + + // Enable prefixing gauge values with hostname + EnableHostname bool `mapstructure:"enable-hostname"` + + // Enable adding hostname to labels + EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` + + // Enable adding service to labels + EnableServiceLabel bool `mapstructure:"enable-service-label"` + + // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. + // It defines the retention duration in seconds. + PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` + + // GlobalLabels defines a global set of name/value label tuples applied to all + // metrics emitted using the wrapper functions defined in telemetry package. + // + // Example: + // [["chain_id", "cosmoshub-1"]] + GlobalLabels [][]string `mapstructure:"global-labels"` +} + +// Metrics defines a wrapper around application telemetry functionality. It allows +// metrics to be gathered at any point in time. When creating a Metrics object, +// internally, a global metrics is registered with a set of sinks as configured +// by the operator. In addition to the sinks, when a process gets a SIGUSR1, a +// dump of formatted recent metrics will be sent to STDERR. +type Metrics struct { + memSink *metrics.InmemSink + prometheusEnabled bool +} + +type GatherResponse struct { + Metrics []byte + ContentType string +} + +func New(cfg Config) (*Metrics, error) { + if !cfg.Enabled { + return nil, nil + } + + if numGlobalLables := len(cfg.GlobalLabels); numGlobalLables > 0 { + parsedGlobalLabels := make([]metrics.Label, numGlobalLables) + for i, gl := range cfg.GlobalLabels { + parsedGlobalLabels[i] = NewLabel(gl[0], gl[1]) + } + + globalLabels = parsedGlobalLabels + } + + metricsConf := metrics.DefaultConfig(cfg.ServiceName) + metricsConf.EnableHostname = cfg.EnableHostname + metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel + + memSink := metrics.NewInmemSink(10*time.Second, time.Minute) + metrics.DefaultInmemSignal(memSink) + + m := &Metrics{memSink: memSink} + fanout := metrics.FanoutSink{memSink} + + if cfg.PrometheusRetentionTime > 0 { + m.prometheusEnabled = true + prometheusOpts := metricsprom.PrometheusOpts{ + Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second, + } + + promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts) + if err != nil { + return nil, err + } + + fanout = append(fanout, promSink) + } + + if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil { + return nil, err + } + + return m, nil +} + +// Gather collects all registered metrics and returns a GatherResponse where the +// metrics are encoded depending on the type. Metrics are either encoded via +// Prometheus or JSON if in-memory. +func (m *Metrics) Gather(format string) (GatherResponse, error) { + switch format { + case FormatPrometheus: + return m.gatherPrometheus() + + case FormatText: + return m.gatherGeneric() + + case FormatDefault: + return m.gatherGeneric() + + default: + return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) + } +} + +func (m *Metrics) gatherPrometheus() (GatherResponse, error) { + if !m.prometheusEnabled { + return GatherResponse{}, fmt.Errorf("prometheus metrics are not enabled") + } + + metricsFamilies, err := prometheus.DefaultGatherer.Gather() + if err != nil { + return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err) + } + + buf := &bytes.Buffer{} + defer buf.Reset() + + e := expfmt.NewEncoder(buf, expfmt.FmtText) + for _, mf := range metricsFamilies { + if err := e.Encode(mf); err != nil { + return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err) + } + } + + return GatherResponse{ContentType: string(expfmt.FmtText), Metrics: buf.Bytes()}, nil +} + +func (m *Metrics) gatherGeneric() (GatherResponse, error) { + summary, err := m.memSink.DisplayMetrics(nil, nil) + if err != nil { + return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err) + } + + content, err := json.Marshal(summary) + if err != nil { + return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err) + } + + return GatherResponse{ContentType: "application/json", Metrics: content}, nil +} diff --git a/telemetry/metrics_test.go b/telemetry/metrics_test.go new file mode 100644 index 000000000000..aa4c934bfb58 --- /dev/null +++ b/telemetry/metrics_test.go @@ -0,0 +1,76 @@ +package telemetry + +import ( + "encoding/json" + "strings" + "testing" + "time" + + "github.com/armon/go-metrics" + "github.com/prometheus/common/expfmt" + "github.com/stretchr/testify/require" +) + +func TestMetrics_Disabled(t *testing.T) { + m, err := New(Config{Enabled: false}) + require.Nil(t, m) + require.Nil(t, err) +} + +func TestMetrics_InMem(t *testing.T) { + m, err := New(Config{ + Enabled: true, + EnableHostname: false, + ServiceName: "test", + }) + require.NoError(t, err) + require.NotNil(t, m) + + emitMetrics() + + gr, err := m.Gather(FormatText) + require.NoError(t, err) + require.Equal(t, gr.ContentType, "application/json") + + jsonMetrics := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(gr.Metrics, &jsonMetrics)) + + counters := jsonMetrics["Counters"].([]interface{}) + require.Equal(t, counters[0].(map[string]interface{})["Count"].(float64), 10.0) + require.Equal(t, counters[0].(map[string]interface{})["Name"].(string), "test.dummy_counter") +} + +func TestMetrics_Prom(t *testing.T) { + m, err := New(Config{ + Enabled: true, + EnableHostname: false, + ServiceName: "test", + PrometheusRetentionTime: 60, + EnableHostnameLabel: false, + }) + require.NoError(t, err) + require.NotNil(t, m) + require.True(t, m.prometheusEnabled) + + emitMetrics() + + gr, err := m.Gather(FormatPrometheus) + require.NoError(t, err) + require.Equal(t, gr.ContentType, string(expfmt.FmtText)) + + require.True(t, strings.Contains(string(gr.Metrics), "test_dummy_counter 30")) +} + +func emitMetrics() { + ticker := time.NewTicker(time.Second) + timeout := time.After(30 * time.Second) + + for { + select { + case <-ticker.C: + metrics.IncrCounter([]string{"dummy_counter"}, 1.0) + case <-timeout: + return + } + } +} diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go new file mode 100644 index 000000000000..56584e296e7b --- /dev/null +++ b/telemetry/wrapper.go @@ -0,0 +1,70 @@ +package telemetry + +import ( + "time" + + "github.com/armon/go-metrics" +) + +// Common metric key constants +const ( + MetricKeyBeginBlocker = "begin_blocker" + MetricKeyEndBlocker = "end_blocker" + MetricLabelNameModule = "module" +) + +func NewLabel(name, value string) metrics.Label { + return metrics.Label{Name: name, Value: value} +} + +// ModuleMeasureSince provides a short hand method for emitting a time measure +// metric for a module with a given set of keys. If any global labels are defined, +// they will be added to the module label. +func ModuleMeasureSince(module string, start time.Time, keys ...string) { + metrics.MeasureSinceWithLabels( + keys, + start.UTC(), + append([]metrics.Label{NewLabel(MetricLabelNameModule, module)}, globalLabels...), + ) +} + +// ModuleSetGauge provides a short hand method for emitting a gauge metric for a +// module with a given set of keys. If any global labels are defined, they will +// be added to the module label. +func ModuleSetGauge(module string, val float32, keys ...string) { + metrics.SetGaugeWithLabels( + keys, + val, + append([]metrics.Label{NewLabel(MetricLabelNameModule, module)}, globalLabels...), + ) +} + +// IncrCounter provides a wrapper functionality for emitting a counter metric with +// global labels (if any). +func IncrCounter(val float32, keys ...string) { + metrics.IncrCounterWithLabels(keys, val, globalLabels) +} + +// IncrCounterWithLabels provides a wrapper functionality for emitting a counter +// metric with global labels (if any) along with the provided labels. +func IncrCounterWithLabels(keys []string, val float32, labels []metrics.Label) { + metrics.IncrCounterWithLabels(keys, val, append(labels, globalLabels...)) +} + +// SetGauge provides a wrapper functionality for emitting a gauge metric with +// global labels (if any). +func SetGauge(val float32, keys ...string) { + metrics.SetGaugeWithLabels(keys, val, globalLabels) +} + +// SetGaugeWithLabels provides a wrapper functionality for emitting a gauge +// metric with global labels (if any) along with the provided labels. +func SetGaugeWithLabels(keys []string, val float32, labels []metrics.Label) { + metrics.SetGaugeWithLabels(keys, val, append(labels, globalLabels...)) +} + +// MeasureSince provides a wrapper functionality for emitting a a time measure +// metric with global labels (if any). +func MeasureSince(start time.Time, keys ...string) { + metrics.MeasureSinceWithLabels(keys, start.UTC(), globalLabels) +} diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go index 3d81808b2d0c..e7d8bb0ce2ae 100644 --- a/x/auth/ante/fee.go +++ b/x/auth/ante/fee.go @@ -40,6 +40,7 @@ func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b if !ok { return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") } + feeCoins := feeTx.GetFee() gas := feeTx.GetGas() diff --git a/x/bank/handler.go b/x/bank/handler.go index 3c83ec4c2af4..6439b6a21d24 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -1,6 +1,9 @@ package bank import ( + "github.com/armon/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/bank/internal/keeper" @@ -40,6 +43,24 @@ func handleMsgSend(ctx sdk.Context, k keeper.Keeper, msg types.MsgSend) (*sdk.Re return nil, err } + defer func() { + for _, a := range msg.Amount { + var amount int64 + denom := a.Denom + if a.Denom == "stake" { + amount = sdk.TokensToConsensusPower(a.Amount) + denom = "consensus_power" + } else { + amount = a.Amount.Int64() + } + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "send"}, + float32(amount), + []metrics.Label{telemetry.NewLabel("denom", denom)}, + ) + } + }() + ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, diff --git a/x/crisis/abci.go b/x/crisis/abci.go index 3e5485979980..bcfa60e88549 100644 --- a/x/crisis/abci.go +++ b/x/crisis/abci.go @@ -1,11 +1,16 @@ package crisis import ( + "time" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" ) // check all registered invariants func EndBlocker(ctx sdk.Context, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + if k.InvCheckPeriod() == 0 || ctx.BlockHeight()%int64(k.InvCheckPeriod()) != 0 { // skip running the invariant check return diff --git a/x/distribution/abci.go b/x/distribution/abci.go index 44a2286cad22..e7adc88b1c10 100644 --- a/x/distribution/abci.go +++ b/x/distribution/abci.go @@ -1,15 +1,19 @@ package distribution import ( + "time" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/distribution/keeper" ) // BeginBlocker sets the proposer for determining distribution during endblock // and distribute rewards for the previous block -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + // determine the total power signing the block var previousTotalPower, sumPreviousPrecommitPower int64 for _, voteInfo := range req.LastCommitInfo.GetVotes() { diff --git a/x/distribution/handler.go b/x/distribution/handler.go index 642b9e0d89a1..9d69cf8c90cc 100644 --- a/x/distribution/handler.go +++ b/x/distribution/handler.go @@ -1,6 +1,9 @@ package distribution import ( + "github.com/armon/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/distribution/keeper" @@ -51,11 +54,29 @@ func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAdd } func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) (*sdk.Result, error) { - _, err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddress, msg.ValidatorAddress) + amount, err := k.WithdrawDelegationRewards(ctx, msg.DelegatorAddress, msg.ValidatorAddress) if err != nil { return nil, err } + defer func() { + for _, a := range amount { + var amountInt64 int64 + denom := a.Denom + if a.Denom == "stake" { + amountInt64 = sdk.TokensToConsensusPower(a.Amount) + denom = "consensus_power" + } else { + amountInt64 = a.Amount.Int64() + } + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "withdraw_reward"}, + float32(amountInt64), + []metrics.Label{telemetry.NewLabel("denom", denom)}, + ) + } + }() + ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, @@ -68,11 +89,29 @@ func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDele } func handleMsgWithdrawValidatorCommission(ctx sdk.Context, msg types.MsgWithdrawValidatorCommission, k keeper.Keeper) (*sdk.Result, error) { - _, err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddress) + amount, err := k.WithdrawValidatorCommission(ctx, msg.ValidatorAddress) if err != nil { return nil, err } + defer func() { + for _, a := range amount { + var amountInt64 int64 + denom := a.Denom + if a.Denom == "stake" { + amountInt64 = sdk.TokensToConsensusPower(a.Amount) + denom = "consensus_power" + } else { + amountInt64 = a.Amount.Int64() + } + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "withdraw_commission"}, + float32(amountInt64), + []metrics.Label{telemetry.NewLabel("denom", denom)}, + ) + } + }() + ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, diff --git a/x/evidence/abci.go b/x/evidence/abci.go index e12ed7065f9b..cfc7888ef65c 100644 --- a/x/evidence/abci.go +++ b/x/evidence/abci.go @@ -2,7 +2,9 @@ package evidence import ( "fmt" + "time" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" @@ -12,6 +14,8 @@ import ( // BeginBlocker iterates through and handles any newly discovered evidence of // misbehavior submitted by Tendermint. Currently, only equivocation is handled. func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + for _, tmEvidence := range req.ByzantineValidators { switch tmEvidence.Type { case tmtypes.ABCIEvidenceTypeDuplicateVote: diff --git a/x/gov/abci.go b/x/gov/abci.go index fa990acbf0e9..aa72fc2317b5 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -2,13 +2,17 @@ package gov import ( "fmt" + "time" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov/types" ) // EndBlocker called every block, process inflation, update validator set. func EndBlocker(ctx sdk.Context, keeper Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) + logger := keeper.Logger(ctx) // delete inactive proposal from store and its deposits diff --git a/x/gov/handler.go b/x/gov/handler.go index 4e4aee2c5334..cdffe66e548e 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -2,7 +2,11 @@ package gov import ( "fmt" + "strconv" + "github.com/armon/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/gov/types" @@ -35,6 +39,8 @@ func handleMsgSubmitProposal(ctx sdk.Context, keeper Keeper, msg MsgSubmitPropos return nil, err } + defer telemetry.IncrCounter(1, ModuleName, "proposal") + votingStarted, err := keeper.AddDeposit(ctx, proposal.ProposalID, msg.Proposer, msg.InitialDeposit) if err != nil { return nil, err @@ -68,6 +74,14 @@ func handleMsgDeposit(ctx sdk.Context, keeper Keeper, msg MsgDeposit) (*sdk.Resu return nil, err } + defer telemetry.IncrCounterWithLabels( + []string{types.ModuleName, "deposit"}, + 1, + []metrics.Label{ + telemetry.NewLabel("proposal_id", strconv.Itoa(int(msg.ProposalID))), + }, + ) + ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, @@ -94,6 +108,14 @@ func handleMsgVote(ctx sdk.Context, keeper Keeper, msg MsgVote) (*sdk.Result, er return nil, err } + defer telemetry.IncrCounterWithLabels( + []string{types.ModuleName, "vote"}, + 1, + []metrics.Label{ + telemetry.NewLabel("proposal_id", strconv.Itoa(int(msg.ProposalID))), + }, + ) + ctx.EventManager().EmitEvent( sdk.NewEvent( sdk.EventTypeMessage, diff --git a/x/mint/abci.go b/x/mint/abci.go index 07d8d84624d0..759bdaa8d085 100644 --- a/x/mint/abci.go +++ b/x/mint/abci.go @@ -1,12 +1,17 @@ package mint import ( + "time" + + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mint/internal/types" ) // BeginBlocker mints new tokens for the previous block. func BeginBlocker(ctx sdk.Context, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + // fetch stored minter & params minter := k.GetMinter(ctx) params := k.GetParams(ctx) @@ -33,6 +38,8 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { panic(err) } + defer telemetry.ModuleSetGauge(types.ModuleName, float32(sdk.TokensToConsensusPower(mintedCoin.Amount)), "minted_tokens") + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeMint, diff --git a/x/slashing/abci.go b/x/slashing/abci.go index c6caeec9b5fd..6f80a2641bf2 100644 --- a/x/slashing/abci.go +++ b/x/slashing/abci.go @@ -1,14 +1,19 @@ package slashing import ( + "time" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" ) // BeginBlocker check for infraction evidence or downtime of validators // on every begin block func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + // Iterate over all the validators which *should* have signed this block // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) diff --git a/x/staking/abci.go b/x/staking/abci.go index 40dbce6a1fcc..3d3520cff6dc 100644 --- a/x/staking/abci.go +++ b/x/staking/abci.go @@ -1,21 +1,26 @@ package staking import ( + "time" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/staking/keeper" ) // BeginBlocker will persist the current header and validator set as a historical entry // and prune the oldest entry based on the HistoricalEntries parameter -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + k.TrackHistoricalInfo(ctx) k.CheckValidatorUpdates(ctx, req.Header) } // Called every block, update validator set -func EndBlocker(ctx sdk.Context, k keeper.Keeper) ([]abci.ValidatorUpdate, []abci.ValidatorUpdate) { +func EndBlocker(ctx sdk.Context, k Keeper) ([]abci.ValidatorUpdate, []abci.ValidatorUpdate) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyEndBlocker) // If dkg and validator updates are triggered at the same time then dkg validator updates // must be computed first dkgUpdates := k.DKGValidatorUpdates(ctx) diff --git a/x/staking/handler.go b/x/staking/handler.go index 180bb8008672..47c0dcb8c11d 100644 --- a/x/staking/handler.go +++ b/x/staking/handler.go @@ -3,9 +3,12 @@ package staking import ( "time" + "github.com/armon/go-metrics" + gogotypes "github.com/gogo/protobuf/types" tmstrings "github.com/tendermint/tendermint/libs/strings" tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/staking/keeper" @@ -184,6 +187,15 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return nil, err } + defer func() { + telemetry.IncrCounter(1, types.ModuleName, "delegate") + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", msg.Type()}, + float32(sdk.TokensToConsensusPower(msg.Amount.Amount)), + []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, + ) + }() + ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeDelegate, @@ -217,7 +229,21 @@ func handleMsgUndelegate(ctx sdk.Context, msg types.MsgUndelegate, k keeper.Keep return nil, err } - completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(completionTime) + ts, err := gogotypes.TimestampProto(completionTime) + if err != nil { + return nil, types.ErrBadRedelegationAddr + } + + defer func() { + telemetry.IncrCounter(1, types.ModuleName, "undelegate") + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", msg.Type()}, + float32(sdk.TokensToConsensusPower(msg.Amount.Amount)), + []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, + ) + }() + + completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(ts) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeUnbond, @@ -254,7 +280,21 @@ func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k k return nil, err } - completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(completionTime) + ts, err := gogotypes.TimestampProto(completionTime) + if err != nil { + return nil, types.ErrBadRedelegationAddr + } + + defer func() { + telemetry.IncrCounter(1, types.ModuleName, "redelegate") + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", msg.Type()}, + float32(sdk.TokensToConsensusPower(msg.Amount.Amount)), + []metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)}, + ) + }() + + completionTimeBz := types.ModuleCdc.MustMarshalBinaryLengthPrefixed(ts) ctx.EventManager().EmitEvents(sdk.Events{ sdk.NewEvent( types.EventTypeRedelegate, diff --git a/x/upgrade/abci.go b/x/upgrade/abci.go index 11edf7fbbc0c..85a29b46a7ee 100644 --- a/x/upgrade/abci.go +++ b/x/upgrade/abci.go @@ -2,9 +2,11 @@ package upgrade import ( "fmt" + "time" abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -17,6 +19,8 @@ import ( // a migration to be executed if needed upon this switch (migration defined in the new binary) // skipUpgradeHeightArray is a set of block heights for which the upgrade must be skipped func BeginBlocker(k Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) { + defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) + plan, found := k.GetUpgradePlan(ctx) if !found { return