You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by ju...@apache.org on 2023/07/04 05:34:25 UTC

[dubbo-go] branch main updated: feat: add some metrics about RT (#2340)

This is an automated email from the ASF dual-hosted git repository.

justxuewei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git


The following commit(s) were added to refs/heads/main by this push:
     new f6fdbfdf7 feat: add some metrics about RT (#2340)
f6fdbfdf7 is described below

commit f6fdbfdf772ad7f82ea64b8d861e4140c3fa17cb
Author: Wang Guan <wg...@gmail.com>
AuthorDate: Tue Jul 4 13:34:20 2023 +0800

    feat: add some metrics about RT (#2340)
    
    * feat: add min RT metrics
    
    * refactor: adapt to Go 1.17 and bundle gaugeVec with syncMap
    
    * feat: add max and sum RT metrics
    
    * feat: avg RT metrics
    
    * refactor: RT sum metrics
    
    * feat: last RT metrics
    
    * refactor: file names and method names
---
 metrics/prometheus/after_invocation.go     |  49 ---------
 metrics/prometheus/before_invocation.go    |  40 --------
 metrics/prometheus/constant.go             |   8 +-
 metrics/prometheus/metric_set.go           |  72 ++++++++------
 metrics/prometheus/{common.go => model.go} | 155 ++++++++++++++++++++---------
 metrics/prometheus/reporter.go             |  79 ++++++++++++---
 metrics/prometheus/util.go                 |  75 ++++++++++++++
 7 files changed, 294 insertions(+), 184 deletions(-)

diff --git a/metrics/prometheus/after_invocation.go b/metrics/prometheus/after_invocation.go
deleted file mode 100644
index f1235a2d7..000000000
--- a/metrics/prometheus/after_invocation.go
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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"
-	"time"
-)
-
-import (
-	"dubbo.apache.org/dubbo-go/v3/protocol"
-)
-
-func (reporter *PrometheusReporter) ReportAfterInvocation(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) {
-	if !reporter.reporterConfig.Enable {
-		return
-	}
-	url := invoker.GetURL()
-
-	role := getRole(url)
-	if role == "" {
-		return
-	}
-	labels := buildLabels(url)
-
-	reporter.reportRTSummaryVec(role, &labels, cost.Milliseconds())
-	reporter.reportRequestsTotalCounterVec(role, &labels)
-	reporter.decRequestsProcessingTotalGaugeVec(role, &labels)
-
-	if res != nil && res.Error() == nil {
-		// succeed
-		reporter.incRequestsSucceedTotalCounterVec(role, &labels)
-	}
-}
diff --git a/metrics/prometheus/before_invocation.go b/metrics/prometheus/before_invocation.go
deleted file mode 100644
index 7477b1362..000000000
--- a/metrics/prometheus/before_invocation.go
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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"
-)
-import (
-	"dubbo.apache.org/dubbo-go/v3/protocol"
-)
-
-func (reporter *PrometheusReporter) ReportBeforeInvocation(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) {
-	if !reporter.reporterConfig.Enable {
-		return
-	}
-	url := invoker.GetURL()
-
-	role := getRole(url)
-	if role == "" {
-		return
-	}
-	labels := buildLabels(url)
-
-	reporter.incRequestsProcessingTotalGaugeVec(role, &labels)
-}
diff --git a/metrics/prometheus/constant.go b/metrics/prometheus/constant.go
index 95e090d46..35858853f 100644
--- a/metrics/prometheus/constant.go
+++ b/metrics/prometheus/constant.go
@@ -34,12 +34,14 @@ const (
 
 	requestsField = "requests"
 	rtField       = "rt"
-	tpsField      = "tps"
 
 	milliSecondsField = "milliseconds"
 
-	counterField = "counter"
-	summaryField = "summary"
+	minField  = "min"
+	maxField  = "max"
+	sumField  = "sum"
+	avgField  = "avg"
+	lastField = "last"
 
 	totalField      = "total"
 	processingField = "processing"
diff --git a/metrics/prometheus/metric_set.go b/metrics/prometheus/metric_set.go
index 79c020369..115c6ca3d 100644
--- a/metrics/prometheus/metric_set.go
+++ b/metrics/prometheus/metric_set.go
@@ -29,40 +29,56 @@ import (
 	"dubbo.apache.org/dubbo-go/v3/metrics"
 )
 
-// metricSet is a set of metrics that are reported to prometheus in dubbo go
+// metricSet is a set of metrics that are reported to prometheus in dubbo-go
 type metricSet struct {
-	// report the consumer-side's rt gauge data
-	consumerRTSummaryVec *prometheus.SummaryVec
-	// report the provider-side's rt gauge data
-	providerRTSummaryVec *prometheus.SummaryVec
+	provider providerMetrics
+	consumer consumerMetrics
+}
+
+func (ms *metricSet) init(reporterConfig *metrics.ReporterConfig) {
+	ms.provider.init(reporterConfig)
+	ms.consumer.init(reporterConfig)
+}
 
-	// report the provider-side's request total counter data
-	providerRequestsTotalCounterVec *prometheus.CounterVec
-	// report the provider-side's processing request counter data
-	providerRequestsProcessingTotalGaugeVec *prometheus.GaugeVec
-	// The number of requests successfully received by the provider
-	providerRequestsSucceedTotalCounterVec *prometheus.CounterVec
+type rpcCommonMetrics struct {
+	requestsTotal           *prometheus.CounterVec
+	requestsProcessingTotal *prometheus.GaugeVec
+	requestsSucceedTotal    *prometheus.CounterVec
+	rtMillisecondsMin       *GaugeVecWithSyncMap
+	rtMillisecondsMax       *GaugeVecWithSyncMap
+	rtMillisecondsSum       *prometheus.CounterVec
+	rtMillisecondsAvg       *GaugeVecWithSyncMap
+	rtMillisecondsLast      *prometheus.GaugeVec
+}
 
-	// report the consumer-side's request total counter data
-	consumerRequestsTotalCounterVec *prometheus.CounterVec
-	// report the consumer-side's processing request counter data
-	consumerRequestsProcessingTotalGaugeVec *prometheus.GaugeVec
-	// The number of successful requests sent by consumers
-	consumerRequestsSucceedTotalCounterVec *prometheus.CounterVec
+type providerMetrics struct {
+	rpcCommonMetrics
 }
 
-var labelNames = []string{applicationNameKey, groupKey, hostnameKey, interfaceKey, ipKey, methodKey, versionKey}
+func (pm *providerMetrics) init(reporterConfig *metrics.ReporterConfig) {
+	pm.requestsTotal = newAutoCounterVec(buildMetricsName(providerField, requestsField, totalField), reporterConfig.Namespace, labelNames)
+	pm.requestsProcessingTotal = newAutoGaugeVec(buildMetricsName(providerField, requestsField, processingField, totalField), reporterConfig.Namespace, labelNames)
+	pm.requestsSucceedTotal = newAutoCounterVec(buildMetricsName(providerField, requestsField, succeedField, totalField), reporterConfig.Namespace, labelNames)
+	pm.rtMillisecondsMin = newAutoGaugeVecWithSyncMap(buildMetricsName(providerField, rtField, milliSecondsField, minField), reporterConfig.Namespace, labelNames)
+	pm.rtMillisecondsMax = newAutoGaugeVecWithSyncMap(buildMetricsName(providerField, rtField, milliSecondsField, maxField), reporterConfig.Namespace, labelNames)
+	pm.rtMillisecondsSum = newAutoCounterVec(buildMetricsName(providerField, rtField, milliSecondsField, sumField), reporterConfig.Namespace, labelNames)
+	pm.rtMillisecondsAvg = newAutoGaugeVecWithSyncMap(buildMetricsName(providerField, rtField, milliSecondsField, avgField), reporterConfig.Namespace, labelNames)
+	pm.rtMillisecondsLast = newAutoGaugeVec(buildMetricsName(providerField, rtField, milliSecondsField, lastField), reporterConfig.Namespace, labelNames)
+}
+
+type consumerMetrics struct {
+	rpcCommonMetrics
+}
 
-// init metric set and register to prometheus
-func (ms *metricSet) initAndRegister(reporterConfig *metrics.ReporterConfig) {
-	ms.consumerRTSummaryVec = newAutoSummaryVec(buildMetricsName(consumerField, rtField, milliSecondsField, summaryField), reporterConfig.Namespace, labelNames, reporterConfig.SummaryMaxAge)
-	ms.providerRTSummaryVec = newAutoSummaryVec(buildMetricsName(providerField, rtField, milliSecondsField, summaryField), reporterConfig.Namespace, labelNames, reporterConfig.SummaryMaxAge)
-	ms.consumerRequestsTotalCounterVec = newAutoCounterVec(buildMetricsName(consumerField, requestsField, totalField), reporterConfig.Namespace, labelNames)
-	ms.providerRequestsTotalCounterVec = newAutoCounterVec(buildMetricsName(providerField, requestsField, totalField), reporterConfig.Namespace, labelNames)
-	ms.consumerRequestsProcessingTotalGaugeVec = newAutoGaugeVec(buildMetricsName(consumerField, requestsField, processingField, totalField), reporterConfig.Namespace, labelNames)
-	ms.providerRequestsProcessingTotalGaugeVec = newAutoGaugeVec(buildMetricsName(providerField, requestsField, processingField, totalField), reporterConfig.Namespace, labelNames)
-	ms.consumerRequestsSucceedTotalCounterVec = newAutoCounterVec(buildMetricsName(consumerField, requestsField, succeedField, totalField), reporterConfig.Namespace, labelNames)
-	ms.providerRequestsSucceedTotalCounterVec = newAutoCounterVec(buildMetricsName(providerField, requestsField, succeedField, totalField), reporterConfig.Namespace, labelNames)
+func (cm *consumerMetrics) init(reporterConfig *metrics.ReporterConfig) {
+	cm.requestsTotal = newAutoCounterVec(buildMetricsName(consumerField, requestsField, totalField), reporterConfig.Namespace, labelNames)
+	cm.requestsProcessingTotal = newAutoGaugeVec(buildMetricsName(consumerField, requestsField, processingField, totalField), reporterConfig.Namespace, labelNames)
+	cm.requestsSucceedTotal = newAutoCounterVec(buildMetricsName(consumerField, requestsField, succeedField, totalField), reporterConfig.Namespace, labelNames)
+	cm.rtMillisecondsMin = newAutoGaugeVecWithSyncMap(buildMetricsName(consumerField, rtField, milliSecondsField, minField), reporterConfig.Namespace, labelNames)
+	cm.rtMillisecondsMax = newAutoGaugeVecWithSyncMap(buildMetricsName(consumerField, rtField, milliSecondsField, maxField), reporterConfig.Namespace, labelNames)
+	cm.rtMillisecondsSum = newAutoCounterVec(buildMetricsName(consumerField, rtField, milliSecondsField, sumField), reporterConfig.Namespace, labelNames)
+	cm.rtMillisecondsAvg = newAutoGaugeVecWithSyncMap(buildMetricsName(consumerField, rtField, milliSecondsField, avgField), reporterConfig.Namespace, labelNames)
+	cm.rtMillisecondsLast = newAutoGaugeVec(buildMetricsName(consumerField, rtField, milliSecondsField, lastField), reporterConfig.Namespace, labelNames)
 }
 
 func buildMetricsName(args ...string) string {
diff --git a/metrics/prometheus/common.go b/metrics/prometheus/model.go
similarity index 57%
rename from metrics/prometheus/common.go
rename to metrics/prometheus/model.go
index 32137d1e2..d62734f8f 100644
--- a/metrics/prometheus/common.go
+++ b/metrics/prometheus/model.go
@@ -18,63 +18,17 @@
 package prometheus
 
 import (
-	"strconv"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"time"
 )
 
 import (
-	"github.com/dubbogo/gost/log/logger"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 )
 
-import (
-	"dubbo.apache.org/dubbo-go/v3/common"
-	"dubbo.apache.org/dubbo-go/v3/common/constant"
-)
-
-var (
-	defaultHistogramBucket = []float64{10, 50, 100, 200, 500, 1000, 10000}
-)
-
-func buildLabels(url *common.URL) prometheus.Labels {
-	return prometheus.Labels{
-		applicationNameKey: url.GetParam(constant.ApplicationKey, ""),
-		groupKey:           url.Group(),
-		hostnameKey:        "not implemented yet",
-		interfaceKey:       url.Service(),
-		ipKey:              common.GetLocalIp(),
-		versionKey:         url.GetParam(constant.AppVersionKey, ""),
-		methodKey:          url.GetParam(constant.MethodKey, ""),
-	}
-}
-
-// return the role of the application, provider or consumer, if the url is not a valid one, return empty string
-func getRole(url *common.URL) (role string) {
-	if isProvider(url) {
-		role = providerField
-	} else if isConsumer(url) {
-		role = consumerField
-	} else {
-		logger.Warnf("The url belongs neither the consumer nor the provider, "+
-			"so the invocation will be ignored. url: %s", url.String())
-	}
-	return
-}
-
-// isProvider shows whether this url represents the application received the request as server
-func isProvider(url *common.URL) bool {
-	role := url.GetParam(constant.RegistryRoleKey, "")
-	return strings.EqualFold(role, strconv.Itoa(common.PROVIDER))
-}
-
-// isConsumer shows whether this url represents the application sent then request as client
-func isConsumer(url *common.URL) bool {
-	role := url.GetParam(constant.RegistryRoleKey, "")
-	return strings.EqualFold(role, strconv.Itoa(common.CONSUMER))
-}
-
 func newHistogramVec(name, namespace string, labels []string) *prometheus.HistogramVec {
 	return prometheus.NewHistogramVec(
 		prometheus.HistogramOpts{
@@ -194,3 +148,108 @@ func newAutoSummaryVec(name, namespace string, labels []string, maxAge int64) *p
 		labels,
 	)
 }
+
+type GaugeVecWithSyncMap struct {
+	GaugeVec *prometheus.GaugeVec
+	SyncMap  *sync.Map
+}
+
+func newAutoGaugeVecWithSyncMap(name, namespace string, labels []string) *GaugeVecWithSyncMap {
+	return &GaugeVecWithSyncMap{
+		GaugeVec: newAutoGaugeVec(name, namespace, labels),
+		SyncMap:  &sync.Map{},
+	}
+}
+
+func convertLabelsToMapKey(labels prometheus.Labels) string {
+	return strings.Join([]string{
+		labels[applicationNameKey],
+		labels[groupKey],
+		labels[hostnameKey],
+		labels[interfaceKey],
+		labels[ipKey],
+		labels[versionKey],
+		labels[methodKey],
+	}, "_")
+}
+
+func (gv *GaugeVecWithSyncMap) updateMin(labels *prometheus.Labels, curValue int64) {
+	key := convertLabelsToMapKey(*labels)
+	cur := &atomic.Value{} // for first store
+	cur.Store(curValue)
+	for {
+		if actual, loaded := gv.SyncMap.LoadOrStore(key, cur); loaded {
+			store := actual.(*atomic.Value)
+			storeValue := store.Load().(int64)
+			if curValue < storeValue {
+				if store.CompareAndSwap(storeValue, curValue) {
+					// value is not changed, should update
+					gv.GaugeVec.With(*labels).Set(float64(curValue))
+					break
+				}
+				// value has changed, continue for loop
+			} else {
+				// no need to update
+				break
+			}
+		} else {
+			// store current curValue as this labels' init value
+			gv.GaugeVec.With(*labels).Set(float64(curValue))
+			break
+		}
+	}
+}
+
+func (gv *GaugeVecWithSyncMap) updateMax(labels *prometheus.Labels, curValue int64) {
+	key := convertLabelsToMapKey(*labels)
+	cur := &atomic.Value{} // for first store
+	cur.Store(curValue)
+	for {
+		if actual, loaded := gv.SyncMap.LoadOrStore(key, cur); loaded {
+			store := actual.(*atomic.Value)
+			storeValue := store.Load().(int64)
+			if curValue > storeValue {
+				if store.CompareAndSwap(storeValue, curValue) {
+					// value is not changed, should update
+					gv.GaugeVec.With(*labels).Set(float64(curValue))
+					break
+				}
+				// value has changed, continue for loop
+			} else {
+				// no need to update
+				break
+			}
+		} else {
+			// store current curValue as this labels' init value
+			gv.GaugeVec.With(*labels).Set(float64(curValue))
+			break
+		}
+	}
+}
+
+func (gv *GaugeVecWithSyncMap) updateAvg(labels *prometheus.Labels, curValue int64) {
+	key := convertLabelsToMapKey(*labels)
+	cur := &atomic.Value{} // for first store
+	type avgPair struct {
+		Sum int64
+		N   int64
+	}
+	cur.Store(avgPair{Sum: curValue, N: 1})
+
+	for {
+		if actual, loaded := gv.SyncMap.LoadOrStore(key, cur); loaded {
+			store := actual.(*atomic.Value)
+			storeValue := store.Load().(avgPair)
+			newValue := avgPair{Sum: storeValue.Sum + curValue, N: storeValue.N + 1}
+			if store.CompareAndSwap(storeValue, newValue) {
+				// value is not changed, should update
+				gv.GaugeVec.With(*labels).Set(float64(newValue.Sum / newValue.N))
+				break
+			}
+		} else {
+			// store current curValue as this labels' init value
+			gv.GaugeVec.With(*labels).Set(float64(curValue))
+			break
+		}
+	}
+}
diff --git a/metrics/prometheus/reporter.go b/metrics/prometheus/reporter.go
index 8c38c6114..7ca4398bf 100644
--- a/metrics/prometheus/reporter.go
+++ b/metrics/prometheus/reporter.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"net/http"
 	"sync"
+	"time"
 )
 
 import (
@@ -32,6 +33,7 @@ import (
 import (
 	"dubbo.apache.org/dubbo-go/v3/common/extension"
 	"dubbo.apache.org/dubbo-go/v3/metrics"
+	"dubbo.apache.org/dubbo-go/v3/protocol"
 )
 
 var (
@@ -62,7 +64,7 @@ func newPrometheusReporter(reporterConfig *metrics.ReporterConfig) metrics.Repor
 	if reporterInstance == nil {
 		reporterInitOnce.Do(func() {
 			ms := &metricSet{}
-			ms.initAndRegister(reporterConfig)
+			ms.init(reporterConfig)
 			reporterInstance = &PrometheusReporter{
 				reporterConfig: reporterConfig,
 				namespace:      reporterConfig.Namespace,
@@ -103,47 +105,92 @@ func (reporter *PrometheusReporter) shutdownServer() {
 	}
 }
 
-func (reporter *PrometheusReporter) reportRTSummaryVec(role string, labels *prometheus.Labels, costMs int64) {
+func (reporter *PrometheusReporter) ReportBeforeInvocation(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) {
+	if !reporter.reporterConfig.Enable {
+		return
+	}
+	url := invoker.GetURL()
+
+	role := getRole(url)
+	if role == "" {
+		return
+	}
+	labels := buildLabels(url)
+
+	reporter.incRequestsProcessingTotal(role, &labels)
+}
+
+func (reporter *PrometheusReporter) ReportAfterInvocation(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation, cost time.Duration, res protocol.Result) {
+	if !reporter.reporterConfig.Enable {
+		return
+	}
+	url := invoker.GetURL()
+
+	role := getRole(url)
+	if role == "" {
+		return
+	}
+	labels := buildLabels(url)
+
+	reporter.incRequestsTotal(role, &labels)
+	reporter.decRequestsProcessingTotal(role, &labels)
+	reporter.reportRTMilliseconds(role, &labels, cost.Milliseconds())
+
+	if res != nil && res.Error() == nil {
+		// succeed
+		reporter.incRequestsSucceedTotal(role, &labels)
+	}
+}
+
+func (reporter *PrometheusReporter) incRequestsTotal(role string, labels *prometheus.Labels) {
 	switch role {
 	case providerField:
-		reporter.providerRTSummaryVec.With(*labels).Observe(float64(costMs))
+		reporter.provider.requestsTotal.With(*labels).Inc()
 	case consumerField:
-		reporter.consumerRTSummaryVec.With(*labels).Observe(float64(costMs))
+		reporter.consumer.requestsTotal.With(*labels).Inc()
 	}
 }
 
-func (reporter *PrometheusReporter) reportRequestsTotalCounterVec(role string, labels *prometheus.Labels) {
+func (reporter *PrometheusReporter) incRequestsProcessingTotal(role string, labels *prometheus.Labels) {
 	switch role {
 	case providerField:
-		reporter.providerRequestsTotalCounterVec.With(*labels).Inc()
+		reporter.provider.requestsProcessingTotal.With(*labels).Inc()
 	case consumerField:
-		reporter.consumerRequestsTotalCounterVec.With(*labels).Inc()
+		reporter.consumer.requestsProcessingTotal.With(*labels).Inc()
 	}
 }
 
-func (reporter *PrometheusReporter) incRequestsProcessingTotalGaugeVec(role string, labels *prometheus.Labels) {
+func (reporter *PrometheusReporter) decRequestsProcessingTotal(role string, labels *prometheus.Labels) {
 	switch role {
 	case providerField:
-		reporter.providerRequestsProcessingTotalGaugeVec.With(*labels).Inc()
+		reporter.provider.requestsProcessingTotal.With(*labels).Dec()
 	case consumerField:
-		reporter.consumerRequestsProcessingTotalGaugeVec.With(*labels).Inc()
+		reporter.consumer.requestsProcessingTotal.With(*labels).Dec()
 	}
 }
 
-func (reporter *PrometheusReporter) decRequestsProcessingTotalGaugeVec(role string, labels *prometheus.Labels) {
+func (reporter *PrometheusReporter) incRequestsSucceedTotal(role string, labels *prometheus.Labels) {
 	switch role {
 	case providerField:
-		reporter.providerRequestsProcessingTotalGaugeVec.With(*labels).Dec()
+		reporter.provider.requestsSucceedTotal.With(*labels).Inc()
 	case consumerField:
-		reporter.consumerRequestsProcessingTotalGaugeVec.With(*labels).Dec()
+		reporter.consumer.requestsSucceedTotal.With(*labels).Inc()
 	}
 }
 
-func (reporter *PrometheusReporter) incRequestsSucceedTotalCounterVec(role string, labels *prometheus.Labels) {
+func (reporter *PrometheusReporter) reportRTMilliseconds(role string, labels *prometheus.Labels, costMs int64) {
 	switch role {
 	case providerField:
-		reporter.providerRequestsSucceedTotalCounterVec.With(*labels).Inc()
+		go reporter.provider.rtMillisecondsLast.With(*labels).Set(float64(costMs))
+		go reporter.provider.rtMillisecondsSum.With(*labels).Add(float64(costMs))
+		go reporter.provider.rtMillisecondsMin.updateMin(labels, costMs)
+		go reporter.provider.rtMillisecondsMax.updateMax(labels, costMs)
+		go reporter.provider.rtMillisecondsAvg.updateAvg(labels, costMs)
 	case consumerField:
-		reporter.consumerRequestsSucceedTotalCounterVec.With(*labels).Inc()
+		go reporter.consumer.rtMillisecondsLast.With(*labels).Set(float64(costMs))
+		go reporter.consumer.rtMillisecondsSum.With(*labels).Add(float64(costMs))
+		go reporter.consumer.rtMillisecondsMin.updateMin(labels, costMs)
+		go reporter.consumer.rtMillisecondsMax.updateMax(labels, costMs)
+		go reporter.consumer.rtMillisecondsAvg.updateAvg(labels, costMs)
 	}
 }
diff --git a/metrics/prometheus/util.go b/metrics/prometheus/util.go
new file mode 100644
index 000000000..4e1a9c8dc
--- /dev/null
+++ b/metrics/prometheus/util.go
@@ -0,0 +1,75 @@
+/*
+ * 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 (
+	"strconv"
+	"strings"
+)
+
+import (
+	"github.com/dubbogo/gost/log/logger"
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+import (
+	"dubbo.apache.org/dubbo-go/v3/common"
+	"dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+var (
+	labelNames             = []string{applicationNameKey, groupKey, hostnameKey, interfaceKey, ipKey, methodKey, versionKey}
+	defaultHistogramBucket = []float64{10, 50, 100, 200, 500, 1000, 10000}
+)
+
+func buildLabels(url *common.URL) prometheus.Labels {
+	return prometheus.Labels{
+		applicationNameKey: url.GetParam(constant.ApplicationKey, ""),
+		groupKey:           url.Group(),
+		hostnameKey:        "not implemented yet",
+		interfaceKey:       url.Service(),
+		ipKey:              common.GetLocalIp(),
+		versionKey:         url.GetParam(constant.AppVersionKey, ""),
+		methodKey:          url.GetParam(constant.MethodKey, ""),
+	}
+}
+
+// return the role of the application, provider or consumer, if the url is not a valid one, return empty string
+func getRole(url *common.URL) (role string) {
+	if isProvider(url) {
+		role = providerField
+	} else if isConsumer(url) {
+		role = consumerField
+	} else {
+		logger.Warnf("The url belongs neither the consumer nor the provider, "+
+			"so the invocation will be ignored. url: %s", url.String())
+	}
+	return
+}
+
+// isProvider shows whether this url represents the application received the request as server
+func isProvider(url *common.URL) bool {
+	role := url.GetParam(constant.RegistryRoleKey, "")
+	return strings.EqualFold(role, strconv.Itoa(common.PROVIDER))
+}
+
+// isConsumer shows whether this url represents the application sent then request as client
+func isConsumer(url *common.URL) bool {
+	role := url.GetParam(constant.RegistryRoleKey, "")
+	return strings.EqualFold(role, strconv.Itoa(common.CONSUMER))
+}