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
This commit is contained in:
namelessssssssssss 2023-04-19 12:41:53 +08:00 committed by GitHub
parent 1b838281a4
commit 742e035a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 875 additions and 15 deletions

View File

@ -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.
* <p>
* 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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&timestamp=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;
}
}

View File

@ -46,6 +46,5 @@
<artifactId>micrometer-tracing-integration-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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<MetricsCategory> categories = Collections.singletonList(MetricsCategory.REQUESTS);
Map<MetricsCategory, List<MetricsEntity>> result = defaultMetricsService.getMetricsByCategories(categories);
Assertions.assertNotNull(result);
Assertions.assertEquals(1, result.size());
List<MetricsEntity> 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());
}
}

View File

@ -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<MethodMetric, TimeWindowCounter> qps = (ConcurrentHashMap<MethodMetric, TimeWindowCounter>) ReflectionUtils.getField(collector, "qps");
qps.put(methodMetric, qpsCounter);
List<MetricSample> 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<Double> 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<MetricSample> 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);
}
}

View File

@ -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 {

View File

@ -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<MetricSample> 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<DataStore> 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<String, Object> serverExecutors = new HashMap<>();
Map<String, Object> 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<String,ThreadPoolExecutor> executors = (Map<String, ThreadPoolExecutor>) 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();
}
}

View File

@ -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<MetricSample> 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<MetricSample> 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<MetricSample> 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());
}
}

View File

@ -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<LongContainer<? extends Number>> 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<GaugeMetricSample> 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());
}
}