This is an automated email from the ASF dual-hosted git repository.
alexstocks pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 47abbbca6 test: add unit test for metrics (#3122)
47abbbca6 is described below
commit 47abbbca60fb8ff3c475f23f0d64bd715a257d08
Author: zbchi <[email protected]>
AuthorDate: Mon Dec 22 14:12:49 2025 +0800
test: add unit test for metrics (#3122)
* test: add unit test for metrics
* fix mistake
* format code
* use const
* fix: improve metrics tests based on Copilot review
---
metrics/api_test.go | 323 +++++++++++++++++++++
metrics/bus_test.go | 73 ++++-
metrics/common_test.go | 104 +++++++
metrics/config_center/collector_test.go | 93 ++++++
.../{bus_test.go => metadata/collector_test.go} | 48 +--
metrics/options_test.go | 151 ++++++++++
metrics/registry/event_test.go | 111 +++++++
metrics/{bus_test.go => reporter_test.go} | 37 +--
metrics/rpc/event_test.go | 76 +++++
metrics/rpc/util_test.go | 190 ++++++++++++
10 files changed, 1156 insertions(+), 50 deletions(-)
diff --git a/metrics/api_test.go b/metrics/api_test.go
new file mode 100644
index 000000000..edcac2a73
--- /dev/null
+++ b/metrics/api_test.go
@@ -0,0 +1,323 @@
+/*
+ * 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 (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+// mockMetricRegistry is a simple mock implementation of MetricRegistry for
testing
+type mockMetricRegistry struct {
+ counters map[string]*mockCounterMetric
+ gauges map[string]*mockGaugeMetric
+ histograms map[string]*mockObservableMetric
+ summaries map[string]*mockObservableMetric
+ rts map[string]*mockObservableMetric
+}
+
+func newMockMetricRegistry() *mockMetricRegistry {
+ return &mockMetricRegistry{
+ counters: make(map[string]*mockCounterMetric),
+ gauges: make(map[string]*mockGaugeMetric),
+ histograms: make(map[string]*mockObservableMetric),
+ summaries: make(map[string]*mockObservableMetric),
+ rts: make(map[string]*mockObservableMetric),
+ }
+}
+
+func (m *mockMetricRegistry) Counter(id *MetricId) CounterMetric {
+ if c, ok := m.counters[id.Name]; ok {
+ return c
+ }
+ c := &mockCounterMetric{}
+ m.counters[id.Name] = c
+ return c
+}
+
+func (m *mockMetricRegistry) Gauge(id *MetricId) GaugeMetric {
+ if g, ok := m.gauges[id.Name]; ok {
+ return g
+ }
+ g := &mockGaugeMetric{}
+ m.gauges[id.Name] = g
+ return g
+}
+
+func (m *mockMetricRegistry) Histogram(id *MetricId) ObservableMetric {
+ if h, ok := m.histograms[id.Name]; ok {
+ return h
+ }
+ h := &mockObservableMetric{}
+ m.histograms[id.Name] = h
+ return h
+}
+
+func (m *mockMetricRegistry) Summary(id *MetricId) ObservableMetric {
+ if s, ok := m.summaries[id.Name]; ok {
+ return s
+ }
+ s := &mockObservableMetric{}
+ m.summaries[id.Name] = s
+ return s
+}
+
+func (m *mockMetricRegistry) Rt(id *MetricId, opts *RtOpts) ObservableMetric {
+ if rt, ok := m.rts[id.Name]; ok {
+ return rt
+ }
+ rt := &mockObservableMetric{}
+ m.rts[id.Name] = rt
+ return rt
+}
+
+func (m *mockMetricRegistry) Export() {}
+
+type mockCounterMetric struct {
+ value float64
+}
+
+func (m *mockCounterMetric) Inc() { m.value++ }
+func (m *mockCounterMetric) Add(v float64) { m.value += v }
+
+type mockGaugeMetric struct {
+ value float64
+}
+
+func (m *mockGaugeMetric) Set(v float64) { m.value = v }
+func (m *mockGaugeMetric) Inc() { m.value++ }
+func (m *mockGaugeMetric) Dec() { m.value-- }
+func (m *mockGaugeMetric) Add(v float64) { m.value += v }
+func (m *mockGaugeMetric) Sub(v float64) { m.value -= v }
+
+type mockObservableMetric struct {
+ value float64
+}
+
+func (m *mockObservableMetric) Observe(v float64) { m.value = v }
+
+func TestMetricIdTagKeys(t *testing.T) {
+ tests := []struct {
+ name string
+ tags map[string]string
+ want int
+ }{
+ {
+ name: "empty tags",
+ tags: map[string]string{},
+ want: 0,
+ },
+ {
+ name: "single tag",
+ tags: map[string]string{"app": "dubbo"},
+ want: 1,
+ },
+ {
+ name: "multiple tags",
+ tags: map[string]string{
+ "app": "dubbo",
+ "version": "1.0.0",
+ "ip": "127.0.0.1",
+ },
+ want: 3,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ m := &MetricId{Name: "test_metric", Tags: tt.tags}
+ got := m.TagKeys()
+ assert.Equal(t, tt.want, len(got))
+ for k := range tt.tags {
+ assert.Contains(t, got, k)
+ }
+ })
+ }
+}
+
+func TestBaseCollectorStateCount(t *testing.T) {
+ registry := newMockMetricRegistry()
+ collector := &BaseCollector{R: registry}
+ level := GetApplicationLevel()
+
+ totalKey := NewMetricKey("total", "Total requests")
+ succKey := NewMetricKey("succ", "Success requests")
+ failKey := NewMetricKey("fail", "Failed requests")
+
+ t.Run("success", func(t *testing.T) {
+ collector.StateCount(totalKey, succKey, failKey, level, true)
+
+ total := registry.counters["total"]
+ succ := registry.counters["succ"]
+ fail := registry.counters["fail"]
+
+ assert.NotNil(t, total)
+ assert.NotNil(t, succ)
+ assert.Equal(t, float64(1), total.value)
+ assert.Equal(t, float64(1), succ.value)
+ if fail != nil {
+ assert.Equal(t, float64(0), fail.value)
+ }
+ })
+
+ t.Run("failure", func(t *testing.T) {
+ collector.StateCount(totalKey, succKey, failKey, level, false)
+
+ total := registry.counters["total"]
+ fail := registry.counters["fail"]
+
+ assert.NotNil(t, total)
+ assert.NotNil(t, fail)
+ assert.Equal(t, float64(2), total.value)
+ assert.Equal(t, float64(1), fail.value)
+ })
+}
+
+func TestDefaultCounterVec(t *testing.T) {
+ key := NewMetricKey("test_counter", "Test counter")
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ t.Run("Inc", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ counterVec := NewCounterVec(registry, key)
+ counterVec.Inc(labels)
+ metricId := NewMetricIdByLabels(key, labels)
+ counter := registry.Counter(metricId).(*mockCounterMetric)
+ assert.Equal(t, float64(1), counter.value)
+ })
+
+ t.Run("Add", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ counterVec := NewCounterVec(registry, key)
+ counterVec.Add(labels, 5.0)
+ metricId := NewMetricIdByLabels(key, labels)
+ counter := registry.Counter(metricId).(*mockCounterMetric)
+ assert.Equal(t, float64(5), counter.value)
+ })
+}
+
+func TestDefaultGaugeVec(t *testing.T) {
+ key := NewMetricKey("test_gauge", "Test gauge")
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ t.Run("Set", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ gaugeVec := NewGaugeVec(registry, key)
+ gaugeVec.Set(labels, 100.0)
+ gauge := registry.Gauge(NewMetricIdByLabels(key,
labels)).(*mockGaugeMetric)
+ assert.Equal(t, float64(100), gauge.value)
+ })
+
+ t.Run("Inc", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ gaugeVec := NewGaugeVec(registry, key)
+ gaugeVec.Set(labels, 100.0)
+ gaugeVec.Inc(labels)
+ gauge := registry.Gauge(NewMetricIdByLabels(key,
labels)).(*mockGaugeMetric)
+ assert.Equal(t, float64(101), gauge.value)
+ })
+
+ t.Run("Dec", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ gaugeVec := NewGaugeVec(registry, key)
+ gaugeVec.Set(labels, 100.0)
+ gaugeVec.Dec(labels)
+ gauge := registry.Gauge(NewMetricIdByLabels(key,
labels)).(*mockGaugeMetric)
+ assert.Equal(t, float64(99), gauge.value)
+ })
+
+ t.Run("Add", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ gaugeVec := NewGaugeVec(registry, key)
+ gaugeVec.Set(labels, 100.0)
+ gaugeVec.Add(labels, 50.0)
+ gauge := registry.Gauge(NewMetricIdByLabels(key,
labels)).(*mockGaugeMetric)
+ assert.Equal(t, float64(150), gauge.value)
+ })
+
+ t.Run("Sub", func(t *testing.T) {
+ registry := newMockMetricRegistry()
+ gaugeVec := NewGaugeVec(registry, key)
+ gaugeVec.Set(labels, 100.0)
+ gaugeVec.Sub(labels, 30.0)
+ gauge := registry.Gauge(NewMetricIdByLabels(key,
labels)).(*mockGaugeMetric)
+ assert.Equal(t, float64(70), gauge.value)
+ })
+}
+
+func TestDefaultRtVec(t *testing.T) {
+ registry := newMockMetricRegistry()
+ key := NewMetricKey("test_rt", "Test response time")
+ rtOpts := &RtOpts{Aggregate: false}
+ rtVec := NewRtVec(registry, key, rtOpts)
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ rtVec.Record(labels, 100.0)
+ rt := registry.Rt(NewMetricIdByLabels(key, labels), rtOpts)
+ assert.NotNil(t, rt)
+}
+
+func TestDefaultQpsMetricVec(t *testing.T) {
+ registry := newMockMetricRegistry()
+ key := NewMetricKey("test_qps", "Test QPS")
+ qpsVec := NewQpsMetricVec(registry, key)
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ for i := 0; i < 5; i++ {
+ qpsVec.Record(labels)
+ }
+
+ gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+ assert.NotNil(t, gauge)
+}
+
+func TestDefaultAggregateCounterVec(t *testing.T) {
+ registry := newMockMetricRegistry()
+ key := NewMetricKey("test_agg_counter", "Test aggregate counter")
+ aggCounterVec := NewAggregateCounterVec(registry, key)
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ for i := 0; i < 3; i++ {
+ aggCounterVec.Inc(labels)
+ }
+
+ gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+ assert.NotNil(t, gauge)
+}
+
+func TestDefaultQuantileMetricVec(t *testing.T) {
+ registry := newMockMetricRegistry()
+ keys := []*MetricKey{
+ NewMetricKey("test_quantile_p50", "P50 quantile"),
+ NewMetricKey("test_quantile_p90", "P90 quantile"),
+ }
+ quantileVec := NewQuantileMetricVec(registry, keys, []float64{0.5, 0.9})
+ labels := map[string]string{"app": "dubbo", "version": "1.0.0"}
+
+ for i := 0; i < 10; i++ {
+ quantileVec.Record(labels, float64(i*10))
+ }
+
+ for _, key := range keys {
+ gauge := registry.Gauge(NewMetricIdByLabels(key, labels))
+ assert.NotNil(t, gauge)
+ }
+}
diff --git a/metrics/bus_test.go b/metrics/bus_test.go
index 4e4a68eec..2d72f92c5 100644
--- a/metrics/bus_test.go
+++ b/metrics/bus_test.go
@@ -28,9 +28,13 @@ import (
var mockChan = make(chan MetricsEvent, 16)
type MockEvent struct {
+ eventType string
}
func (m MockEvent) Type() string {
+ if m.eventType != "" {
+ return m.eventType
+ }
return "dubbo.metrics.mock"
}
@@ -38,17 +42,76 @@ func NewEmptyMockEvent() *MockEvent {
return &MockEvent{}
}
+func NewMockEventWithType(eventType string) *MockEvent {
+ return &MockEvent{eventType: eventType}
+}
+
func init() {
Subscribe("dubbo.metrics.mock", mockChan)
Publish(NewEmptyMockEvent())
}
func TestBusPublish(t *testing.T) {
- t.Run("testBusPublish", func(t *testing.T) {
- event := <-mockChan
+ event := <-mockChan
+
+ if mockEvent, ok := event.(*MockEvent); ok {
+ assert.Equal(t, NewEmptyMockEvent(), mockEvent)
+ }
+}
+
+func TestBusUnsubscribe(t *testing.T) {
+ testChan := make(chan MetricsEvent, 1)
+ eventType := "dubbo.metrics.test.unsubscribe"
+
+ Subscribe(eventType, testChan)
+
+ testEvent1 := NewMockEventWithType(eventType)
+ Publish(testEvent1)
+
+ select {
+ case event := <-testChan:
+ assert.Equal(t, testEvent1, event)
+ default:
+ t.Fatal("Expected to receive event before unsubscribe")
+ }
+
+ Unsubscribe(eventType)
- if event, ok := event.(MockEvent); ok {
- assert.Equal(t, event, NewEmptyMockEvent())
+ testEvent2 := NewMockEventWithType(eventType)
+ Publish(testEvent2)
+
+ select {
+ case _, ok := <-testChan:
+ if ok {
+ t.Fatal("Channel should be closed after Unsubscribe")
}
- })
+ default:
+ }
+}
+
+func TestBusPublishChannelFull(t *testing.T) {
+ testChan := make(chan MetricsEvent, 1)
+ eventType := "dubbo.metrics.test.full"
+
+ Subscribe(eventType, testChan)
+
+ testEvent1 := NewMockEventWithType(eventType)
+ testChan <- testEvent1
+
+ testEvent2 := NewMockEventWithType(eventType)
+ Publish(testEvent2)
+
+ select {
+ case event := <-testChan:
+ assert.Equal(t, testEvent1, event)
+ select {
+ case <-testChan:
+ t.Fatal("Expected second event to be dropped when
channel is full")
+ default:
+ }
+ default:
+ t.Fatal("Expected to receive first event from channel")
+ }
+
+ Unsubscribe(eventType)
}
diff --git a/metrics/common_test.go b/metrics/common_test.go
new file mode 100644
index 000000000..b3d943ea0
--- /dev/null
+++ b/metrics/common_test.go
@@ -0,0 +1,104 @@
+/*
+ * 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 (
+ "os"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func TestMain(m *testing.M) {
+ InitAppInfo("test-app", "1.0.0")
+ os.Exit(m.Run())
+}
+
+func TestGetApplicationLevel(t *testing.T) {
+ level := GetApplicationLevel()
+
+ assert.NotNil(t, level)
+ assert.Equal(t, "test-app", level.ApplicationName)
+ assert.Equal(t, "1.0.0", level.Version)
+ assert.NotEmpty(t, level.Ip)
+ assert.NotEmpty(t, level.HostName)
+}
+
+func TestApplicationMetricLevelTags(t *testing.T) {
+ level := GetApplicationLevel()
+ tags := level.Tags()
+
+ assert.NotNil(t, tags)
+ assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+ assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+ assert.NotEmpty(t, tags[constant.TagIp])
+ assert.NotEmpty(t, tags[constant.TagHostname])
+ assert.Equal(t, "", tags[constant.TagGitCommitId])
+}
+
+func TestServiceMetricLevelTags(t *testing.T) {
+ serviceMetric := NewServiceMetric("com.example.Service")
+ tags := serviceMetric.Tags()
+
+ assert.NotNil(t, tags)
+ assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+ assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+ assert.Equal(t, "com.example.Service", tags[constant.TagInterface])
+ assert.NotEmpty(t, tags[constant.TagIp])
+ assert.NotEmpty(t, tags[constant.TagHostname])
+}
+
+func TestMethodMetricLevelTags(t *testing.T) {
+ serviceMetric := NewServiceMetric("com.example.Service")
+ methodLevel := MethodMetricLevel{
+ ServiceMetricLevel: serviceMetric,
+ Method: "TestMethod",
+ Group: "test-group",
+ Version: "1.0.0",
+ }
+
+ tags := methodLevel.Tags()
+
+ assert.NotNil(t, tags)
+ assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+ assert.Equal(t, "1.0.0", tags[constant.TagApplicationVersion])
+ assert.Equal(t, "com.example.Service", tags[constant.TagInterface])
+ assert.Equal(t, "TestMethod", tags[constant.TagMethod])
+ assert.Equal(t, "test-group", tags[constant.TagGroup])
+ assert.Equal(t, "1.0.0", tags[constant.TagVersion])
+}
+
+func TestConfigCenterLevelTags(t *testing.T) {
+ level := NewConfigCenterLevel("test-key", "test-group", "nacos",
"added")
+ tags := level.Tags()
+
+ assert.NotNil(t, tags)
+ assert.Equal(t, "test-app", tags[constant.TagApplicationName])
+ assert.Equal(t, "test-key", tags[constant.TagKey])
+ assert.Equal(t, "test-group", tags[constant.TagGroup])
+ assert.Equal(t, "nacos", tags[constant.TagConfigCenter])
+ assert.Equal(t, "added", tags[constant.TagChangeType])
+ assert.NotEmpty(t, tags[constant.TagIp])
+ assert.NotEmpty(t, tags[constant.TagHostname])
+}
diff --git a/metrics/config_center/collector_test.go
b/metrics/config_center/collector_test.go
new file mode 100644
index 000000000..5cb965d22
--- /dev/null
+++ b/metrics/config_center/collector_test.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 metrics
+
+import (
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/remoting"
+)
+
+func TestConfigCenterMetricEventType(t *testing.T) {
+ event := &ConfigCenterMetricEvent{
+ key: "test-key",
+ group: "test-group",
+ configCenter: Nacos,
+ changeType: remoting.EventTypeAdd,
+ size: 1.0,
+ }
+
+ assert.Equal(t, constant.MetricsConfigCenter, event.Type())
+}
+
+func TestConfigCenterMetricEventGetChangeType(t *testing.T) {
+ tests := []struct {
+ name string
+ changeType remoting.EventType
+ want string
+ }{
+ {
+ name: "EventTypeAdd",
+ changeType: remoting.EventTypeAdd,
+ want: "added",
+ },
+ {
+ name: "EventTypeDel",
+ changeType: remoting.EventTypeDel,
+ want: "deleted",
+ },
+ {
+ name: "EventTypeUpdate",
+ changeType: remoting.EventTypeUpdate,
+ want: "modified",
+ },
+ {
+ name: "unknown event type",
+ changeType: remoting.EventType(999),
+ want: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ event := &ConfigCenterMetricEvent{
+ changeType: tt.changeType,
+ }
+ got := event.getChangeType()
+ assert.Equal(t, tt.want, got)
+ })
+ }
+}
+
+func TestNewIncMetricEvent(t *testing.T) {
+ event := NewIncMetricEvent("test-key", "test-group",
remoting.EventTypeAdd, Nacos)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, "test-key", event.key)
+ assert.Equal(t, "test-group", event.group)
+ assert.Equal(t, remoting.EventTypeAdd, event.changeType)
+ assert.Equal(t, Nacos, event.configCenter)
+ assert.Equal(t, 1.0, event.size)
+}
diff --git a/metrics/bus_test.go b/metrics/metadata/collector_test.go
similarity index 52%
copy from metrics/bus_test.go
copy to metrics/metadata/collector_test.go
index 4e4a68eec..d0dda338e 100644
--- a/metrics/bus_test.go
+++ b/metrics/metadata/collector_test.go
@@ -15,40 +15,50 @@
* limitations under the License.
*/
-package metrics
+package metadata
import (
"testing"
+ "time"
)
import (
"github.com/stretchr/testify/assert"
)
-var mockChan = make(chan MetricsEvent, 16)
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
-type MockEvent struct {
-}
+func TestMetadataMetricEventType(t *testing.T) {
+ event := &MetadataMetricEvent{
+ Name: MetadataPush,
+ Succ: true,
+ }
-func (m MockEvent) Type() string {
- return "dubbo.metrics.mock"
+ assert.Equal(t, constant.MetricsMetadata, event.Type())
}
-func NewEmptyMockEvent() *MockEvent {
- return &MockEvent{}
-}
+func TestMetadataMetricEventCostMs(t *testing.T) {
+ start := time.Now()
+ end := start.Add(10 * time.Millisecond)
+
+ event := &MetadataMetricEvent{
+ Name: MetadataPush,
+ Start: start,
+ End: end,
+ }
-func init() {
- Subscribe("dubbo.metrics.mock", mockChan)
- Publish(NewEmptyMockEvent())
+ cost := event.CostMs()
+ assert.Equal(t, 10.0, cost)
}
-func TestBusPublish(t *testing.T) {
- t.Run("testBusPublish", func(t *testing.T) {
- event := <-mockChan
+func TestNewMetadataMetricTimeEvent(t *testing.T) {
+ event := NewMetadataMetricTimeEvent(MetadataPush)
- if event, ok := event.(MockEvent); ok {
- assert.Equal(t, event, NewEmptyMockEvent())
- }
- })
+ assert.NotNil(t, event)
+ assert.Equal(t, MetadataPush, event.Name)
+ assert.NotNil(t, event.Start)
+ assert.NotNil(t, event.Attachment)
+ assert.Empty(t, event.Attachment)
}
diff --git a/metrics/options_test.go b/metrics/options_test.go
new file mode 100644
index 000000000..7f7d2851a
--- /dev/null
+++ b/metrics/options_test.go
@@ -0,0 +1,151 @@
+/*
+ * 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 (
+ "testing"
+ "time"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDefaultOptions(t *testing.T) {
+ opts := defaultOptions()
+ assert.NotNil(t, opts)
+ assert.NotNil(t, opts.Metrics)
+}
+
+func TestNewOptions(t *testing.T) {
+ t.Run("no options", func(t *testing.T) {
+ opts := NewOptions()
+ assert.NotNil(t, opts)
+ assert.NotNil(t, opts.Metrics)
+ })
+
+ t.Run("with single option", func(t *testing.T) {
+ opts := NewOptions(WithPrometheus())
+ assert.NotNil(t, opts)
+ assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+ })
+
+ t.Run("with multiple options", func(t *testing.T) {
+ opts := NewOptions(
+ WithPrometheus(),
+ WithPort(9090),
+ WithPath("/metrics"),
+ )
+ assert.NotNil(t, opts)
+ assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+ assert.Equal(t, "9090", opts.Metrics.Port)
+ assert.Equal(t, "/metrics", opts.Metrics.Path)
+ })
+}
+
+func TestWithAggregationEnabled(t *testing.T) {
+ opts := NewOptions(WithAggregationEnabled())
+ assert.NotNil(t, opts.Metrics.Aggregation.Enabled)
+ assert.True(t, *opts.Metrics.Aggregation.Enabled)
+}
+
+func TestWithAggregationBucketNum(t *testing.T) {
+ opts := NewOptions(WithAggregationBucketNum(20))
+ assert.Equal(t, 20, opts.Metrics.Aggregation.BucketNum)
+}
+
+func TestWithAggregationTimeWindowSeconds(t *testing.T) {
+ opts := NewOptions(WithAggregationTimeWindowSeconds(60))
+ assert.Equal(t, 60, opts.Metrics.Aggregation.TimeWindowSeconds)
+}
+
+func TestWithPrometheus(t *testing.T) {
+ opts := NewOptions(WithPrometheus())
+ assert.Equal(t, "prometheus", opts.Metrics.Protocol)
+}
+
+func TestWithPrometheusExporterEnabled(t *testing.T) {
+ opts := NewOptions(WithPrometheusExporterEnabled())
+ assert.NotNil(t, opts.Metrics.Prometheus.Exporter.Enabled)
+ assert.True(t, *opts.Metrics.Prometheus.Exporter.Enabled)
+}
+
+func TestWithPrometheusPushgatewayEnabled(t *testing.T) {
+ opts := NewOptions(WithPrometheusPushgatewayEnabled())
+ assert.NotNil(t, opts.Metrics.Prometheus.Pushgateway.Enabled)
+ assert.True(t, *opts.Metrics.Prometheus.Pushgateway.Enabled)
+}
+
+func TestWithPrometheusGatewayUrl(t *testing.T) {
+ opts := NewOptions(WithPrometheusGatewayUrl("http://localhost:9091"))
+ assert.Equal(t, "http://localhost:9091",
opts.Metrics.Prometheus.Pushgateway.BaseUrl)
+}
+
+func TestWithPrometheusGatewayJob(t *testing.T) {
+ opts := NewOptions(WithPrometheusGatewayJob("test-job"))
+ assert.Equal(t, "test-job", opts.Metrics.Prometheus.Pushgateway.Job)
+}
+
+func TestWithPrometheusGatewayUsername(t *testing.T) {
+ opts := NewOptions(WithPrometheusGatewayUsername("admin"))
+ assert.Equal(t, "admin", opts.Metrics.Prometheus.Pushgateway.Username)
+}
+
+func TestWithPrometheusGatewayPassword(t *testing.T) {
+ opts := NewOptions(WithPrometheusGatewayPassword("secret"))
+ assert.Equal(t, "secret", opts.Metrics.Prometheus.Pushgateway.Password)
+}
+
+func TestWithPrometheusGatewayInterval(t *testing.T) {
+ opts := NewOptions(WithPrometheusGatewayInterval(60 * time.Second))
+ assert.Equal(t, 60, opts.Metrics.Prometheus.Pushgateway.PushInterval)
+}
+
+func TestWithConfigCenterEnabled(t *testing.T) {
+ opts := NewOptions(WithConfigCenterEnabled())
+ assert.NotNil(t, opts.Metrics.EnableConfigCenter)
+ assert.True(t, *opts.Metrics.EnableConfigCenter)
+}
+
+func TestWithMetadataEnabled(t *testing.T) {
+ opts := NewOptions(WithMetadataEnabled())
+ assert.NotNil(t, opts.Metrics.EnableMetadata)
+ assert.True(t, *opts.Metrics.EnableMetadata)
+}
+
+func TestWithRegistryEnabled(t *testing.T) {
+ opts := NewOptions(WithRegistryEnabled())
+ assert.NotNil(t, opts.Metrics.EnableRegistry)
+ assert.True(t, *opts.Metrics.EnableRegistry)
+}
+
+func TestWithEnabled(t *testing.T) {
+ opts := NewOptions(WithEnabled())
+ assert.NotNil(t, opts.Metrics.Enable)
+ assert.True(t, *opts.Metrics.Enable)
+}
+
+func TestWithPort(t *testing.T) {
+ opts := NewOptions(WithPort(8080))
+ assert.Equal(t, "8080", opts.Metrics.Port)
+}
+
+func TestWithPath(t *testing.T) {
+ opts := NewOptions(WithPath("/custom/metrics"))
+ assert.Equal(t, "/custom/metrics", opts.Metrics.Path)
+}
diff --git a/metrics/registry/event_test.go b/metrics/registry/event_test.go
new file mode 100644
index 000000000..b7d2236de
--- /dev/null
+++ b/metrics/registry/event_test.go
@@ -0,0 +1,111 @@
+/*
+ * 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 registry
+
+import (
+ "testing"
+ "time"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+)
+
+func TestRegistryMetricsEventType(t *testing.T) {
+ event := &RegistryMetricsEvent{
+ Name: Reg,
+ Succ: true,
+ }
+
+ assert.Equal(t, constant.MetricsRegistry, event.Type())
+}
+
+func TestRegistryMetricsEventCostMs(t *testing.T) {
+ start := time.Now()
+ end := start.Add(10 * time.Millisecond)
+
+ event := &RegistryMetricsEvent{
+ Name: Reg,
+ Start: start,
+ End: end,
+ }
+
+ cost := event.CostMs()
+ assert.Equal(t, 10.0, cost)
+}
+
+func TestNewRegisterEvent(t *testing.T) {
+ start := time.Now()
+ event := NewRegisterEvent(true, start).(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, Reg, event.Name)
+ assert.True(t, event.Succ)
+ assert.Equal(t, start, event.Start)
+ assert.True(t, event.End.After(start))
+}
+
+func TestNewSubscribeEvent(t *testing.T) {
+ event := NewSubscribeEvent(true).(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, Sub, event.Name)
+ assert.True(t, event.Succ)
+}
+
+func TestNewNotifyEvent(t *testing.T) {
+ start := time.Now()
+ event := NewNotifyEvent(start).(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, Notify, event.Name)
+ assert.Equal(t, start, event.Start)
+ assert.True(t, event.End.After(start))
+}
+
+func TestNewDirectoryEvent(t *testing.T) {
+ event := NewDirectoryEvent("test-dir").(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, Directory, event.Name)
+ assert.NotNil(t, event.Attachment)
+ assert.Equal(t, "test-dir", event.Attachment["DirTyp"])
+}
+
+func TestNewServerRegisterEvent(t *testing.T) {
+ start := time.Now()
+ event := NewServerRegisterEvent(true, start).(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, ServerReg, event.Name)
+ assert.True(t, event.Succ)
+ assert.Equal(t, start, event.Start)
+ assert.True(t, event.End.After(start))
+}
+
+func TestNewServerSubscribeEvent(t *testing.T) {
+ event := NewServerSubscribeEvent(true).(*RegistryMetricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, ServerSub, event.Name)
+ assert.True(t, event.Succ)
+}
diff --git a/metrics/bus_test.go b/metrics/reporter_test.go
similarity index 64%
copy from metrics/bus_test.go
copy to metrics/reporter_test.go
index 4e4a68eec..45fa7141d 100644
--- a/metrics/bus_test.go
+++ b/metrics/reporter_test.go
@@ -25,30 +25,15 @@ import (
"github.com/stretchr/testify/assert"
)
-var mockChan = make(chan MetricsEvent, 16)
-
-type MockEvent struct {
-}
-
-func (m MockEvent) Type() string {
- return "dubbo.metrics.mock"
-}
-
-func NewEmptyMockEvent() *MockEvent {
- return &MockEvent{}
-}
-
-func init() {
- Subscribe("dubbo.metrics.mock", mockChan)
- Publish(NewEmptyMockEvent())
-}
-
-func TestBusPublish(t *testing.T) {
- t.Run("testBusPublish", func(t *testing.T) {
- event := <-mockChan
-
- if event, ok := event.(MockEvent); ok {
- assert.Equal(t, event, NewEmptyMockEvent())
- }
- })
+func TestNewReporterConfig(t *testing.T) {
+ config := NewReporterConfig()
+
+ assert.NotNil(t, config)
+ assert.True(t, config.Enable)
+ assert.Equal(t, "dubbo", config.Namespace)
+ assert.Equal(t, "9090", config.Port)
+ assert.Equal(t, "/metrics", config.Path)
+ assert.Equal(t, string(ReportModePull), string(config.Mode))
+ assert.Empty(t, config.PushGatewayAddress)
+ assert.Equal(t, int64(DefMaxAge), config.SummaryMaxAge)
}
diff --git a/metrics/rpc/event_test.go b/metrics/rpc/event_test.go
new file mode 100644
index 000000000..a20e20350
--- /dev/null
+++ b/metrics/rpc/event_test.go
@@ -0,0 +1,76 @@
+/*
+ * 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 rpc
+
+import (
+ "testing"
+ "time"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/base"
+ "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+ "dubbo.apache.org/dubbo-go/v3/protocol/result"
+)
+
+func TestMetricsEventType(t *testing.T) {
+ url := base.NewBaseInvoker(&common.URL{})
+ invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+ event := &metricsEvent{
+ name: BeforeInvoke,
+ invoker: url,
+ invocation: invoc,
+ }
+
+ assert.Equal(t, constant.MetricsRpc, event.Type())
+}
+
+func TestNewBeforeInvokeEvent(t *testing.T) {
+ url := base.NewBaseInvoker(&common.URL{})
+ invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+ event := NewBeforeInvokeEvent(url, invoc).(*metricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, BeforeInvoke, event.name)
+ assert.Equal(t, url, event.invoker)
+ assert.Equal(t, invoc, event.invocation)
+}
+
+func TestNewAfterInvokeEvent(t *testing.T) {
+ url := base.NewBaseInvoker(&common.URL{})
+ invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+ costTime := 100 * time.Millisecond
+ res := &result.RPCResult{}
+
+ event := NewAfterInvokeEvent(url, invoc, costTime, res).(*metricsEvent)
+
+ assert.NotNil(t, event)
+ assert.Equal(t, AfterInvoke, event.name)
+ assert.Equal(t, url, event.invoker)
+ assert.Equal(t, invoc, event.invocation)
+ assert.Equal(t, costTime, event.costTime)
+ assert.Equal(t, res, event.result)
+}
diff --git a/metrics/rpc/util_test.go b/metrics/rpc/util_test.go
new file mode 100644
index 000000000..fdc861d85
--- /dev/null
+++ b/metrics/rpc/util_test.go
@@ -0,0 +1,190 @@
+/*
+ * 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 rpc
+
+import (
+ "strconv"
+ "testing"
+)
+
+import (
+ "github.com/stretchr/testify/assert"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/invocation"
+)
+
+func TestBuildLabels(t *testing.T) {
+ url := common.NewURLWithOptions(
+ common.WithProtocol("dubbo"),
+ common.WithParamsValue(constant.ApplicationKey, "test-app"),
+ common.WithParamsValue(constant.AppVersionKey, "1.0.0"),
+ common.WithParamsValue(constant.VersionKey, "2.0.0"),
+ common.WithParamsValue(constant.GroupKey, "test-group"),
+ common.WithParamsValue(constant.InterfaceKey,
"com.example.Service"),
+ )
+
+ invoc := invocation.NewRPCInvocation("TestMethod", []any{}, nil)
+
+ labels := buildLabels(url, invoc)
+
+ assert.NotNil(t, labels)
+ assert.Equal(t, "test-app", labels[constant.TagApplicationName])
+ assert.Equal(t, "1.0.0", labels[constant.TagApplicationVersion])
+ assert.Equal(t, "com.example.Service", labels[constant.TagInterface])
+ assert.Equal(t, "TestMethod", labels[constant.TagMethod])
+ assert.Equal(t, "test-group", labels[constant.TagGroup])
+ assert.Equal(t, "2.0.0", labels[constant.TagVersion])
+ assert.NotEmpty(t, labels[constant.TagHostname])
+ assert.NotEmpty(t, labels[constant.TagIp])
+}
+
+func TestGetRole(t *testing.T) {
+ tests := []struct {
+ name string
+ url *common.URL
+ expected string
+ }{
+ {
+ name: "provider role",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+ ),
+ expected: constant.SideProvider,
+ },
+ {
+ name: "consumer role",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+ ),
+ expected: constant.SideConsumer,
+ },
+ {
+ name: "no role",
+ url: common.NewURLWithOptions(
+ common.WithProtocol("dubbo"),
+ ),
+ expected: "",
+ },
+ {
+ name: "invalid role",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, "invalid"),
+ ),
+ expected: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ role := getRole(tt.url)
+ assert.Equal(t, tt.expected, role)
+ })
+ }
+}
+
+func TestIsProvider(t *testing.T) {
+ tests := []struct {
+ name string
+ url *common.URL
+ expected bool
+ }{
+ {
+ name: "provider",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+ ),
+ expected: true,
+ },
+ {
+ name: "provider string",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, "3"),
+ ),
+ expected: true,
+ },
+ {
+ name: "not provider",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+ ),
+ expected: false,
+ },
+ {
+ name: "no role",
+ url: common.NewURLWithOptions(
+ common.WithProtocol("dubbo"),
+ ),
+ expected: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := isProvider(tt.url)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestIsConsumer(t *testing.T) {
+ tests := []struct {
+ name string
+ url *common.URL
+ expected bool
+ }{
+ {
+ name: "consumer",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.CONSUMER)),
+ ),
+ expected: true,
+ },
+ {
+ name: "consumer string",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, "0"),
+ ),
+ expected: true,
+ },
+ {
+ name: "not consumer",
+ url: common.NewURLWithOptions(
+
common.WithParamsValue(constant.RegistryRoleKey, strconv.Itoa(common.PROVIDER)),
+ ),
+ expected: false,
+ },
+ {
+ name: "no role",
+ url: common.NewURLWithOptions(
+ common.WithProtocol("dubbo"),
+ ),
+ expected: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := isConsumer(tt.url)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}