dubbo.apache.org/dubbo-go/v3@v3.1.1/metrics/prometheus/registry.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package prometheus 19 20 import ( 21 "bytes" 22 "context" 23 "net/http" 24 "sync" 25 "time" 26 ) 27 28 import ( 29 "github.com/dubbogo/gost/log/logger" 30 31 prom "github.com/prometheus/client_golang/prometheus" 32 "github.com/prometheus/client_golang/prometheus/promhttp" 33 "github.com/prometheus/client_golang/prometheus/push" 34 35 "github.com/prometheus/common/expfmt" 36 ) 37 38 import ( 39 "dubbo.apache.org/dubbo-go/v3/common" 40 "dubbo.apache.org/dubbo-go/v3/common/constant" 41 "dubbo.apache.org/dubbo-go/v3/common/extension" 42 "dubbo.apache.org/dubbo-go/v3/metrics" 43 ) 44 45 func init() { 46 metrics.SetRegistry(constant.ProtocolPrometheus, func(url *common.URL) metrics.MetricRegistry { 47 return &promMetricRegistry{r: prom.DefaultRegisterer, gather: prom.DefaultGatherer, url: url} 48 }) 49 } 50 51 type promMetricRegistry struct { 52 r prom.Registerer 53 gather prom.Gatherer 54 vecs sync.Map 55 url *common.URL 56 } 57 58 func NewPromMetricRegistry(reg *prom.Registry, url *common.URL) *promMetricRegistry { 59 return &promMetricRegistry{r: reg, gather: reg, url: url} 60 } 61 62 func (p *promMetricRegistry) getOrComputeVec(key string, supplier func() prom.Collector) interface{} { 63 v, ok := p.vecs.Load(key) 64 if !ok { 65 v, ok = p.vecs.LoadOrStore(key, supplier()) 66 if !ok { 67 p.r.MustRegister(v.(prom.Collector)) // only registe collector which stored success 68 } 69 } 70 return v 71 } 72 73 func (p *promMetricRegistry) Counter(m *metrics.MetricId) metrics.CounterMetric { 74 vec := p.getOrComputeVec(m.Name, func() prom.Collector { 75 return prom.NewCounterVec(prom.CounterOpts{ 76 Name: m.Name, 77 Help: m.Desc, 78 }, m.TagKeys()) 79 }).(*prom.CounterVec) 80 return vec.With(m.Tags) 81 } 82 83 func (p *promMetricRegistry) Gauge(m *metrics.MetricId) metrics.GaugeMetric { 84 vec := p.getOrComputeVec(m.Name, func() prom.Collector { 85 return prom.NewGaugeVec(prom.GaugeOpts{ 86 Name: m.Name, 87 Help: m.Desc, 88 }, m.TagKeys()) 89 }).(*prom.GaugeVec) 90 return vec.With(m.Tags) 91 } 92 93 func (p *promMetricRegistry) Histogram(m *metrics.MetricId) metrics.ObservableMetric { 94 vec := p.getOrComputeVec(m.Name, func() prom.Collector { 95 return prom.NewHistogramVec(prom.HistogramOpts{ 96 Name: m.Name, 97 Help: m.Desc, 98 }, m.TagKeys()) 99 }).(*prom.HistogramVec) 100 return vec.With(m.Tags) 101 } 102 103 func (p *promMetricRegistry) Summary(m *metrics.MetricId) metrics.ObservableMetric { 104 vec := p.getOrComputeVec(m.Name, func() prom.Collector { 105 return prom.NewSummaryVec(prom.SummaryOpts{ 106 Name: m.Name, 107 Help: m.Desc, 108 }, m.TagKeys()) 109 }).(*prom.SummaryVec) 110 return vec.With(m.Tags) 111 } 112 113 func (p *promMetricRegistry) Rt(m *metrics.MetricId, opts *metrics.RtOpts) metrics.ObservableMetric { 114 key := m.Name 115 var supplier func() prom.Collector 116 if opts != nil && opts.Aggregate { 117 key += "_aggregate" 118 if opts.BucketNum == 0 { 119 opts.BucketNum = p.url.GetParamByIntValue(constant.AggregationBucketNumKey, constant.AggregationDefaultBucketNum) 120 } 121 if opts.TimeWindowSeconds == 0 { 122 opts.TimeWindowSeconds = p.url.GetParamInt(constant.AggregationTimeWindowSecondsKey, constant.AggregationDefaultTimeWindowSeconds) 123 } 124 supplier = func() prom.Collector { 125 return NewAggRtVec(&RtOpts{ 126 Name: m.Name, 127 Help: m.Desc, 128 bucketNum: opts.BucketNum, 129 timeWindowSeconds: opts.TimeWindowSeconds, 130 }, m.TagKeys()) 131 } 132 } else { 133 supplier = func() prom.Collector { 134 return NewRtVec(&RtOpts{ 135 Name: m.Name, 136 Help: m.Desc, 137 }, m.TagKeys()) 138 } 139 } 140 vec := p.getOrComputeVec(key, supplier).(*RtVec) 141 return vec.With(m.Tags) 142 } 143 144 func (p *promMetricRegistry) Export() { 145 if p.url.GetParamBool(constant.PrometheusExporterEnabledKey, false) { 146 go p.exportHttp() 147 } 148 if p.url.GetParamBool(constant.PrometheusPushgatewayEnabledKey, false) { 149 p.exportPushgateway() 150 } 151 } 152 153 func (p *promMetricRegistry) exportHttp() { 154 mux := http.NewServeMux() 155 path := p.url.GetParam(constant.PrometheusExporterMetricsPathKey, constant.PrometheusDefaultMetricsPath) 156 port := p.url.GetParam(constant.PrometheusExporterMetricsPortKey, constant.PrometheusDefaultMetricsPort) 157 mux.Handle(path, promhttp.InstrumentMetricHandler(p.r, promhttp.HandlerFor(p.gather, promhttp.HandlerOpts{}))) 158 srv := &http.Server{Addr: ":" + port, Handler: mux} 159 extension.AddCustomShutdownCallback(func() { 160 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 161 defer cancel() 162 if err := srv.Shutdown(ctx); nil != err { 163 logger.Fatalf("prometheus server shutdown failed, err: %v", err) 164 } else { 165 logger.Info("prometheus server gracefully shutdown success") 166 } 167 }) 168 logger.Infof("prometheus endpoint :%s%s", port, path) 169 if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { // except Shutdown or Close 170 logger.Errorf("new prometheus server with error: %v", err) 171 } 172 } 173 174 func (p *promMetricRegistry) exportPushgateway() { 175 baseUrl, exist := p.url.GetNonDefaultParam(constant.PrometheusPushgatewayBaseUrlKey) 176 if !exist { 177 logger.Error("no pushgateway base url found in config path: metrics.prometheus.pushgateway.base-url, please check your config") 178 return 179 } 180 username := p.url.GetParam(constant.PrometheusPushgatewayBaseUrlKey, "") 181 password := p.url.GetParam(constant.PrometheusPushgatewayBaseUrlKey, "") 182 job := p.url.GetParam(constant.PrometheusPushgatewayJobKey, constant.PrometheusDefaultJobName) 183 pushInterval := p.url.GetParamByIntValue(constant.PrometheusPushgatewayPushIntervalKey, constant.PrometheusDefaultPushInterval) 184 pusher := push.New(baseUrl, job).Gatherer(p.gather) 185 if len(username) != 0 { 186 pusher.BasicAuth(username, password) 187 } 188 logger.Infof("prometheus pushgateway will push to %s every %d seconds", baseUrl, pushInterval) 189 ticker := time.NewTicker(time.Duration(pushInterval) * time.Second) 190 go func() { 191 for range ticker.C { 192 err := pusher.Add() 193 if err != nil { 194 logger.Errorf("push metric data to prometheus pushgateway error: %v", err) 195 } else { 196 logger.Debugf("prometheus pushgateway push to %s success", baseUrl) 197 } 198 } 199 }() 200 } 201 202 func (p *promMetricRegistry) Scrape() (string, error) { 203 gathering, err := p.gather.Gather() 204 if err != nil { 205 return "", err 206 } 207 out := &bytes.Buffer{} 208 for _, mf := range gathering { 209 if _, err := expfmt.MetricFamilyToText(out, mf); err != nil { 210 return "", err 211 } 212 } 213 return out.String(), nil 214 }