diff --git a/common/constant/time.go b/common/constant/time.go new file mode 100644 index 0000000000..be1baaca67 --- /dev/null +++ b/common/constant/time.go @@ -0,0 +1,26 @@ +/* + * 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 constant + +import ( + "time" +) + +var ( + MsToNanoRate = int64(time.Millisecond / time.Nanosecond) +) diff --git a/common/extension/metrics.go b/common/extension/metrics.go new file mode 100644 index 0000000000..42fca7a2db --- /dev/null +++ b/common/extension/metrics.go @@ -0,0 +1,44 @@ +/* + * 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 extension + +import ( + "github.com/apache/dubbo-go/metrics" +) + +var ( + // we couldn't store the instance because the some instance may initialize before loading configuration + // so lazy initialization will be better. + metricReporterMap = make(map[string]func() metrics.Reporter, 4) +) + +// SetMetricReporter set a reporter with the name +func SetMetricReporter(name string, reporterFunc func() metrics.Reporter) { + metricReporterMap[name] = reporterFunc +} + +// GetMetricReporter find the reporter with name. +// if not found, it will panic. +// we should know that this method usually is called when system starts, so we should panic +func GetMetricReporter(name string) metrics.Reporter { + reporterFunc, found := metricReporterMap[name] + if !found { + panic("Cannot find the reporter with name: " + name) + } + return reporterFunc() +} diff --git a/common/extension/metrics_test.go b/common/extension/metrics_test.go new file mode 100644 index 0000000000..6a8a3fe538 --- /dev/null +++ b/common/extension/metrics_test.go @@ -0,0 +1,49 @@ +/* + * 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 extension + +import ( + "context" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) + +import ( + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +func TestGetMetricReporter(t *testing.T) { + reporter := &mockReporter{} + name := "mock" + SetMetricReporter(name, func() metrics.Reporter { + return reporter + }) + res := GetMetricReporter(name) + assert.Equal(t, reporter, res) +} + +type mockReporter struct { +} + +func (m mockReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { +} diff --git a/config/base_config.go b/config/base_config.go index 0949574115..f38ef9b953 100644 --- a/config/base_config.go +++ b/config/base_config.go @@ -40,12 +40,14 @@ type multiConfiger interface { Prefix() string } -// BaseConfig ... +// BaseConfig is the common configuration for provider and consumer type BaseConfig struct { ConfigCenterConfig *ConfigCenterConfig `yaml:"config_center" json:"config_center,omitempty"` configCenterUrl *common.URL prefix string fatherConfig interface{} + + MetricConfig *MetricConfig `yaml:"metrics" json:"metrics,omitempty"` } func (c *BaseConfig) startConfigCenter(ctx context.Context) error { diff --git a/config/config_loader.go b/config/config_loader.go index d6eb7ff524..875d1f6ddb 100644 --- a/config/config_loader.go +++ b/config/config_loader.go @@ -31,9 +31,11 @@ import ( ) var ( - consumerConfig *ConsumerConfig - providerConfig *ProviderConfig - maxWait = 3 + consumerConfig *ConsumerConfig + providerConfig *ProviderConfig + metricConfig *MetricConfig + applicationConfig *ApplicationConfig + maxWait = 3 ) // loaded consumer & provider config from xxx.yml, and log config from xxx.xml @@ -75,6 +77,10 @@ func Load() { if consumerConfig == nil { logger.Warnf("consumerConfig is nil!") } else { + + metricConfig = consumerConfig.MetricConfig + applicationConfig = consumerConfig.ApplicationConfig + checkApplicationName(consumerConfig.ApplicationConfig) if err := configCenterRefreshConsumer(); err != nil { logger.Errorf("[consumer config center refresh] %#v", err) @@ -131,6 +137,11 @@ func Load() { if providerConfig == nil { logger.Warnf("providerConfig is nil!") } else { + + // so, you should know that the consumer's config will be override + metricConfig = providerConfig.MetricConfig + applicationConfig = providerConfig.ApplicationConfig + checkApplicationName(providerConfig.ApplicationConfig) if err := configCenterRefreshProvider(); err != nil { logger.Errorf("[provider config center refresh] %#v", err) @@ -162,3 +173,42 @@ func GetRPCService(name string) common.RPCService { func RPCService(service common.RPCService) { consumerConfig.References[service.Reference()].Implement(service) } + +// GetMetricConfig find the MetricConfig +// if it is nil, create a new one +func GetMetricConfig() *MetricConfig { + if metricConfig == nil { + metricConfig = &MetricConfig{} + } + return metricConfig +} + +// GetApplicationConfig find the application config +// if not, we will create one +// Usually applicationConfig will be initialized when system start +func GetApplicationConfig() *ApplicationConfig { + if applicationConfig == nil { + applicationConfig = &ApplicationConfig{} + } + return applicationConfig +} + +// GetProviderConfig find the provider config +// if not found, create new one +func GetProviderConfig() ProviderConfig { + if providerConfig == nil { + logger.Warnf("providerConfig is nil!") + return ProviderConfig{} + } + return *providerConfig +} + +// GetConsumerConfig find the consumer config +// if not found, create new one +func GetConsumerConfig() ConsumerConfig { + if consumerConfig == nil { + logger.Warnf("consumerConfig is nil!") + return ConsumerConfig{} + } + return *consumerConfig +} diff --git a/config/consumer_config.go b/config/consumer_config.go index 7756f3b51c..1bfa761fc9 100644 --- a/config/consumer_config.go +++ b/config/consumer_config.go @@ -85,15 +85,6 @@ func SetConsumerConfig(c ConsumerConfig) { consumerConfig = &c } -// GetConsumerConfig ... -func GetConsumerConfig() ConsumerConfig { - if consumerConfig == nil { - logger.Warnf("consumerConfig is nil!") - return ConsumerConfig{} - } - return *consumerConfig -} - // ConsumerInit ... func ConsumerInit(confConFile string) error { if confConFile == "" { diff --git a/config/metric_config.go b/config/metric_config.go new file mode 100644 index 0000000000..73a3ca1cfe --- /dev/null +++ b/config/metric_config.go @@ -0,0 +1,37 @@ +/* + * 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 config + +var ( + defaultHistogramBucket = []float64{10, 50, 100, 200, 500, 1000, 10000} +) + +// This is the config struct for all metrics implementation +type MetricConfig struct { + Reporters []string `yaml:"reporters" json:"reporters,omitempty"` + HistogramBucket []float64 `yaml:"histogram_bucket" json:"histogram_bucket,omitempty"` +} + +// find the histogram bucket +// if it's empty, the default value will be return +func (mc *MetricConfig) GetHistogramBucket() []float64 { + if len(mc.HistogramBucket) == 0 { + mc.HistogramBucket = defaultHistogramBucket + } + return mc.HistogramBucket +} diff --git a/config/metric_config_test.go b/config/metric_config_test.go new file mode 100644 index 0000000000..fe9d2493f3 --- /dev/null +++ b/config/metric_config_test.go @@ -0,0 +1,31 @@ +/* + * 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 config + +import ( + "testing" +) + +import ( + "github.com/stretchr/testify/assert" +) + +func TestGetMetricConfig(t *testing.T) { + empty := GetMetricConfig() + assert.NotNil(t, empty) +} diff --git a/config/provider_config.go b/config/provider_config.go index 0bfa78647b..0f5c71a7de 100644 --- a/config/provider_config.go +++ b/config/provider_config.go @@ -76,15 +76,6 @@ func SetProviderConfig(p ProviderConfig) { providerConfig = &p } -// GetProviderConfig ... -func GetProviderConfig() ProviderConfig { - if providerConfig == nil { - logger.Warnf("providerConfig is nil!") - return ProviderConfig{} - } - return *providerConfig -} - // ProviderInit ... func ProviderInit(confProFile string) error { if len(confProFile) == 0 { diff --git a/filter/filter_impl/metrics_filter.go b/filter/filter_impl/metrics_filter.go new file mode 100644 index 0000000000..f4734172b7 --- /dev/null +++ b/filter/filter_impl/metrics_filter.go @@ -0,0 +1,93 @@ +/* + * 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 filter_impl + +import ( + "context" + "time" +) + +import ( + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/filter" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +const ( + metricFilterName = "metrics" +) + +var ( + metricFilterInstance filter.Filter +) + +// must initialized before using the filter and after loading configuration +func init() { + extension.SetFilter(metricFilterName, newMetricsFilter) +} + +// metricFilter will calculate the invocation's duration and the report to the reporters +// If you want to use this filter to collect the metrics, +// Adding this into your configuration file, like: +// filter: "metrics" +// metrics: +// reporter: +// - "your reporter" # here you should specify the reporter, for example 'prometheus' +// more info please take a look at dubbo-samples projects +type metricsFilter struct { + reporters []metrics.Reporter +} + +// Invoke collect the duration of invocation and then report the duration by using goroutine +func (p *metricsFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + start := time.Now() + res := invoker.Invoke(ctx, invocation) + end := time.Now() + duration := end.Sub(start) + go func() { + for _, reporter := range p.reporters { + reporter.Report(ctx, invoker, invocation, duration, res) + } + }() + return res +} + +// OnResponse do nothing and return the result +func (p *metricsFilter) OnResponse(ctx context.Context, res protocol.Result, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result { + return res +} + +// newMetricsFilter the metricsFilter is singleton. +// it's lazy initialization +// make sure that the configuration had been loaded before invoking this method. +func newMetricsFilter() filter.Filter { + if metricFilterInstance == nil { + reporterNames := config.GetMetricConfig().Reporters + reporters := make([]metrics.Reporter, 0, len(reporterNames)) + for _, name := range reporterNames { + reporters = append(reporters, extension.GetMetricReporter(name)) + } + metricFilterInstance = &metricsFilter{ + reporters: reporters, + } + } + + return metricFilterInstance +} diff --git a/filter/filter_impl/metrics_filter_test.go b/filter/filter_impl/metrics_filter_test.go new file mode 100644 index 0000000000..83697f0f29 --- /dev/null +++ b/filter/filter_impl/metrics_filter_test.go @@ -0,0 +1,84 @@ +/* + * 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 filter_impl + +import ( + "context" + "sync" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestMetricsFilter_Invoke(t *testing.T) { + + // prepare the mock reporter + config.GetMetricConfig().Reporters = []string{"mock"} + mk := &mockReporter{} + extension.SetMetricReporter("mock", func() metrics.Reporter { + return mk + }) + + instance := extension.GetFilter(metricFilterName) + + url, _ := common.NewURL(context.Background(), + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") + invoker := protocol.NewBaseInvoker(url) + + attach := make(map[string]string, 10) + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + + ctx := context.Background() + + mk.On("Report", ctx, invoker, inv).Return(true, nil) + + mk.wg.Add(1) + result := instance.Invoke(ctx, invoker, inv) + assert.NotNil(t, result) + mk.AssertNotCalled(t, "Report", 1) + // it will do nothing + result = instance.OnResponse(ctx, nil, invoker, inv) + assert.Nil(t, result) +} + +type mockReporter struct { + mock.Mock + wg sync.WaitGroup +} + +func (m *mockReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { + m.Called(ctx, invoker, invocation) + m.wg.Done() +} diff --git a/go.mod b/go.mod index 09f778a960..54b39d322e 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/nacos-group/nacos-sdk-go v0.0.0-20190723125407-0242d42e3dbb github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.8.1 - github.com/prometheus/client_golang v1.1.0 // indirect + github.com/prometheus/client_golang v1.1.0 github.com/satori/go.uuid v1.2.0 github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 // indirect github.com/soheilhy/cmux v0.1.4 // indirect diff --git a/metrics/prometheus/reporter.go b/metrics/prometheus/reporter.go new file mode 100644 index 0000000000..1636b14da2 --- /dev/null +++ b/metrics/prometheus/reporter.go @@ -0,0 +1,184 @@ +/* + * 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 prometheus + +import ( + "context" + "strconv" + "strings" + "sync" + "time" +) +import ( + "github.com/prometheus/client_golang/prometheus" +) + +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/constant" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/common/logger" + "github.com/apache/dubbo-go/config" + "github.com/apache/dubbo-go/metrics" + "github.com/apache/dubbo-go/protocol" +) + +const ( + reporterName = "prometheus" + serviceKey = constant.SERVICE_KEY + groupKey = constant.GROUP_KEY + versionKey = constant.VERSION_KEY + methodKey = constant.METHOD_KEY + timeoutKey = constant.TIMEOUT_KEY + + providerKey = "provider" + consumerKey = "consumer" + + // to identify the metric's type + histogramSuffix = "_histogram" + // to identify the metric's type + summarySuffix = "_summary" +) + +var ( + labelNames = []string{serviceKey, groupKey, versionKey, methodKey, timeoutKey} + namespace = config.GetApplicationConfig().Name + reporterInstance *PrometheusReporter + reporterInitOnce sync.Once +) + +// should initialize after loading configuration +func init() { + + extension.SetMetricReporter(reporterName, newPrometheusReporter) +} + +// PrometheusReporter +// it will collect the data for Prometheus +// if you want to use this, you should initialize your prometheus. +// https://prometheus.io/docs/guides/go-application/ +type PrometheusReporter struct { + + // report the consumer-side's summary data + consumerSummaryVec *prometheus.SummaryVec + // report the provider-side's summary data + providerSummaryVec *prometheus.SummaryVec + + // report the provider-side's histogram data + providerHistogramVec *prometheus.HistogramVec + // report the consumer-side's histogram data + consumerHistogramVec *prometheus.HistogramVec +} + +// Report report the duration to Prometheus +// the role in url must be consumer or provider +// or it will be ignored +func (reporter *PrometheusReporter) Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) { + url := invoker.GetUrl() + var sumVec *prometheus.SummaryVec + var hisVec *prometheus.HistogramVec + if isProvider(url) { + sumVec = reporter.providerSummaryVec + hisVec = reporter.providerHistogramVec + } else if isConsumer(url) { + sumVec = reporter.consumerSummaryVec + hisVec = reporter.consumerHistogramVec + } else { + logger.Warnf("The url is not the consumer's or provider's, "+ + "so the invocation will be ignored. url: %s", url.String()) + return + } + + labels := prometheus.Labels{ + serviceKey: url.Service(), + groupKey: url.GetParam(groupKey, ""), + versionKey: url.GetParam(versionKey, ""), + methodKey: invocation.MethodName(), + timeoutKey: url.GetParam(timeoutKey, ""), + } + + costMs := float64(cost.Nanoseconds() / constant.MsToNanoRate) + sumVec.With(labels).Observe(costMs) + hisVec.With(labels).Observe(costMs) +} + +func newHistogramVec(side string) *prometheus.HistogramVec { + mc := config.GetMetricConfig() + return prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: namespace, + Subsystem: side, + Name: serviceKey + histogramSuffix, + Help: "This is the dubbo's histogram metrics", + Buckets: mc.GetHistogramBucket(), + }, + labelNames) +} + +// whether this url represents the application received the request as server +func isProvider(url common.URL) bool { + role := url.GetParam(constant.ROLE_KEY, "") + return strings.EqualFold(role, strconv.Itoa(common.PROVIDER)) +} + +// whether this url represents the application sent then request as client +func isConsumer(url common.URL) bool { + role := url.GetParam(constant.ROLE_KEY, "") + return strings.EqualFold(role, strconv.Itoa(common.CONSUMER)) +} + +// newSummaryVec create SummaryVec, the Namespace is dubbo +// the objectives is from my experience. +func newSummaryVec(side string) *prometheus.SummaryVec { + return prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Namespace: namespace, + Help: "This is the dubbo's summary metrics", + Subsystem: side, + Name: serviceKey + summarySuffix, + Objectives: map[float64]float64{ + 0.5: 0.01, + 0.75: 0.01, + 0.90: 0.005, + 0.98: 0.002, + 0.99: 0.001, + 0.999: 0.0001, + }, + }, + labelNames, + ) +} + +// newPrometheusReporter create new prometheusReporter +// it will register the metrics into prometheus +func newPrometheusReporter() metrics.Reporter { + if reporterInstance == nil { + reporterInitOnce.Do(func() { + reporterInstance = &PrometheusReporter{ + consumerSummaryVec: newSummaryVec(consumerKey), + providerSummaryVec: newSummaryVec(providerKey), + + consumerHistogramVec: newHistogramVec(consumerKey), + providerHistogramVec: newHistogramVec(providerKey), + } + prometheus.MustRegister(reporterInstance.consumerSummaryVec, reporterInstance.providerSummaryVec, + reporterInstance.consumerHistogramVec, reporterInstance.providerHistogramVec) + }) + } + return reporterInstance +} diff --git a/metrics/prometheus/reporter_test.go b/metrics/prometheus/reporter_test.go new file mode 100644 index 0000000000..d1741d16d0 --- /dev/null +++ b/metrics/prometheus/reporter_test.go @@ -0,0 +1,72 @@ +/* + * 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 prometheus + +import ( + "context" + "testing" + "time" +) + +import ( + "github.com/stretchr/testify/assert" +) +import ( + "github.com/apache/dubbo-go/common" + "github.com/apache/dubbo-go/common/extension" + "github.com/apache/dubbo-go/protocol" + "github.com/apache/dubbo-go/protocol/invocation" +) + +func TestPrometheusReporter_Report(t *testing.T) { + reporter := extension.GetMetricReporter(reporterName) + url, _ := common.NewURL(context.Background(), + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ + "BDTService&organization=ikurento.com&owner=ZX®istry.role=3&retries=&"+ + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") + invoker := protocol.NewBaseInvoker(url) + + attach := make(map[string]string, 10) + inv := invocation.NewRPCInvocation("MethodName", []interface{}{"OK", "Hello"}, attach) + + assert.False(t, isConsumer(url)) + ctx := context.Background() + reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) + + // consumer side + url, _ = common.NewURL(context.Background(), + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ + "BDTService&organization=ikurento.com&owner=ZX®istry.role=0&retries=&"+ + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") + invoker = protocol.NewBaseInvoker(url) + reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) + + // invalid role + url, _ = common.NewURL(context.Background(), + "dubbo://:20000/UserProvider?app.version=0.0.1&application=BDTService&bean.name=UserProvider"+ + "&cluster=failover&environment=dev&group=&interface=com.ikurento.user.UserProvider&loadbalance=random&methods.GetUser."+ + "loadbalance=random&methods.GetUser.retries=1&methods.GetUser.weight=0&module=dubbogo+user-info+server&name="+ + "BDTService&organization=ikurento.com&owner=ZX®istry.role=9&retries=&"+ + "service.filter=echo%2Ctoken%2Caccesslog×tamp=1569153406&token=934804bf-b007-4174-94eb-96e3e1d60cc7&version=&warmup=100") + invoker = protocol.NewBaseInvoker(url) + reporter.Report(ctx, invoker, inv, 100*time.Millisecond, nil) +} diff --git a/metrics/reporter.go b/metrics/reporter.go new file mode 100644 index 0000000000..85ef1dcdf0 --- /dev/null +++ b/metrics/reporter.go @@ -0,0 +1,37 @@ +/* + * 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 metrics + +import ( + "context" + "time" +) +import ( + "github.com/apache/dubbo-go/protocol" +) + +const ( + NameSpace = "dubbo" +) + +// it will be use to report the invocation's duration +type Reporter interface { + // report the duration of an invocation + Report(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, + cost time.Duration, res protocol.Result) +}