From 742e035a8a1c3d7c13f8476b60ad12da7456851e Mon Sep 17 00:00:00 2001 From: namelessssssssssss <100946116+namelessssssssssss@users.noreply.github.com> Date: Wed, 19 Apr 2023 12:41:53 +0800 Subject: [PATCH] Add metrics provider/consumer uts (#12120) * Provide uts in metrics-api * Provide uts in metrics-default * Provide uts in metrics-default * Update pom.xml * Update pom.xml * Remove 'import *' * Remove 'import *' * Merge remote branch * Add license * Update DefaultDubboClientObservationConventionTest.java * Merge remote branch * Add ut for METRIC_QPS * Add ut for Provider/Consumer Metrics * Add ut for Provider/Consumer Metrics * Add ut for Provider/Consumer Metrics * Update pom.xml for test * Add test for p95 & p99 * Add test for p95 & p99 * Update pom.xml * Update import * Remove unused todo * Update test * Remove unused import * Remove unnecessary test unit * Fix codestyle problems * Fix codestyle problems --- .../dubbo/common/utils/ReflectionUtils.java | 101 ++++++++++ ...tDubboClientObservationConventionTest.java | 71 +++++++ ...tDubboServerObservationConventionTest.java | 69 +++++++ .../utils/ObservationConventionUtils.java | 48 +++++ dubbo-metrics/dubbo-metrics-default/pom.xml | 1 - .../metrics/DefaultMetricsServiceTest.java | 84 ++++++++ .../AggregateMetricsCollectorTest.java | 151 ++++++++++++++- .../ConfigCenterMetricsCollectorTest.java | 7 - .../sample/ThreadPoolMetricsSamplerTest.java | 180 ++++++++++++++++++ .../metrics/sampler/MethodMetricsTest.java | 132 +++++++++++++ .../collector/RegistryStatCompositeTest.java | 46 +++++ 11 files changed, 875 insertions(+), 15 deletions(-) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectionUtils.java create mode 100644 dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboClientObservationConventionTest.java create mode 100644 dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboServerObservationConventionTest.java create mode 100644 dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/utils/ObservationConventionUtils.java create mode 100644 dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/DefaultMetricsServiceTest.java create mode 100644 dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/sample/ThreadPoolMetricsSamplerTest.java create mode 100644 dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/sampler/MethodMetricsTest.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectionUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectionUtils.java new file mode 100644 index 0000000000..b659dcb97b --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectionUtils.java @@ -0,0 +1,101 @@ +/* + * 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 org.apache.dubbo.common.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * A utility class that provides methods for accessing and manipulating private fields and methods of an object. + * This is useful for white-box testing, where the internal workings of a class need to be tested directly. + *

+ * Note: Usage of this class should be limited to testing purposes only, as it violates the encapsulation principle. + */ +public class ReflectionUtils { + + private ReflectionUtils(){} + + /** + * Retrieves the value of the specified field from the given object. + * + * @param source The object from which to retrieve the field value. + * @param fieldName The name of the field to retrieve. + * @return The value of the specified field in the given object. + * @throws RuntimeException If the specified field does not exist. + */ + public static Object getField(Object source, String fieldName) { + try { + Field f = source.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + return f.get(source); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + /** + * Invokes the specified method on the given object with the provided parameters. + * + * @param source The object on which to invoke the method. + * @param methodName The name of the method to invoke. + * @param params The parameters to pass to the method. + * @return The result of invoking the specified method on the given object. + */ + public static Object invoke(Object source, String methodName, Object... params) { + try { + Class[] classes = Arrays.stream(params) + .map(param -> param != null ? param.getClass() : null) + .toArray(Class[]::new); + + for (Method method : source.getClass().getDeclaredMethods()) { + if (method.getName().equals(methodName) && matchParameters(method.getParameterTypes(), classes)) { + method.setAccessible(true); + return method.invoke(source, params); + } + } + throw new NoSuchMethodException("No method found with the specified name and parameter types"); + } catch (Exception e) { + throw new ReflectionException(e); + } + } + + private static boolean matchParameters(Class[] methodParamTypes, Class[] givenParamTypes) { + if (methodParamTypes.length != givenParamTypes.length) { + return false; + } + + for (int i = 0; i < methodParamTypes.length; i++) { + if (givenParamTypes[i] == null) { + if (methodParamTypes[i].isPrimitive()) { + return false; + } + } else if (!methodParamTypes[i].isAssignableFrom(givenParamTypes[i])) { + return false; + } + } + + return true; + } + + public static class ReflectionException extends RuntimeException{ + public ReflectionException(Throwable cause) { + super(cause); + } + } + +} diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboClientObservationConventionTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboClientObservationConventionTest.java new file mode 100644 index 0000000000..8b91e2531d --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboClientObservationConventionTest.java @@ -0,0 +1,71 @@ +/* + * 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 org.apache.dubbo.metrics.observation; + +import io.micrometer.common.KeyValues; +import org.apache.dubbo.metrics.observation.utils.ObservationConventionUtils; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class DefaultDubboClientObservationConventionTest { + + static DubboClientObservationConvention dubboClientObservationConvention = DefaultDubboClientObservationConvention.getInstance(); + + @Test + void testGetName() { + Assertions.assertEquals("rpc.client.duration", dubboClientObservationConvention.getName()); + } + + @Test + void testGetLowCardinalityKeyValues() throws NoSuchFieldException, IllegalAccessException { + RpcInvocation invocation = new RpcInvocation(); + invocation.setMethodName("testMethod"); + invocation.setAttachment("interface", "com.example.TestService"); + invocation.setTargetServiceUniqueName("targetServiceName1"); + + Invoker invoker = ObservationConventionUtils.getMockInvokerWithUrl(); + invocation.setInvoker(invoker); + + DubboClientContext context = new DubboClientContext(invoker, invocation); + + KeyValues keyValues = dubboClientObservationConvention.getLowCardinalityKeyValues(context); + + Assertions.assertEquals("testMethod", ObservationConventionUtils.getValueForKey(keyValues, "rpc.method")); + Assertions.assertEquals("targetServiceName1", ObservationConventionUtils.getValueForKey(keyValues, "rpc.service")); + Assertions.assertEquals("apache_dubbo", ObservationConventionUtils.getValueForKey(keyValues, "rpc.system")); + } + + @Test + void testGetContextualName() { + RpcInvocation invocation = new RpcInvocation(); + Invoker invoker = ObservationConventionUtils.getMockInvokerWithUrl(); + invocation.setMethodName("testMethod"); + invocation.setServiceName("com.example.TestService"); + + DubboClientContext context = new DubboClientContext(invoker, invocation); + + DefaultDubboClientObservationConvention convention = new DefaultDubboClientObservationConvention(); + + String contextualName = convention.getContextualName(context); + Assertions.assertEquals("com.example.TestService/testMethod", contextualName); + } + + +} diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboServerObservationConventionTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboServerObservationConventionTest.java new file mode 100644 index 0000000000..faa49457e7 --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/DefaultDubboServerObservationConventionTest.java @@ -0,0 +1,69 @@ +/* + * 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 org.apache.dubbo.metrics.observation; + +import io.micrometer.common.KeyValues; +import org.apache.dubbo.metrics.observation.utils.ObservationConventionUtils; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") +public class DefaultDubboServerObservationConventionTest { + + static DubboServerObservationConvention dubboServerObservationConvention = DefaultDubboServerObservationConvention.getInstance(); + + @Test + void testGetName() { + Assertions.assertEquals("rpc.server.duration", dubboServerObservationConvention.getName()); + } + + @Test + void testGetLowCardinalityKeyValues() throws NoSuchFieldException, IllegalAccessException { + RpcInvocation invocation = new RpcInvocation(); + invocation.setMethodName("testMethod"); + invocation.setAttachment("interface", "com.example.TestService"); + invocation.setTargetServiceUniqueName("targetServiceName1"); + + Invoker invoker = ObservationConventionUtils.getMockInvokerWithUrl(); + invocation.setInvoker(invoker); + + DubboServerContext context = new DubboServerContext(invoker, invocation); + + KeyValues keyValues = dubboServerObservationConvention.getLowCardinalityKeyValues(context); + + Assertions.assertEquals("testMethod", ObservationConventionUtils.getValueForKey(keyValues, "rpc.method")); + Assertions.assertEquals("targetServiceName1", ObservationConventionUtils.getValueForKey(keyValues, "rpc.service")); + Assertions.assertEquals("apache_dubbo", ObservationConventionUtils.getValueForKey(keyValues, "rpc.system")); + } + + @Test + void testGetContextualName() { + RpcInvocation invocation = new RpcInvocation(); + Invoker invoker = ObservationConventionUtils.getMockInvokerWithUrl(); + invocation.setMethodName("testMethod"); + invocation.setServiceName("com.example.TestService"); + + DubboClientContext context = new DubboClientContext(invoker, invocation); + + DefaultDubboClientObservationConvention convention = new DefaultDubboClientObservationConvention(); + + String contextualName = convention.getContextualName(context); + Assertions.assertEquals("com.example.TestService/testMethod", contextualName); + } +} diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/utils/ObservationConventionUtils.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/utils/ObservationConventionUtils.java new file mode 100644 index 0000000000..4bf2abfa1e --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/observation/utils/ObservationConventionUtils.java @@ -0,0 +1,48 @@ +/* + * 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 org.apache.dubbo.metrics.observation.utils; + +import io.micrometer.common.KeyValue; +import io.micrometer.common.KeyValues; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invoker; +import org.mockito.Mockito; + +import java.lang.reflect.Field; + +public class ObservationConventionUtils { + + + public static Invoker getMockInvokerWithUrl(){ + URL url = URL.valueOf("dubbo://127.0.0.1:12345/com.example.TestService?anyhost=true&application=test&category=providers&dubbo=2.0.2&generic=false&interface=com.example.TestService&methods=testMethod&pid=26716&side=provider×tamp=1633863896653"); + Invoker invoker = Mockito.mock(Invoker.class); + Mockito.when(invoker.getUrl()).thenReturn(url); + return invoker; + } + + public static String getValueForKey(KeyValues keyValues, Object key) throws NoSuchFieldException, IllegalAccessException { + Field f = KeyValues.class.getDeclaredField("keyValues"); + f.setAccessible(true); + KeyValue[] kv = (KeyValue[]) f.get(keyValues); + for (KeyValue keyValue : kv) { + if (keyValue.getKey().equals(key)) { + return keyValue.getValue(); + } + } + return null; + } +} diff --git a/dubbo-metrics/dubbo-metrics-default/pom.xml b/dubbo-metrics/dubbo-metrics-default/pom.xml index 87ec6a3b9b..71be413c6a 100644 --- a/dubbo-metrics/dubbo-metrics-default/pom.xml +++ b/dubbo-metrics/dubbo-metrics-default/pom.xml @@ -46,6 +46,5 @@ micrometer-tracing-integration-test test - diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/DefaultMetricsServiceTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/DefaultMetricsServiceTest.java new file mode 100644 index 0000000000..5bb8c014b7 --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/DefaultMetricsServiceTest.java @@ -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 org.apache.dubbo.metrics; + +import org.apache.dubbo.common.beans.factory.ScopeBeanFactory; +import org.apache.dubbo.metrics.collector.MetricsCollector; +import org.apache.dubbo.metrics.model.MetricsCategory; +import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; +import org.apache.dubbo.metrics.model.sample.MetricSample; +import org.apache.dubbo.metrics.service.DefaultMetricsService; +import org.apache.dubbo.metrics.service.MetricsEntity; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.when; + +@SuppressWarnings("rawtypes") +public class DefaultMetricsServiceTest { + + private MetricsCollector metricsCollector; + + private DefaultMetricsService defaultMetricsService; + + + @BeforeEach + public void setUp() { + ApplicationModel applicationModel = Mockito.mock(ApplicationModel.class); + ScopeBeanFactory beanFactory = Mockito.mock(ScopeBeanFactory.class); + metricsCollector = Mockito.mock(MetricsCollector.class); + + when(applicationModel.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeansOfType(MetricsCollector.class)).thenReturn(Collections.singletonList(metricsCollector)); + + defaultMetricsService = new DefaultMetricsService(applicationModel); + } + + @Test + public void testGetMetricsByCategories() { + MetricSample sample = new GaugeMetricSample<>( + "testMetric", + "testDescription", + null, + MetricsCategory.REQUESTS, + 42, + value -> 42.0 + ); + when(metricsCollector.collect()).thenReturn(Collections.singletonList(sample)); + List categories = Collections.singletonList(MetricsCategory.REQUESTS); + + Map> result = defaultMetricsService.getMetricsByCategories(categories); + + Assertions.assertNotNull(result); + Assertions.assertEquals(1, result.size()); + List entities = result.get(MetricsCategory.REQUESTS); + Assertions.assertNotNull(entities); + Assertions.assertEquals(1, entities.size()); + + MetricsEntity entity = entities.get(0); + Assertions.assertEquals("testMetric", entity.getName()); + Assertions.assertEquals(42.0, entity.getValue()); + Assertions.assertEquals(MetricsCategory.REQUESTS, entity.getCategory()); + } +} diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java index f2275125b1..7bc56579b6 100644 --- a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/AggregateMetricsCollectorTest.java @@ -18,40 +18,53 @@ package org.apache.dubbo.metrics.collector; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.beans.factory.ScopeBeanFactory; import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.utils.ReflectionUtils; import org.apache.dubbo.config.ApplicationConfig; + import org.apache.dubbo.config.MetricsConfig; + +import org.apache.dubbo.config.context.ConfigManager; import org.apache.dubbo.config.nested.AggregationConfig; import org.apache.dubbo.metrics.TestMetricsInvoker; +import org.apache.dubbo.metrics.aggregate.TimeWindowCounter; import org.apache.dubbo.metrics.collector.sample.MethodMetricsSampler; import org.apache.dubbo.metrics.event.MetricsEvent; +import org.apache.dubbo.metrics.event.RTEvent; +import org.apache.dubbo.metrics.model.MethodMetric; import org.apache.dubbo.metrics.model.key.MetricsKey; import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; import org.apache.dubbo.metrics.model.sample.MetricSample; import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.model.ApplicationModel; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; + import java.util.stream.Collectors; -import static org.apache.dubbo.common.constants.CommonConstants.*; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_INTERFACE_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_METHOD_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_VERSION_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; +import static org.apache.dubbo.common.constants.MetricsConstants.*; +import static org.apache.dubbo.metrics.model.MetricsCategory.QPS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.spy; class AggregateMetricsCollectorTest { private ApplicationModel applicationModel; private DefaultMetricsCollector defaultCollector; - private String interfaceName; private String methodName; private String group; @@ -59,6 +72,37 @@ class AggregateMetricsCollectorTest { private RpcInvocation invocation; private String side; + public static MethodMetric getTestMethodMetric() { + + MethodMetric methodMetric = new MethodMetric(); + methodMetric.setApplicationName("TestApp"); + methodMetric.setInterfaceName("TestInterface"); + methodMetric.setMethodName("TestMethod"); + methodMetric.setGroup("TestGroup"); + methodMetric.setVersion("1.0.0"); + methodMetric.setSide("PROVIDER"); + + return methodMetric; + } + + public static AggregateMetricsCollector getTestCollector() { + + ApplicationModel applicationModel = mock(ApplicationModel.class); + ConfigManager configManager = new ConfigManager(applicationModel); + MetricsConfig metricsConfig = spy(new MetricsConfig()); + + configManager.setMetrics(metricsConfig); + + when(metricsConfig.getAggregation()).thenReturn(new AggregationConfig()); + when(applicationModel.getApplicationConfigManager()).thenReturn(configManager); + + ScopeBeanFactory beanFactory = mock(ScopeBeanFactory.class); + when(beanFactory.getBean(DefaultMetricsCollector.class)).thenReturn(new DefaultMetricsCollector()); + when(applicationModel.getBeanFactory()).thenReturn(beanFactory); + + return new AggregateMetricsCollector(applicationModel); + } + @BeforeEach public void setup() { ApplicationConfig config = new ApplicationConfig(); @@ -167,4 +211,97 @@ class AggregateMetricsCollectorTest { Assertions.assertTrue(sampleMap.containsKey(MetricsKey.METRIC_RT_P99.getNameByType(side))); Assertions.assertTrue(sampleMap.containsKey(MetricsKey.METRIC_RT_P95.getNameByType(side))); } + + @Test + public void testQPS() { + ApplicationModel applicationModel = mock(ApplicationModel.class); + ConfigManager configManager = mock(ConfigManager.class); + MetricsConfig metricsConfig = mock(MetricsConfig.class); + ScopeBeanFactory beanFactory = mock(ScopeBeanFactory.class); + AggregationConfig aggregationConfig = mock(AggregationConfig.class); + + when(applicationModel.getApplicationConfigManager()).thenReturn(configManager); + when(applicationModel.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBean(DefaultMetricsCollector.class)).thenReturn(new DefaultMetricsCollector()); + when(configManager.getMetrics()).thenReturn(Optional.of(metricsConfig)); + when(metricsConfig.getAggregation()).thenReturn(aggregationConfig); + when(aggregationConfig.getEnabled()).thenReturn(Boolean.TRUE); + + AggregateMetricsCollector collector = new AggregateMetricsCollector(applicationModel); + + MethodMetric methodMetric = getTestMethodMetric(); + + TimeWindowCounter qpsCounter = new TimeWindowCounter(10, 120); + + for (int i = 0; i < 10000; i++) { + qpsCounter.increment(); + } + + @SuppressWarnings("unchecked") + ConcurrentHashMap qps = (ConcurrentHashMap) ReflectionUtils.getField(collector, "qps"); + qps.put(methodMetric, qpsCounter); + + List collectedQPS = new ArrayList<>(); + ReflectionUtils.invoke(collector, "collectQPS", collectedQPS); + + Assertions.assertFalse(collectedQPS.isEmpty()); + Assertions.assertEquals(1, collectedQPS.size()); + + MetricSample sample = collectedQPS.get(0); + Assertions.assertEquals(MetricsKey.METRIC_QPS.getNameByType("PROVIDER"), sample.getName()); + Assertions.assertEquals(MetricsKey.METRIC_QPS.getDescription(), sample.getDescription()); + + Assertions.assertEquals(QPS, sample.getCategory()); + Assertions.assertEquals(10000, ((TimeWindowCounter) ((GaugeMetricSample) sample).getValue()).get()); + } + + @Test + void testP95AndP99() throws InterruptedException { + AggregateMetricsCollector collector = getTestCollector(); + MethodMetric methodMetric = getTestMethodMetric(); + + List requestTimes = new ArrayList<>(10000); + + for (int i = 0; i < 300; i++) { + requestTimes.add(1000 * Math.random()); + } + + Collections.sort(requestTimes); + double p95Index = 0.95 * (requestTimes.size() - 1); + double p99Index = 0.99 * (requestTimes.size() - 1); + + double manualP95 = requestTimes.get((int) Math.round(p95Index)); + double manualP99 = requestTimes.get((int) Math.round(p99Index)); + + for (Double requestTime : requestTimes) { + collector.onEvent(new RTEvent(applicationModel, methodMetric, requestTime.longValue())); + } + Thread.sleep(4000L); + + List samples = collector.collect(); + + GaugeMetricSample p95Sample = samples.stream() + .filter(sample -> sample.getName().endsWith("p95")) + .map(sample -> (GaugeMetricSample) sample) + .findFirst() + .orElse(null); + + GaugeMetricSample p99Sample = samples.stream() + .filter(sample -> sample.getName().endsWith("p99")) + .map(sample -> (GaugeMetricSample) sample) + .findFirst() + .orElse(null); + + Assertions.assertNotNull(p95Sample); + Assertions.assertNotNull(p99Sample); + + double p95 = p95Sample.applyAsDouble(); + double p99 = p99Sample.applyAsDouble(); + + //An error of less than 5% is allowed + Assertions.assertTrue(Math.abs(1 - p95 / manualP95) < 0.05); + Assertions.assertTrue(Math.abs(1 - p99 / manualP99) < 0.05); + } + } + diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/ConfigCenterMetricsCollectorTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/ConfigCenterMetricsCollectorTest.java index e234279339..544dbe7f0c 100644 --- a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/ConfigCenterMetricsCollectorTest.java +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/ConfigCenterMetricsCollectorTest.java @@ -20,7 +20,6 @@ package org.apache.dubbo.metrics.collector; import org.apache.dubbo.common.config.configcenter.ConfigChangeType; import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; import org.apache.dubbo.config.ApplicationConfig; -import org.apache.dubbo.metrics.model.ConfigCenterMetric; import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; import org.apache.dubbo.metrics.model.sample.MetricSample; import org.apache.dubbo.rpc.model.ApplicationModel; @@ -32,14 +31,8 @@ import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; -import java.util.function.Supplier; import static org.apache.dubbo.common.constants.MetricsConstants.TAG_APPLICATION_NAME; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_INTERFACE_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_METHOD_KEY; -import static org.apache.dubbo.common.constants.MetricsConstants.TAG_VERSION_KEY; -import static org.junit.jupiter.api.Assertions.*; class ConfigCenterMetricsCollectorTest { diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/sample/ThreadPoolMetricsSamplerTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/sample/ThreadPoolMetricsSamplerTest.java new file mode 100644 index 0000000000..4b336d91f6 --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/collector/sample/ThreadPoolMetricsSamplerTest.java @@ -0,0 +1,180 @@ +/* + * 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 org.apache.dubbo.metrics.collector.sample; + +import org.apache.dubbo.common.beans.factory.ScopeBeanFactory; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.store.DataStore; +import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository; +import org.apache.dubbo.metrics.collector.DefaultMetricsCollector; +import org.apache.dubbo.metrics.model.ThreadPoolMetric; +import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; +import org.apache.dubbo.metrics.model.sample.MetricSample; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + +import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER_SHARED_EXECUTOR_SERVICE_COMPONENT_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.EXECUTOR_SERVICE_COMPONENT_KEY; +import static org.mockito.Mockito.when; + +@SuppressWarnings("all") +public class ThreadPoolMetricsSamplerTest { + + ThreadPoolMetricsSampler sampler; + + @BeforeEach + void setUp() { + DefaultMetricsCollector collector = new DefaultMetricsCollector(); + sampler = new ThreadPoolMetricsSampler(collector); + } + + @Test + void testSample() { + + ExecutorService executorService = java.util.concurrent.Executors.newFixedThreadPool(5); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService; + sampler.addExecutors("testPool", executorService); + + List metricSamples = sampler.sample(); + + Assertions.assertEquals(6, metricSamples.size()); + + boolean coreSizeFound = false; + boolean maxSizeFound = false; + boolean activeSizeFound = false; + boolean threadCountFound = false; + boolean queueSizeFound = false; + boolean largestSizeFound = false; + + for (MetricSample sample : metricSamples) { + ThreadPoolMetric threadPoolMetric = ((ThreadPoolMetric) ((GaugeMetricSample) sample).getValue()); + switch (sample.getName()) { + case "dubbo.thread.pool.core.size": + coreSizeFound = true; + Assertions.assertEquals(5, threadPoolMetric.getCorePoolSize()); + break; + case "dubbo.thread.pool.largest.size": + largestSizeFound = true; + Assertions.assertEquals(0, threadPoolMetric.getLargestPoolSize()); + break; + case "dubbo.thread.pool.max.size": + maxSizeFound = true; + Assertions.assertEquals(5, threadPoolMetric.getMaximumPoolSize()); + break; + case "dubbo.thread.pool.active.size": + activeSizeFound = true; + Assertions.assertEquals(0, threadPoolMetric.getActiveCount()); + break; + case "dubbo.thread.pool.thread.count": + threadCountFound = true; + Assertions.assertEquals(0, threadPoolMetric.getPoolSize()); + break; + case "dubbo.thread.pool.queue.size": + queueSizeFound = true; + Assertions.assertEquals(0, threadPoolMetric.getQueueSize()); + break; + } + } + + Assertions.assertTrue(coreSizeFound); + Assertions.assertTrue(maxSizeFound); + Assertions.assertTrue(activeSizeFound); + Assertions.assertTrue(threadCountFound); + Assertions.assertTrue(queueSizeFound); + Assertions.assertTrue(largestSizeFound); + + executorService.shutdown(); + } + + private DefaultMetricsCollector collector; + + private ThreadPoolMetricsSampler sampler2; + + @Mock + private ApplicationModel applicationModel; + + @Mock + ScopeBeanFactory scopeBeanFactory; + + @Mock + private DataStore dataStore; + + @Mock + private FrameworkExecutorRepository frameworkExecutorRepository; + + @Mock + private ExtensionLoader extensionLoader; + + @BeforeEach + public void setUp2() { + MockitoAnnotations.openMocks(this); + + collector = new DefaultMetricsCollector(); + sampler2 = new ThreadPoolMetricsSampler(collector); + + when(scopeBeanFactory.getBean(FrameworkExecutorRepository.class)).thenReturn(new FrameworkExecutorRepository()); + + collector.collectApplication(applicationModel); + when(applicationModel.getBeanFactory()).thenReturn(scopeBeanFactory); + when(applicationModel.getExtensionLoader(DataStore.class)).thenReturn(extensionLoader); + when(extensionLoader.getDefaultExtension()).thenReturn(dataStore); + } + + @Test + public void testRegistryDefaultSampleThreadPoolExecutor() throws NoSuchFieldException, IllegalAccessException { + + Map serverExecutors = new HashMap<>(); + Map clientExecutors = new HashMap<>(); + + ExecutorService serverExecutor = Executors.newFixedThreadPool(5); + ExecutorService clientExecutor = Executors.newFixedThreadPool(5); + + serverExecutors.put("server1", serverExecutor); + clientExecutors.put("client1", clientExecutor); + + when(dataStore.get(EXECUTOR_SERVICE_COMPONENT_KEY)).thenReturn(serverExecutors); + when(dataStore.get(CONSUMER_SHARED_EXECUTOR_SERVICE_COMPONENT_KEY)).thenReturn(clientExecutors); + + when(frameworkExecutorRepository.getSharedExecutor()).thenReturn(Executors.newFixedThreadPool(5)); + + sampler2.registryDefaultSampleThreadPoolExecutor(); + + Field f = ThreadPoolMetricsSampler.class.getDeclaredField("sampleThreadPoolExecutor"); + f.setAccessible(true); + Map executors = (Map) f.get(sampler2); + + Assertions.assertEquals(3, executors.size()); + Assertions.assertTrue(executors.containsKey("DubboServerHandler-server1")); + Assertions.assertTrue(executors.containsKey("DubboClientHandler-client1")); + Assertions.assertTrue(executors.containsKey("sharedExecutor")); + + serverExecutor.shutdown(); + clientExecutor.shutdown(); + } +} diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/sampler/MethodMetricsTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/sampler/MethodMetricsTest.java new file mode 100644 index 0000000000..9e4eff6941 --- /dev/null +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/sampler/MethodMetricsTest.java @@ -0,0 +1,132 @@ +/* + * 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 org.apache.dubbo.metrics.sampler; + +import org.apache.dubbo.metrics.collector.DefaultMetricsCollector; +import org.apache.dubbo.metrics.collector.sample.MethodMetricsSampler; +import org.apache.dubbo.metrics.event.MetricsEvent; +import org.apache.dubbo.metrics.model.key.MetricsKey; +import org.apache.dubbo.metrics.model.sample.CounterMetricSample; +import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; +import org.apache.dubbo.metrics.model.sample.MetricSample; +import org.apache.dubbo.metrics.observation.MockInvocation; +import org.apache.dubbo.rpc.Invocation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class MethodMetricsTest { + + private DefaultMetricsCollector collector; + private MethodMetricsSampler sampler; + + private Invocation invocation; + + @BeforeEach + public void setUp() { + collector = new DefaultMetricsCollector(); + sampler = new MethodMetricsSampler(collector); + invocation = spy(new MockInvocation()); + when(invocation.getTargetServiceUniqueName()).thenReturn("TestService-1"); + } + + @Test + void testRequestsCount() { + final long requestTimes = 1000L; + + //METRIC_REQUESTS + for (long i = 0; i < requestTimes; i++) { + sampler.inc(invocation, MetricsEvent.Type.TOTAL.getNameByType("provider")); + } + + List samples = sampler.sample(); + + MetricSample requestsSample = samples.stream() + .filter(sample -> MetricsKey.METRIC_REQUESTS.getNameByType("provider").equals(sample.getName())) + .findFirst() + .orElse(null); + + Assertions.assertNotNull(requestsSample, "METRIC_REQUESTS sample should not be null"); + Assertions.assertEquals(MetricSample.Type.COUNTER, requestsSample.getType(), "METRIC_REQUESTS sample should have a COUNTER type"); + Assertions.assertTrue(requestsSample instanceof CounterMetricSample); + Assertions.assertEquals(requestTimes, ((CounterMetricSample) requestsSample).getValue().longValue()); + } + + @Test + void testRequestsProcessing() { + final long requestTimes = 1000L; + + //METRIC_REQUESTS + for (long i = 0; i < requestTimes; i++) { + sampler.inc(invocation, MetricsEvent.Type.PROCESSING.getNameByType("provider")); + } + + List samples = sampler.sample(); + + MetricSample requestsSample = samples.stream() + .filter(sample -> MetricsKey.METRIC_REQUESTS_PROCESSING.getNameByType("provider").equals(sample.getName())) + .findFirst() + .orElse(null); + + Assertions.assertNotNull(requestsSample, "METRIC_REQUESTS_PROCESSING sample should not be null"); + Assertions.assertEquals(MetricSample.Type.GAUGE, requestsSample.getType(), "METRIC_REQUESTS_PROCESSING sample should have a GAUGE type"); + Assertions.assertTrue(requestsSample instanceof GaugeMetricSample); + Assertions.assertEquals(requestTimes, ((AtomicLong) ((GaugeMetricSample) requestsSample).getValue()).get()); + + for (long i = 0; i < requestTimes; i++) { + sampler.dec(invocation, MetricsEvent.Type.PROCESSING.getNameByType("provider")); + } + + samples = sampler.sample(); + + requestsSample = samples.stream() + .filter(sample -> MetricsKey.METRIC_REQUESTS_PROCESSING.getNameByType("provider").equals(sample.getName())) + .findFirst() + .orElse(null); + + Assertions.assertEquals(0, ((AtomicLong) ((GaugeMetricSample) requestsSample).getValue()).get()); + } + + @Test + void testRequestSucceed() { + final long requestTimes = 1000L; + + //METRIC_REQUESTS_SUCCEED + for (long i = 0; i < requestTimes; i++) { + sampler.inc(invocation, MetricsEvent.Type.SUCCEED.getNameByType("provider")); + } + + List samples = sampler.sample(); + + MetricSample requestsSample = samples.stream() + .filter(sample -> MetricsKey.METRIC_REQUESTS_SUCCEED.getNameByType("provider").equals(sample.getName())) + .findFirst() + .orElse(null); + + Assertions.assertNotNull(requestsSample, "METRIC_REQUESTS_SUCCEED sample should not be null"); + Assertions.assertEquals(MetricSample.Type.COUNTER, requestsSample.getType(), "METRIC_REQUESTS_SUCCEED sample should have a COUNTER type"); + Assertions.assertTrue(requestsSample instanceof CounterMetricSample); + Assertions.assertEquals(requestTimes, ((AtomicLong) ((CounterMetricSample) requestsSample).getValue()).get()); + } + +} diff --git a/dubbo-metrics/dubbo-metrics-registry/src/test/java/org/apache/dubbo/metrics/registry/metrics/collector/RegistryStatCompositeTest.java b/dubbo-metrics/dubbo-metrics-registry/src/test/java/org/apache/dubbo/metrics/registry/metrics/collector/RegistryStatCompositeTest.java index ba44ab6c15..b1a34e7e32 100644 --- a/dubbo-metrics/dubbo-metrics-registry/src/test/java/org/apache/dubbo/metrics/registry/metrics/collector/RegistryStatCompositeTest.java +++ b/dubbo-metrics/dubbo-metrics-registry/src/test/java/org/apache/dubbo/metrics/registry/metrics/collector/RegistryStatCompositeTest.java @@ -21,15 +21,22 @@ import org.apache.dubbo.metrics.data.ApplicationStatComposite; import org.apache.dubbo.metrics.data.BaseStatComposite; import org.apache.dubbo.metrics.data.RtStatComposite; import org.apache.dubbo.metrics.data.ServiceStatComposite; +import org.apache.dubbo.metrics.model.MetricsCategory; import org.apache.dubbo.metrics.model.container.LongContainer; import org.apache.dubbo.metrics.registry.RegistryMetricsConstants; +import org.apache.dubbo.metrics.model.sample.GaugeMetricSample; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import static org.apache.dubbo.metrics.model.key.MetricsKey.METRIC_RT_AVG; +import static org.apache.dubbo.metrics.model.key.MetricsKey.METRIC_RT_MAX; +import static org.apache.dubbo.metrics.model.key.MetricsKey.METRIC_RT_MIN; import static org.apache.dubbo.metrics.model.key.MetricsKey.REGISTER_METRIC_REQUESTS; import static org.apache.dubbo.metrics.registry.RegistryMetricsConstants.OP_TYPE_NOTIFY; import static org.apache.dubbo.metrics.registry.RegistryMetricsConstants.OP_TYPE_REGISTER; @@ -37,6 +44,7 @@ import static org.apache.dubbo.metrics.registry.RegistryMetricsConstants.OP_TYPE import static org.apache.dubbo.metrics.registry.RegistryMetricsConstants.OP_TYPE_SUBSCRIBE; import static org.apache.dubbo.metrics.registry.RegistryMetricsConstants.OP_TYPE_SUBSCRIBE_SERVICE; + public class RegistryStatCompositeTest { private final String applicationName = "app1"; @@ -77,4 +85,42 @@ public class RegistryStatCompositeTest { Optional> subContainer = statComposite.getRtStatComposite().getRtStats().stream().filter(longContainer -> longContainer.specifyType(OP_TYPE_NOTIFY.getType())).findFirst(); subContainer.ifPresent(v -> Assertions.assertEquals(10L, v.get(applicationName).longValue())); } + + @Test + @SuppressWarnings("rawtypes") + void testCalcServiceKeyRt() { + String applicationName = "TestApp"; + String serviceKey = "TestService"; + String registryOpType = OP_TYPE_REGISTER_SERVICE.getType(); + Long responseTime1 = 100L; + Long responseTime2 = 200L; + + statComposite.calcServiceKeyRt(applicationName, serviceKey, registryOpType, responseTime1); + statComposite.calcServiceKeyRt(applicationName, serviceKey, registryOpType, responseTime2); + + List exportedRtMetrics = statComposite.export(MetricsCategory.RT); + + GaugeMetricSample minSample = exportedRtMetrics.stream() + .filter(sample -> sample.getTags().containsValue(applicationName)) + .filter(sample -> sample.getName().equals(METRIC_RT_MIN.getNameByType("register.service"))) + .findFirst().orElse(null); + GaugeMetricSample maxSample = exportedRtMetrics.stream() + .filter(sample -> sample.getTags().containsValue(applicationName)) + .filter(sample -> sample.getName().equals(METRIC_RT_MAX.getNameByType("register.service"))) + .findFirst().orElse(null); + GaugeMetricSample avgSample = exportedRtMetrics.stream() + .filter(sample -> sample.getTags().containsValue(applicationName)) + .filter(sample -> sample.getName().equals(METRIC_RT_AVG.getNameByType("register.service"))) + .findFirst().orElse(null); + + Assertions.assertNotNull(minSample); + Assertions.assertNotNull(maxSample); + Assertions.assertNotNull(avgSample); + + Assertions.assertEquals(responseTime1, minSample.applyAsLong()); + Assertions.assertEquals(responseTime2, maxSample.applyAsLong()); + Assertions.assertEquals((responseTime1 + responseTime2) / 2, avgSample.applyAsLong()); + } + + }