github.com/Finschia/finschia-sdk@v0.48.1/telemetry/metrics.go (about) 1 package telemetry 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "time" 8 9 metrics "github.com/armon/go-metrics" 10 metricsprom "github.com/armon/go-metrics/prometheus" 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/common/expfmt" 13 ) 14 15 // globalLabels defines the set of global labels that will be applied to all 16 // metrics emitted using the telemetry package function wrappers. 17 var globalLabels = []metrics.Label{} 18 19 // Metrics supported format types. 20 const ( 21 FormatDefault = "" 22 FormatPrometheus = "prometheus" 23 FormatText = "text" 24 ) 25 26 // Config defines the configuration options for application telemetry. 27 type Config struct { 28 // Prefixed with keys to separate services 29 ServiceName string `mapstructure:"service-name"` 30 31 // Enabled enables the application telemetry functionality. When enabled, 32 // an in-memory sink is also enabled by default. Operators may also enabled 33 // other sinks such as Prometheus. 34 Enabled bool `mapstructure:"enabled"` 35 36 // Enable prefixing gauge values with hostname 37 EnableHostname bool `mapstructure:"enable-hostname"` 38 39 // Enable adding hostname to labels 40 EnableHostnameLabel bool `mapstructure:"enable-hostname-label"` 41 42 // Enable adding service to labels 43 EnableServiceLabel bool `mapstructure:"enable-service-label"` 44 45 // PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. 46 // It defines the retention duration in seconds. 47 PrometheusRetentionTime int64 `mapstructure:"prometheus-retention-time"` 48 49 // GlobalLabels defines a global set of name/value label tuples applied to all 50 // metrics emitted using the wrapper functions defined in telemetry package. 51 // 52 // Example: 53 // [["chain_id", "cosmoshub-1"]] 54 GlobalLabels [][]string `mapstructure:"global-labels"` 55 } 56 57 // Metrics defines a wrapper around application telemetry functionality. It allows 58 // metrics to be gathered at any point in time. When creating a Metrics object, 59 // internally, a global metrics is registered with a set of sinks as configured 60 // by the operator. In addition to the sinks, when a process gets a SIGUSR1, a 61 // dump of formatted recent metrics will be sent to STDERR. 62 type Metrics struct { 63 memSink *metrics.InmemSink 64 prometheusEnabled bool 65 } 66 67 // GatherResponse is the response type of registered metrics 68 type GatherResponse struct { 69 Metrics []byte 70 ContentType string 71 } 72 73 // New creates a new instance of Metrics 74 func New(cfg Config) (*Metrics, error) { 75 if !cfg.Enabled { 76 return nil, nil 77 } 78 79 if numGlobalLables := len(cfg.GlobalLabels); numGlobalLables > 0 { 80 parsedGlobalLabels := make([]metrics.Label, numGlobalLables) 81 for i, gl := range cfg.GlobalLabels { 82 parsedGlobalLabels[i] = NewLabel(gl[0], gl[1]) 83 } 84 85 globalLabels = parsedGlobalLabels 86 } 87 88 metricsConf := metrics.DefaultConfig(cfg.ServiceName) 89 metricsConf.EnableHostname = cfg.EnableHostname 90 metricsConf.EnableHostnameLabel = cfg.EnableHostnameLabel 91 92 memSink := metrics.NewInmemSink(10*time.Second, time.Minute) 93 metrics.DefaultInmemSignal(memSink) 94 95 m := &Metrics{memSink: memSink} 96 fanout := metrics.FanoutSink{memSink} 97 98 if cfg.PrometheusRetentionTime > 0 { 99 m.prometheusEnabled = true 100 prometheusOpts := metricsprom.PrometheusOpts{ 101 Expiration: time.Duration(cfg.PrometheusRetentionTime) * time.Second, 102 } 103 104 promSink, err := metricsprom.NewPrometheusSinkFrom(prometheusOpts) 105 if err != nil { 106 return nil, err 107 } 108 109 fanout = append(fanout, promSink) 110 } 111 112 if _, err := metrics.NewGlobal(metricsConf, fanout); err != nil { 113 return nil, err 114 } 115 116 return m, nil 117 } 118 119 // Gather collects all registered metrics and returns a GatherResponse where the 120 // metrics are encoded depending on the type. Metrics are either encoded via 121 // Prometheus or JSON if in-memory. 122 func (m *Metrics) Gather(format string) (GatherResponse, error) { 123 switch format { 124 case FormatPrometheus: 125 return m.gatherPrometheus() 126 127 case FormatText: 128 return m.gatherGeneric() 129 130 case FormatDefault: 131 return m.gatherGeneric() 132 133 default: 134 return GatherResponse{}, fmt.Errorf("unsupported metrics format: %s", format) 135 } 136 } 137 138 func (m *Metrics) gatherPrometheus() (GatherResponse, error) { 139 if !m.prometheusEnabled { 140 return GatherResponse{}, fmt.Errorf("prometheus metrics are not enabled") 141 } 142 143 metricsFamilies, err := prometheus.DefaultGatherer.Gather() 144 if err != nil { 145 return GatherResponse{}, fmt.Errorf("failed to gather prometheus metrics: %w", err) 146 } 147 148 buf := &bytes.Buffer{} 149 defer buf.Reset() 150 151 e := expfmt.NewEncoder(buf, expfmt.FmtText) 152 for _, mf := range metricsFamilies { 153 if err := e.Encode(mf); err != nil { 154 return GatherResponse{}, fmt.Errorf("failed to encode prometheus metrics: %w", err) 155 } 156 } 157 158 return GatherResponse{ContentType: string(expfmt.FmtText), Metrics: buf.Bytes()}, nil 159 } 160 161 func (m *Metrics) gatherGeneric() (GatherResponse, error) { 162 summary, err := m.memSink.DisplayMetrics(nil, nil) 163 if err != nil { 164 return GatherResponse{}, fmt.Errorf("failed to gather in-memory metrics: %w", err) 165 } 166 167 content, err := json.Marshal(summary) 168 if err != nil { 169 return GatherResponse{}, fmt.Errorf("failed to encode in-memory metrics: %w", err) 170 } 171 172 return GatherResponse{ContentType: "application/json", Metrics: content}, nil 173 }