From 94d6b1dd0ffe141b01135218e6e3cfebd5e51eb4 Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 20 Nov 2024 18:39:09 +0100 Subject: [PATCH] fix(server/v2/api/telemetry): enable global metrics (#22571) (cherry picked from commit b45cf753a7a344a275e0489c03360fc91663c220) # Conflicts: # server/v2/api/telemetry/server.go --- server/v2/api/telemetry/server.go | 130 ++++++++++++++++++++++++++++++ simapp/v2/simdv2/cmd/commands.go | 3 +- telemetry/metrics.go | 5 ++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 server/v2/api/telemetry/server.go diff --git a/server/v2/api/telemetry/server.go b/server/v2/api/telemetry/server.go new file mode 100644 index 000000000000..f612964a8476 --- /dev/null +++ b/server/v2/api/telemetry/server.go @@ -0,0 +1,130 @@ +package telemetry + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "cosmossdk.io/core/server" + "cosmossdk.io/core/transaction" + "cosmossdk.io/log" + serverv2 "cosmossdk.io/server/v2" +) + +var ( + _ serverv2.ServerComponent[transaction.Tx] = (*Server[transaction.Tx])(nil) + _ serverv2.HasConfig = (*Server[transaction.Tx])(nil) +) + +const ServerName = "telemetry" + +type Server[T transaction.Tx] struct { + logger log.Logger + config *Config + server *http.Server + metrics *Metrics +} + +// New creates a new telemetry server. +func New[T transaction.Tx](cfg server.ConfigMap, logger log.Logger, enableTelemetry func()) (*Server[T], error) { + srv := &Server[T]{} + serverCfg := srv.Config().(*Config) + if len(cfg) > 0 { + if err := serverv2.UnmarshalSubConfig(cfg, srv.Name(), &serverCfg); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + } + srv.config = serverCfg + srv.logger = logger.With(log.ModuleKey, srv.Name()) + + if enableTelemetry == nil { + panic("enableTelemetry must be provided") + } + + if srv.config.Enable { + enableTelemetry() + } + + metrics, err := NewMetrics(srv.config) + if err != nil { + return nil, fmt.Errorf("failed to initialize metrics: %w", err) + } + srv.metrics = metrics + return srv, nil +} + +// Name returns the server name. +func (s *Server[T]) Name() string { + return ServerName +} + +func (s *Server[T]) Config() any { + if s.config == nil || s.config.Address == "" { + return DefaultConfig() + } + + return s.config +} + +func (s *Server[T]) Start(ctx context.Context) error { + if !s.config.Enable { + s.logger.Info(fmt.Sprintf("%s server is disabled via config", s.Name())) + return nil + } + + mux := http.NewServeMux() + // /metrics is the default standard path for Prometheus metrics. + mux.HandleFunc("/metrics", s.metricsHandler) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/metrics", http.StatusMovedPermanently) + }) + + s.server = &http.Server{ + Addr: s.config.Address, + Handler: mux, + } + + s.logger.Info("starting telemetry server...", "address", s.config.Address) + if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return fmt.Errorf("failed to start telemetry server: %w", err) + } + + return nil +} + +func (s *Server[T]) Stop(ctx context.Context) error { + if !s.config.Enable || s.server == nil { + return nil + } + + s.logger.Info("stopping telemetry server...", "address", s.config.Address) + return s.server.Shutdown(ctx) +} + +func (s *Server[T]) metricsHandler(w http.ResponseWriter, r *http.Request) { + format := strings.TrimSpace(r.FormValue("format")) + + // errorResponse defines the attributes of a JSON error response. + type errorResponse struct { + Code int `json:"code,omitempty"` + Error string `json:"error"` + } + + gr, err := s.metrics.Gather(format) + if err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + bz, err := json.Marshal(errorResponse{Code: 400, Error: fmt.Sprintf("failed to gather metrics: %s", err)}) + if err != nil { + return + } + _, _ = w.Write(bz) + + return + } + + w.Header().Set("Content-Type", gr.ContentType) + _, _ = w.Write(gr.Metrics) +} diff --git a/simapp/v2/simdv2/cmd/commands.go b/simapp/v2/simdv2/cmd/commands.go index 24fad3eaa538..9fb41250ec8e 100644 --- a/simapp/v2/simdv2/cmd/commands.go +++ b/simapp/v2/simdv2/cmd/commands.go @@ -25,6 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/debug" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/rpc" + sdktelemetry "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/genutil" @@ -118,7 +119,7 @@ func InitRootCmd[T transaction.Tx]( } } - telemetryServer, err := telemetry.New[T](deps.GlobalConfig, logger) + telemetryServer, err := telemetry.New[T](deps.GlobalConfig, logger, sdktelemetry.EnableTelemetry) if err != nil { return nil, err } diff --git a/telemetry/metrics.go b/telemetry/metrics.go index 175261408aae..67ace50c53ec 100644 --- a/telemetry/metrics.go +++ b/telemetry/metrics.go @@ -24,6 +24,11 @@ func IsTelemetryEnabled() bool { return globalTelemetryEnabled } +// EnableTelemetry allows for the global telemetry enabled state to be set. +func EnableTelemetry() { + globalTelemetryEnabled = true +} + // 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{}