add metrics base api interface (#2350)
diff --git a/common/host_util.go b/common/host_util.go
index ba36c0c..5c411ab 100644
--- a/common/host_util.go
+++ b/common/host_util.go
@@ -23,6 +23,7 @@
)
import (
+ "github.com/dubbogo/gost/log/logger"
gxnet "github.com/dubbogo/gost/net"
)
@@ -31,6 +32,7 @@
)
var localIp string
+var localHostname string
func GetLocalIp() string {
if len(localIp) != 0 {
@@ -40,6 +42,18 @@
return localIp
}
+func GetLocalHostName() string {
+ if len(localHostname) != 0 {
+ return localHostname
+ }
+ hostname, err := os.Hostname()
+ if err != nil {
+ logger.Errorf("can not get local hostname")
+ }
+ localHostname = hostname
+ return localHostname
+}
+
func HandleRegisterIPAndPort(url *URL) {
// if developer define registry port and ip, use it first.
if ipToRegistry := os.Getenv(constant.DubboIpToRegistryKey); len(ipToRegistry) > 0 {
diff --git a/config/metric_config.go b/config/metric_config.go
index b47d48a..02bce7d 100644
--- a/config/metric_config.go
+++ b/config/metric_config.go
@@ -22,9 +22,12 @@
"github.com/dubbogo/gost/log/logger"
+ "github.com/pkg/errors"
+)
+
+import (
"dubbo.apache.org/dubbo-go/v3/common/extension"
"dubbo.apache.org/dubbo-go/v3/metrics"
- "github.com/pkg/errors"
)
// MetricConfig This is the config struct for all metrics implementation
@@ -36,6 +39,7 @@
Path string `default:"/metrics" yaml:"path" json:"path,omitempty" property:"path"`
PushGatewayAddress string `default:"" yaml:"push-gateway-address" json:"push-gateway-address,omitempty" property:"push-gateway-address"`
SummaryMaxAge int64 `default:"600000000000" yaml:"summary-max-age" json:"summary-max-age,omitempty" property:"summary-max-age"`
+ Protocol string `default:"prometheus" yaml:"protocol" json:"protocol,omitempty" property:"protocol"`
}
func (mc *MetricConfig) ToReporterConfig() *metrics.ReporterConfig {
@@ -65,7 +69,9 @@
if err := verify(mc); err != nil {
return err
}
- extension.GetMetricReporter("prometheus", mc.ToReporterConfig())
+ config := mc.ToReporterConfig()
+ extension.GetMetricReporter(mc.Protocol, config)
+ metrics.Init(config)
return nil
}
@@ -88,7 +94,7 @@
mc.Enable = newMetricConfig.Enable
logger.Infof("MetricConfig's Enable was dynamically updated, new value:%v", mc.Enable)
- extension.GetMetricReporter("prometheus", mc.ToReporterConfig())
+ extension.GetMetricReporter(mc.Protocol, mc.ToReporterConfig())
}
}
}
diff --git a/imports/imports.go b/imports/imports.go
index c586896..71dd06b 100644
--- a/imports/imports.go
+++ b/imports/imports.go
@@ -64,6 +64,7 @@
_ "dubbo.apache.org/dubbo-go/v3/metadata/service/exporter/configurable"
_ "dubbo.apache.org/dubbo-go/v3/metadata/service/local"
_ "dubbo.apache.org/dubbo-go/v3/metadata/service/remote"
+ _ "dubbo.apache.org/dubbo-go/v3/metrics/app_info"
_ "dubbo.apache.org/dubbo-go/v3/metrics/prometheus"
_ "dubbo.apache.org/dubbo-go/v3/protocol/dubbo"
_ "dubbo.apache.org/dubbo-go/v3/protocol/dubbo3"
diff --git a/metrics/api.go b/metrics/api.go
new file mode 100644
index 0000000..c9d57be
--- /dev/null
+++ b/metrics/api.go
@@ -0,0 +1,161 @@
+package metrics
+
+var registries = make(map[string]func(*ReporterConfig) MetricRegistry)
+var collectors = make([]CollectorFunc, 0)
+var registry MetricRegistry
+
+// CollectorFunc 各个指标处理模块扩展
+type CollectorFunc func(MetricRegistry, *ReporterConfig)
+
+// Init 整个 Metrics 模块初始化入口
+func Init(config *ReporterConfig) {
+ // config.extention = prometheus
+ regFunc, ok := registries[config.Protocol]
+ if !ok {
+ regFunc = registries["prometheus"] // default
+ }
+ registry = regFunc(config)
+ for _, co := range collectors {
+ co(registry, config)
+ }
+ registry.Export()
+}
+
+// SetRegistry 扩展其他数据容器,暴露方式,内置 Prometheus 实现
+func SetRegistry(name string, v func(*ReporterConfig) MetricRegistry) {
+ registries[name] = v
+}
+
+// AddCollector 扩展指标收集器,例如 metadata、耗时、配置中心等
+func AddCollector(name string, fun func(MetricRegistry, *ReporterConfig)) {
+ collectors = append(collectors, fun)
+}
+
+// MetricRegistry 数据指标容器,指标计算、指标暴露、聚合
+type MetricRegistry interface {
+ Counter(*MetricId) CounterMetric // add or update a counter
+ Gauge(*MetricId) GaugeMetric // add or update a gauge
+ Histogram(*MetricId) HistogramMetric // add a metric num to a histogram
+ Summary(*MetricId) SummaryMetric // add a metric num to a summary
+ Export() // 数据暴露, 如 Prometheus 是 http 暴露
+ // GetMetrics() []*MetricSample // 获取所有指标数据
+ // GetMetricsString() (string, error) // 如需复用端口则加一下这个接口
+}
+
+// 组合暴露方式,参考 micrometer CompositeMeterRegistry
+//type CompositeRegistry struct {
+// rs []MetricRegistry
+//}
+
+// Type 指标类型,暂定和 micrometer 一致
+type Type uint8
+
+const (
+ Counter Type = iota
+ Gauge
+ LongTaskTimer
+ Timer
+ DistributionSummary
+ Other
+)
+
+// MetricId
+// # HELP dubbo_metadata_store_provider_succeed_total Succeed Store Provider Metadata
+// # TYPE dubbo_metadata_store_provider_succeed_total gauge
+// dubbo_metadata_store_provider_succeed_total{application_name="provider",hostname="localhost",interface="org.example.DemoService",ip="10.252.156.213",} 1.0
+// 除值以外的其他属性
+type MetricId struct {
+ Name string
+ Desc string
+ Tags map[string]string
+ Type Type
+}
+
+func (m *MetricId) TagKeys() []string {
+ keys := make([]string, 0, len(m.Tags))
+ for k := range m.Tags {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
+// MetricSample 一个指标的完整定义,包含值,这是指标的最终呈现,不是中间值(如 summary,histogram 他们统计完后会导出为一组 MetricSample)
+type MetricSample struct {
+ *MetricId
+ value float64
+}
+
+// CounterMetric 指标抽象接口
+type CounterMetric interface {
+ Inc()
+ Add(float64)
+}
+
+// GaugeMetric 指标抽象接口
+type GaugeMetric interface {
+ Set(float64)
+ // Inc()
+ // Dec()
+ // Add(float64)
+ // Sub(float64)
+}
+
+// HistogramMetric 指标抽象接口
+type HistogramMetric interface {
+ Record(float64)
+}
+
+// SummaryMetric 指标抽象接口
+type SummaryMetric interface {
+ Record(float64)
+}
+
+// StatesMetrics 综合指标,包括总数、成功数,失败数,调用 MetricsRegistry 实现最终暴露
+type StatesMetrics interface {
+ Success()
+ AddSuccess(float64)
+ Fail()
+ AddFailed(float64)
+}
+
+func NewStatesMetrics(total *MetricId, succ *MetricId, fail *MetricId) StatesMetrics {
+ return &DefaultStatesMetric{total: total, succ: succ, fail: fail, r: registry}
+}
+
+// TimeMetrics 综合指标, 包括 min(Gauge)、max(Gauge)、avg(Gauge)、sum(Gauge)、last(Gauge),调用 MetricRegistry 实现最终暴露
+// 参见 dubbo-java org.apache.dubbo.metrics.aggregate.TimeWindowAggregator 类实现
+type TimeMetrics interface {
+ Record(float64)
+}
+
+// NewTimeMetrics init and write all data to registry
+func NewTimeMetrics(min *MetricId, avg *MetricId, max *MetricId, last *MetricId, sum *MetricId) {
+
+}
+
+type DefaultStatesMetric struct {
+ r MetricRegistry
+ total *MetricId
+ succ *MetricId
+ fail *MetricId
+}
+
+func (c DefaultStatesMetric) Success() {
+ c.r.Counter(c.total).Inc()
+ c.r.Counter(c.succ).Inc()
+}
+
+func (c DefaultStatesMetric) AddSuccess(v float64) {
+ c.r.Counter(c.total).Add(v)
+ c.r.Counter(c.succ).Add(v)
+}
+
+func (c DefaultStatesMetric) Fail() {
+ c.r.Counter(c.total).Inc()
+ c.r.Counter(c.fail).Inc()
+}
+
+func (c DefaultStatesMetric) AddFailed(v float64) {
+ c.r.Counter(c.total).Add(v)
+ c.r.Counter(c.fail).Add(v)
+}
diff --git a/metrics/app_info/application_info.go b/metrics/app_info/application_info.go
new file mode 100644
index 0000000..8b95683
--- /dev/null
+++ b/metrics/app_info/application_info.go
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package app_info
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/metrics"
+ "dubbo.apache.org/dubbo-go/v3/metrics/common"
+)
+
+/*
+ * # HELP dubbo_application_info_total Total Application Info
+ * # TYPE dubbo_application_info_total counter
+ * dubbo_application_info_total{application_name="metrics-provider",application_version="3.2.1",git_commit_id="20de8b22ffb2a23531f6d9494a4963fcabd52561",hostname="localhost",ip="127.0.0.1",} 1.0
+ */
+var info = common.NewMetricKey("dubbo_application_info_total", "Total Application Info") // Total Application Info include application name、version etc
+
+func init() {
+ metrics.AddCollector("application_info", func(mr metrics.MetricRegistry, config *metrics.ReporterConfig) {
+ mr.Counter(&metrics.MetricId{Name: info.Name, Desc: info.Desc, Tags: common.NewApplicationLevel().Tags()}).Inc()
+ })
+}
diff --git a/metrics/common/common.go b/metrics/common/common.go
new file mode 100644
index 0000000..c8acefe
--- /dev/null
+++ b/metrics/common/common.go
@@ -0,0 +1,101 @@
+package common
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/config"
+)
+
+const (
+ TagIp = "ip"
+ TagPid = "pid"
+ TagHostname = "hostname"
+ TagApplicationName = "application_name"
+ TagApplicationModule = "application_module_id"
+ TagInterfaceKey = "interface"
+ TagMethodKey = "method"
+ TagGroupKey = "group"
+ TagVersionKey = "version"
+ TagApplicationVersionKey = "application_version"
+ TagKeyKey = "key"
+ TagConfigCenter = "config_center"
+ TagChangeType = "change_type"
+ TagThreadName = "thread_pool_name"
+ TagGitCommitId = "git_commit_id"
+)
+
+type MetricKey struct {
+ Name string
+ Desc string
+}
+
+func NewMetricKey(name string, desc string) *MetricKey {
+ return &MetricKey{Name: name, Desc: desc}
+}
+
+type MetricLevel interface {
+ Tags() map[string]string
+}
+
+type ApplicationMetricLevel struct {
+ ApplicationName string
+ Version string
+ GitCommitId string
+ Ip string
+ HostName string
+}
+
+var appLevel *ApplicationMetricLevel
+
+func NewApplicationLevel() *ApplicationMetricLevel {
+ if appLevel == nil {
+ var rootConfig = config.GetRootConfig()
+ appLevel = &ApplicationMetricLevel{
+ ApplicationName: rootConfig.Application.Name,
+ Version: rootConfig.Application.Version,
+ Ip: common.GetLocalIp(),
+ HostName: common.GetLocalHostName(),
+ GitCommitId: "",
+ }
+ }
+ return appLevel
+}
+
+func (m *ApplicationMetricLevel) Tags() map[string]string {
+ tags := make(map[string]string)
+ tags[TagIp] = m.Ip
+ tags[TagHostname] = m.HostName
+ tags[TagApplicationName] = m.ApplicationName
+ tags[TagApplicationVersionKey] = m.Version
+ tags[TagGitCommitId] = m.GitCommitId
+ return tags
+}
+
+type ServiceMetricLevel struct {
+ *ApplicationMetricLevel
+ Interface string
+}
+
+func NewServiceMetric(interfaceName string) *ServiceMetricLevel {
+ return &ServiceMetricLevel{ApplicationMetricLevel: NewApplicationLevel(), Interface: interfaceName}
+}
+
+func (m ServiceMetricLevel) Tags() map[string]string {
+ tags := m.ApplicationMetricLevel.Tags()
+ tags[TagInterfaceKey] = m.Interface
+ return tags
+}
+
+type MethodMetricLevel struct {
+ *ServiceMetricLevel
+ Method string
+ Group string
+ Version string
+}
+
+func (m MethodMetricLevel) Tags() map[string]string {
+ tags := m.ServiceMetricLevel.Tags()
+ tags[TagMethodKey] = m.Method
+ tags[TagGroupKey] = m.Group
+ tags[TagVersionKey] = m.Version
+ return tags
+}
diff --git a/metrics/prometheus/registry.go b/metrics/prometheus/registry.go
new file mode 100644
index 0000000..977d9d2
--- /dev/null
+++ b/metrics/prometheus/registry.go
@@ -0,0 +1,172 @@
+package prometheus
+
+import (
+ "bytes"
+ "sync"
+)
+
+import (
+ prom "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+
+ "github.com/prometheus/common/expfmt"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/metrics"
+)
+
+func init() {
+ metrics.SetRegistry("prometheus", func(rc *metrics.ReporterConfig) metrics.MetricRegistry {
+ return &promMetricRegistry{
+ cvm: make(map[string]*prom.CounterVec),
+ gvm: make(map[string]*prom.GaugeVec),
+ hvm: make(map[string]*prom.HistogramVec),
+ svm: make(map[string]*prom.SummaryVec),
+ }
+ })
+}
+
+type promMetricRegistry struct {
+ mtx sync.RWMutex // Protects metrics.
+ cvm map[string]*prom.CounterVec // prom.CounterVec
+ gvm map[string]*prom.GaugeVec // prom.GaugeVec
+ hvm map[string]*prom.HistogramVec // prom.HistogramVec
+ svm map[string]*prom.SummaryVec // prom.SummaryVec
+}
+
+func (p *promMetricRegistry) Counter(m *metrics.MetricId) metrics.CounterMetric {
+ p.mtx.RLock()
+ vec, ok := p.cvm[m.Name]
+ p.mtx.RUnlock()
+ if !ok {
+ p.mtx.Lock()
+ vec = promauto.NewCounterVec(prom.CounterOpts{
+ Name: m.Name,
+ Help: m.Desc,
+ }, m.TagKeys())
+ p.cvm[m.Name] = vec
+ p.mtx.Unlock()
+ }
+ c := vec.With(m.Tags)
+ return &counter{pc: c}
+}
+
+func (p *promMetricRegistry) Gauge(m *metrics.MetricId) metrics.GaugeMetric {
+ p.mtx.RLock()
+ vec, ok := p.gvm[m.Name]
+ p.mtx.RUnlock()
+ if !ok {
+ p.mtx.Lock()
+ vec = promauto.NewGaugeVec(prom.GaugeOpts{
+ Name: m.Name,
+ Help: m.Desc,
+ }, m.TagKeys())
+ p.gvm[m.Name] = vec
+ p.mtx.Unlock()
+ }
+ g := vec.With(m.Tags)
+ return &gauge{pg: g}
+}
+
+func (p *promMetricRegistry) Histogram(m *metrics.MetricId) metrics.HistogramMetric {
+ p.mtx.RLock()
+ vec, ok := p.hvm[m.Name]
+ p.mtx.RUnlock()
+ if !ok {
+ p.mtx.Lock()
+ vec = promauto.NewHistogramVec(prom.HistogramOpts{
+ Name: m.Name,
+ Help: m.Desc,
+ }, m.TagKeys())
+ p.hvm[m.Name] = vec
+ p.mtx.Unlock()
+ }
+ h := vec.With(m.Tags)
+ return &histogram{ph: h.(prom.Histogram)}
+}
+
+func (p *promMetricRegistry) Summary(m *metrics.MetricId) metrics.SummaryMetric {
+ p.mtx.RLock()
+ vec, ok := p.svm[m.Name]
+ p.mtx.RUnlock()
+ if !ok {
+ p.mtx.Lock()
+ vec = promauto.NewSummaryVec(prom.SummaryOpts{
+ Name: m.Name,
+ Help: m.Desc,
+ }, m.TagKeys())
+ p.svm[m.Name] = vec
+ p.mtx.Unlock()
+ }
+ s := vec.With(m.Tags)
+ return &summary{ps: s.(prom.Summary)}
+}
+
+func (p *promMetricRegistry) Export() {
+
+}
+
+func (p *promMetricRegistry) Scrape() (string, error) {
+ r := prom.DefaultRegisterer.(*prom.Registry)
+ gathering, err := r.Gather()
+ if err != nil {
+ return "", err
+ }
+ out := &bytes.Buffer{}
+ for _, mf := range gathering {
+ if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
+ return "", err
+ }
+ }
+ return out.String(), nil
+}
+
+type counter struct {
+ pc prom.Counter
+}
+
+func (c *counter) Inc() {
+ c.pc.Inc()
+}
+func (c *counter) Add(v float64) {
+ c.pc.Add(v)
+}
+
+type gauge struct {
+ pg prom.Gauge
+}
+
+// func (g *gauge) Inc() {
+// g.pg.Inc()
+// }
+//
+// func (g *gauge) Dec() {
+// g.pg.Dec()
+// }
+func (g *gauge) Set(v float64) {
+ g.pg.Set(v)
+}
+
+// func (g *gauge) Add(v float64) {
+// g.pg.Add(v)
+// }
+// func (g *gauge) Sub(v float64) {
+// g.pg.Sub(v)
+// }
+
+type histogram struct {
+ ph prom.Histogram
+}
+
+func (h *histogram) Record(v float64) {
+ h.ph.Observe(v)
+}
+
+type summary struct {
+ ps prom.Summary
+}
+
+func (s *summary) Record(v float64) {
+ s.ps.Observe(v)
+}
diff --git a/metrics/reporter.go b/metrics/reporter.go
index 604d412..bf9693b 100644
--- a/metrics/reporter.go
+++ b/metrics/reporter.go
@@ -36,6 +36,7 @@
Path string
PushGatewayAddress string
SummaryMaxAge int64
+ Protocol string // MetricsRegistry 扩展配置 ,如:prometheus
}
type ReportMode string