diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/README.md b/4-governance/dubbo-samples-metrics-prometheus-springboot3/README.md new file mode 100644 index 0000000000..5d92dbd354 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/README.md @@ -0,0 +1,33 @@ +# 使用Metrics模块进行数据采集然后暴漏数据给普罗米修斯监控 +Dubbo使用开源的[Dubbo Metrics](https://github.com/alibaba/metrics)进行数据埋点,并且通过服务暴露,使用的时候,首先需要进行配置: + +* 依赖(其中Dubbo版本在3.2.0及以后) +```xml + + org.apache.dubbo + dubbo-spring-boot-observability-starter + + +``` +* 服务端 +```xml + + + + +``` + +* 客户端 +```xml + + + + +``` +先启动服务端,然后启动客户端, +提供端监控指标:http://localhost:20888/metrics +消费端监控指标:http://localhost:20889/metrics + + +可观测性文档如下链接: + [https://cn.dubbo.apache.org/zh-cn/overview/tasks/observability/metrics-start/](https://cn.dubbo.apache.org/zh-cn/overview/tasks/observability/metrics-start/) diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-configuration.yml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-configuration.yml new file mode 100644 index 0000000000..8b055d25a9 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-configuration.yml @@ -0,0 +1,37 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +services: + metrics-prometheus-provider-springboot3: + type: app + basedir: dubbo-samples-metrics-prometheus-provider-springboot3 + mainClass: org.apache.dubbo.samples.metrics.prometheus.provider.MetricsProvider + + metrics-prometheus-consumer-springboot3: + type: test + basedir: dubbo-samples-metrics-prometheus-consumer-springboot3 + tests: + - "**/*IT.class" + systemProps: + - zookeeper.address=metrics-prometheus-provider-springboot3 + - zookeeper.port=2181 + - dubbo.address=metrics-prometheus-provider-springboot3 + - dubbo.port=20880 + waitPortsBeforeRun: + - metrics-prometheus-provider-springboot3:2181 + - metrics-prometheus-provider-springboot3:20880 + depends_on: + - metrics-prometheus-provider-springboot3 diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-versions.conf b/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-versions.conf new file mode 100644 index 0000000000..7b341dc629 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/case-versions.conf @@ -0,0 +1,25 @@ +# +# +# 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. +# + + +# Supported component versions of the test case + +# Spring app +dubbo.version=[ >= 3.3.7 ] +spring.version=6.* +java.version= [>= 17] diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/pom.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/pom.xml new file mode 100644 index 0000000000..53a9a9ab67 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/pom.xml @@ -0,0 +1,110 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-samples-metrics-prometheus-springboot3 + 1.0-SNAPSHOT + + + dubbo-samples-metrics-prometheus-consumer-springboot3 + dubbo-samples-metrics-prometheus-consumer-springboot3 + + + + org.apache.dubbo + dubbo-zookeeper-curator5-spring-boot-starter + + + + org.apache.dubbo + dubbo-samples-metrics-prometheus-interface-springboot3 + ${project.parent.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents + httpclient + test + + + + io.dropwizard.metrics + metrics-core + 4.1.12.1 + + + org.xerial.snappy + snappy-java + 1.1.10.5 + + + + io.micrometer + micrometer-registry-prometheus + + + org.apache.dubbo + dubbo-metrics-prometheus + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.6.14 + + org.apache.dubbo.samples.metrics.prometheus.consumer.MetricsConsumer + + + + + repackage + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jar-file + package + + run + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/EmbeddedZooKeeper.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/EmbeddedZooKeeper.java new file mode 100644 index 0000000000..f03955cbea --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/EmbeddedZooKeeper.java @@ -0,0 +1,304 @@ + +/* + * Copyright 2014 the original author or authors. + * + * Licensed 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.samples.metrics.prometheus.consumer; + +import org.apache.zookeeper.server.ServerConfig; +import org.apache.zookeeper.server.ZooKeeperServerMain; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.SmartLifecycle; +import org.springframework.util.ErrorHandler; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * from: https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java + *

+ * Helper class to start an embedded instance of standalone (non clustered) ZooKeeper. + *

+ * NOTE: at least an external standalone server (if not an ensemble) are recommended, even for + */ +public class EmbeddedZooKeeper implements SmartLifecycle { + + private static final Random RANDOM = new Random(); + + /** + * Logger. + */ + private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class); + + /** + * ZooKeeper client port. This will be determined dynamically upon startup. + */ + private final int clientPort; + + /** + * Whether to auto-start. Default is true. + */ + private boolean autoStartup = true; + + /** + * Lifecycle phase. Default is 0. + */ + private int phase = 0; + + /** + * Thread for running the ZooKeeper server. + */ + private volatile Thread zkServerThread; + + /** + * ZooKeeper server. + */ + private volatile ZooKeeperServerMain zkServer; + + /** + * {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. + */ + private ErrorHandler errorHandler; + + private boolean daemon = true; + + /** + * Construct an EmbeddedZooKeeper with a random port. + */ + public EmbeddedZooKeeper() { + clientPort = findRandomPort(30000, 65535); + } + + /** + * Construct an EmbeddedZooKeeper with the provided port. + * + * @param clientPort port for ZooKeeper server to bind to + */ + public EmbeddedZooKeeper(int clientPort, boolean daemon) { + this.clientPort = clientPort; + this.daemon = daemon; + } + + /** + * Returns the port that clients should use to connect to this embedded server. + * + * @return dynamically determined client port + */ + public int getClientPort() { + return this.clientPort; + } + + /** + * Specify whether to start automatically. Default is true. + * + * @param autoStartup whether to start automatically + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Specify the lifecycle phase for the embedded server. + * + * @param phase the lifecycle phase + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPhase() { + return this.phase; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return (zkServerThread != null); + } + + /** + * Start the ZooKeeper server in a background thread. + *

+ * Register an error handler via {@link #setErrorHandler} in order to handle + * any exceptions thrown during startup or execution. + */ + @Override + public synchronized void start() { + if (zkServerThread == null) { + zkServerThread = new Thread(new ServerRunnable(), "ZooKeeper Server Starter"); + zkServerThread.setDaemon(daemon); + zkServerThread.start(); + } + } + + /** + * Shutdown the ZooKeeper server. + */ + @Override + public synchronized void stop() { + if (zkServerThread != null) { + // The shutdown method is protected...thus this hack to invoke it. + // This will log an exception on shutdown; see + // https://issues.apache.org/jira/browse/ZOOKEEPER-1873 for details. + try { + Method shutdown = ZooKeeperServerMain.class.getDeclaredMethod("shutdown"); + shutdown.setAccessible(true); + shutdown.invoke(zkServer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // It is expected that the thread will exit after + // the server is shutdown; this will block until + // the shutdown is complete. + try { + zkServerThread.join(5000); + zkServerThread = null; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.warn("Interrupted while waiting for embedded ZooKeeper to exit"); + // abandoning zk thread + zkServerThread = null; + } + } + } + + /** + * Stop the server if running and invoke the callback when complete. + */ + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + /** + * Provide an {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. If none + * is provided, only error-level logging will occur. + * + * @param errorHandler the {@link ErrorHandler} to be invoked + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Runnable implementation that starts the ZooKeeper server. + */ + private class ServerRunnable implements Runnable { + + @Override + public void run() { + try { + Properties properties = new Properties(); + File file = new File(System.getProperty("java.io.tmpdir") + + File.separator + UUID.randomUUID()); + file.deleteOnExit(); + properties.setProperty("dataDir", file.getAbsolutePath()); + properties.setProperty("clientPort", String.valueOf(clientPort)); + + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + quorumPeerConfig.parseProperties(properties); + + zkServer = new ZooKeeperServerMain(); + ServerConfig configuration = new ServerConfig(); + configuration.readFrom(quorumPeerConfig); + + System.setProperty("zookeeper.admin.enableServer", "false"); + + zkServer.runFromConfig(configuration); + } catch (Exception e) { + if (errorHandler != null) { + errorHandler.handleError(e); + } else { + logger.error("Exception running embedded ZooKeeper", e); + } + } + } + } + + /** + * Workaround for SocketUtils.findRandomPort() deprecation. + * + * @param min min port + * @param max max port + * @return a random generated available port + */ + private static int findRandomPort(int min, int max) { + if (min < 1024) { + throw new IllegalArgumentException("Max port shouldn't be less than 1024."); + } + + if (max > 65535) { + throw new IllegalArgumentException("Max port shouldn't be greater than 65535."); + } + + if (min > max) { + throw new IllegalArgumentException("Min port shouldn't be greater than max port."); + } + + int port = 0; + int counter = 0; + + // Workaround for legacy JDK doesn't support Random.nextInt(min, max). + List randomInts = RANDOM.ints(min, max + 1) + .limit(max - min) + .mapToObj(Integer::valueOf) + .collect(Collectors.toList()); + + do { + if (counter > max - min) { + throw new IllegalStateException("Unable to find a port between " + min + "-" + max); + } + + port = randomInts.get(counter); + counter++; + } while (isPortInUse(port)); + + return port; + } + + private static boolean isPortInUse(int port) { + try (ServerSocket ignored = new ServerSocket(port)) { + return false; + } catch (IOException e) { + // continue + } + return true; + } +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/MetricsConsumer.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/MetricsConsumer.java new file mode 100644 index 0000000000..212437e280 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/consumer/MetricsConsumer.java @@ -0,0 +1,62 @@ +/* + * 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.samples.metrics.prometheus.consumer; + +import org.apache.dubbo.samples.metrics.prometheus.api.DemoService; +import org.apache.dubbo.samples.metrics.prometheus.api.DemoService2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + + +public class MetricsConsumer { + + private static Logger logger = LoggerFactory.getLogger(MetricsConsumer.class); + + + public static void main(String[] args) { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dubbo-demo-consumer.xml"); + DemoService demoService = ctx.getBean(DemoService.class); + DemoService2 demoService2 = ctx.getBean(DemoService2.class); + while (true) { + // Calling dubbo services immediately to generate metrics data as soon as possible. + logger.info(demoService.sayHello("Dubbo").getMsg()); + try { + logger.info(demoService.randomResponseTime("Dubbo").getMsg()); + } catch (Exception e) { + logger.error("randomResponseTime failed: ", e); + } + try { + logger.info(demoService.runTimeException("Dubbo").getMsg()); + } catch (Exception e) { + logger.error("runTimeException failed: ", e); + } + try { + logger.info(demoService.timeLimitedMethod("Dubbo").getMsg()); + } catch (Exception e) { + logger.error("timeLimitedMethod failed: ", e); + } + try { + Thread.sleep(1000); + } catch (Exception e) { + logger.error("sleep failed: ", e); + } + } + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/dubbo-demo-consumer.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/dubbo-demo-consumer.xml new file mode 100644 index 0000000000..299678f3cb --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/dubbo-demo-consumer.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/log4j2.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..69e1321d22 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/log4j2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/security/serialize.allowlist b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/security/serialize.allowlist new file mode 100644 index 0000000000..a40ac9e3b0 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/main/resources/security/serialize.allowlist @@ -0,0 +1,19 @@ +# +# +# 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. +# +# +java.lang.ArithmeticException diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/consumer/ConsumerMetricsIT.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/consumer/ConsumerMetricsIT.java new file mode 100644 index 0000000000..fa29fc3e60 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-consumer-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/consumer/ConsumerMetricsIT.java @@ -0,0 +1,173 @@ +/* + * 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.samples.metrics.prometheus.consumer; + +import org.apache.dubbo.samples.metrics.prometheus.util.PrometheusMetricAssert; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +public class ConsumerMetricsIT { + private static final Logger logger = LoggerFactory.getLogger(ConsumerMetricsIT.class); + + private final String port = "20889"; + + private final List metricExpectations = new ArrayList<>(); + + @Before + public void setUp() { + addCounter("dubbo.application.info.total"); + + //config + addGauge("dubbo.configcenter.total"); + + addGauge("dubbo.provider.rt.milliseconds.p99"); + addGauge("dubbo.provider.requests.total.aggregate"); + addGauge("dubbo.provider.rt.milliseconds.p90"); + addGauge("dubbo.provider.rt.max.milliseconds.aggregate"); + addCounter("dubbo.provider.requests.succeed.total"); + addGauge("dubbo.provider.requests.business.failed.aggregate"); + addCounter("dubbo.provider.requests.business.failed.total"); + addCounter("dubbo.provider.requests.total"); + addGauge("dubbo.provider.requests.failed.service.unavailable.total.aggregate"); + addGauge("dubbo.provider.requests.limit.aggregate"); + addGauge("dubbo.provider.qps.total"); + addGauge("dubbo.provider.rt.milliseconds.p50"); + addGauge("dubbo.provider.requests.processing.total"); + addCounter("dubbo.provider.requests.failed.total"); + addGauge("dubbo.provider.requests.succeed.aggregate"); + addGauge("dubbo.provider.rt.milliseconds.p95"); + addGauge("dubbo.provider.requests.failed.aggregate"); + addGauge("dubbo.provider.rt.avg.milliseconds.aggregate"); + addGauge("dubbo.provider.requests.failed.total.aggregate"); + addGauge("dubbo.provider.requests.failed.network.total.aggregate"); + addGauge("dubbo.provider.rt.min.milliseconds.aggregate"); + addGauge("dubbo.provider.requests.failed.codec.total.aggregate"); + addGauge("dubbo.provider.requests.timeout.failed.aggregate"); + + //consumer + addCounter("dubbo.consumer.requests.failed.service.unavailable.total"); + + //register + addGauge("dubbo.registry.subscribe.num.total"); + addGauge("dubbo.registry.register.requests.succeed.total"); + addGauge("dubbo.register.rt.milliseconds.last"); + addGauge("dubbo.registry.register.requests.total"); + addGauge("dubbo.register.rt.milliseconds.avg"); + addGauge("dubbo.registry.subscribe.num.succeed.total"); + addGauge("dubbo.register.rt.milliseconds.max"); + addGauge("dubbo.register.rt.milliseconds.min"); + addGauge("dubbo.register.rt.milliseconds.sum"); + addGauge("dubbo.registry.notify.requests.total"); + addGauge("dubbo.registry.register.requests.failed.total"); + addGauge("dubbo.registry.subscribe.num.failed.total"); + addGauge("dubbo.register.rt.milliseconds.max"); + addGauge("dubbo.registry.register.requests.succeed.total"); + + //metadata + addGauge("dubbo.metadata.subscribe.num.failed.total"); + addGauge("dubbo.metadata.push.num.failed.total"); + addGauge("dubbo.metadata.subscribe.num.total"); + addGauge("dubbo.metadata.subscribe.num.succeed.total"); + addGauge("dubbo.metadata.push.num.succeed.total"); + addGauge("dubbo.metadata.push.num.total"); + + //thread + addGauge("dubbo.thread.pool.thread.count"); + addGauge("dubbo.thread.pool.largest.size"); + addGauge("dubbo.thread.pool.active.size"); + addGauge("dubbo.thread.pool.queue.size"); + addGauge("dubbo.thread.pool.core.size"); + addGauge("dubbo.thread.pool.max.size"); + } + + // Use explicit kind only when it changes cross-registry compatible names. + // Example: `dubbo.application.info.total` resolves to `dubbo_application_total` on new clients. + private void addCounter(String rawName) { + metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.COUNTER)); + } + + // Example: `...qps.total` is exposed without `_total` on new clients when treated as a gauge. + private void addGauge(String rawName) { + metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.GAUGE)); + } + + @Test + public void test() throws Exception { + new EmbeddedZooKeeper(2181, false).start(); + + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-consumer.xml"); + + List notExistedList = new ArrayList<>(); + HttpGet request = new HttpGet("http://localhost:" + port + "/metrics"); + try (CloseableHttpClient client = HttpClients.createDefault()) { + // retry 3 times as all metrics data are not collected immediately. + for (int retryTime = 0; retryTime < 3; retryTime++) { + notExistedList.clear(); + CloseableHttpResponse response = client.execute(request); + InputStream inputStream = response.getEntity().getContent(); + String text = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining("\n")); + for (MetricExpectation expectation : metricExpectations) { + try { + PrometheusMetricAssert.assertDubboMetricExposed(text, expectation.rawName, expectation.kind); + } catch (AssertionError ignored) { + notExistedList.add(expectation); + } + } + if (notExistedList.isEmpty()) { + break; + } + Thread.sleep(1000); + } + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + context.stop(); + for (MetricExpectation metric : notExistedList) { + logger.error("metric rawName:{} with kind:{} doesn't exist", metric.rawName, metric.kind); + } + Assert.assertTrue(notExistedList.isEmpty()); + } + + private static final class MetricExpectation { + private final String rawName; + private final PrometheusMetricAssert.MetricKind kind; + + private MetricExpectation(String rawName, PrometheusMetricAssert.MetricKind kind) { + this.rawName = rawName; + this.kind = kind; + } + } +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/pom.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/pom.xml new file mode 100644 index 0000000000..8e66043cf5 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-samples-metrics-prometheus-springboot3 + 1.0-SNAPSHOT + + + dubbo-samples-metrics-prometheus-interface-springboot3 + dubbo-samples-metrics-prometheus-interface-springboot3 + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService.java new file mode 100644 index 0000000000..3f93d0c382 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService.java @@ -0,0 +1,43 @@ +/* + * 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.samples.metrics.prometheus.api; + +import org.apache.dubbo.samples.metrics.prometheus.api.model.Result; +import org.apache.dubbo.samples.metrics.prometheus.api.model.User; + +import java.util.concurrent.CompletableFuture; + +public interface DemoService { + + CompletableFuture sayHello(); + + Result sayHello(String name); + + Result sayHello(Long id, String name); + + Result sayHello(User user); + + String stringArray(String[] bytes); + + Result timeLimitedMethod(String name) throws InterruptedException; + + Result randomResponseTime(String name) throws InterruptedException; + + Result runTimeException(String name); + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService2.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService2.java new file mode 100644 index 0000000000..462c5ab5e2 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/DemoService2.java @@ -0,0 +1,43 @@ +/* + * 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.samples.metrics.prometheus.api; + +import org.apache.dubbo.samples.metrics.prometheus.api.model.Result; +import org.apache.dubbo.samples.metrics.prometheus.api.model.User; + +import java.util.concurrent.CompletableFuture; + +public interface DemoService2 { + + CompletableFuture sayHello(); + + Result sayHello(String name); + + Result sayHello(Long id, String name); + + Result sayHello(User user); + + String stringArray(String[] bytes); + + Result timeLimitedMethod(String name) throws InterruptedException; + + Result randomResponseTime(String name) throws InterruptedException; + + Result runTimeException(String name); + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/Result.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/Result.java new file mode 100644 index 0000000000..955123ad30 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/Result.java @@ -0,0 +1,51 @@ +/* + * 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.samples.metrics.prometheus.api.model; + +import java.io.Serializable; + +public class Result implements Serializable { + + public Result() { + } + + public Result(String userName, String msg) { + this.msg = msg; + this.userName = userName; + } + + private String userName; + private String msg; + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/User.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/User.java new file mode 100644 index 0000000000..6093e0c45f --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/api/model/User.java @@ -0,0 +1,51 @@ +/* + * 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.samples.metrics.prometheus.api.model; + +import java.io.Serializable; + +public class User implements Serializable { + + private Long id; + private String username; + + public User() { + } + + public User(final Long id, final String username) { + this.id = id; + this.username = username; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/util/PrometheusMetricAssert.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/util/PrometheusMetricAssert.java new file mode 100644 index 0000000000..019edc181b --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-interface-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/util/PrometheusMetricAssert.java @@ -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 org.apache.dubbo.samples.metrics.prometheus.util; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public final class PrometheusMetricAssert { + + private static final String[] RESERVED_SUFFIXES = { + "_total", "_created", "_bucket", "_info", + ".total", ".created", ".bucket", ".info" + }; + + private PrometheusMetricAssert() { + } + + public enum MetricKind { + COUNTER, + GAUGE, + INFO, + HISTOGRAM, + SUMMARY, + UNKNOWN + } + + public static void assertDubboMetricExposed(String scrapeText, String dubboRawName, MetricKind kind) { + Set compatibleNames = resolveCompatibleNames(dubboRawName, kind); + boolean matched = compatibleNames.stream().anyMatch(name -> containsMetricLine(scrapeText, name)); + if (!matched) { + throw new AssertionError("Expected dubbo metric to be exposed. rawName=" + dubboRawName + + ", kind=" + kind + + ", compatibleNames=" + compatibleNames); + } + } + + public static Set resolveCompatibleNames(String dubboRawName, MetricKind kind) { + Set names = new LinkedHashSet<>(); + names.add(legacyPrometheusName(dubboRawName, kind)); + names.add(newPrometheusName(dubboRawName, kind)); + return names; + } + + private static boolean containsMetricLine(String scrapeText, String metricName) { + Pattern pattern = Pattern.compile("(?m)^" + Pattern.quote(metricName) + "(\\{|\\s).*"); + return pattern.matcher(scrapeText).find(); + } + + private static String legacyPrometheusName(String rawName, MetricKind kind) { + String conventionName = snakeCase(rawName); + if (kind == MetricKind.COUNTER && !conventionName.endsWith("_total")) { + conventionName += "_total"; + } + + String sanitized = conventionName.replaceAll("[^a-zA-Z0-9_:]", "_"); + if (sanitized.isEmpty()) { + return "m_"; + } + if (!Character.isLetter(sanitized.charAt(0))) { + return "m_" + sanitized; + } + return sanitized; + } + + private static String snakeCase(String rawName) { + StringBuilder result = new StringBuilder(); + char[] chars = rawName.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char current = chars[i]; + if (Character.isUpperCase(current)) { + if (i > 0 && Character.isLowerCase(chars[i - 1])) { + result.append('_'); + } + result.append(Character.toLowerCase(current)); + } else if (Character.isLetterOrDigit(current) || current == '_') { + result.append(current); + } else { + result.append('_'); + } + } + return result.toString(); + } + + private static String newPrometheusName(String rawName, MetricKind kind) { + String baseName = stripReservedSuffixesRepeatedly(rawName); + String prometheusName = sanitizePrometheusName(baseName.replace('.', '_')); + + switch (kind) { + case COUNTER: + return appendSuffixIfAbsent(prometheusName, "_total"); + case INFO: + return appendSuffixIfAbsent(prometheusName, "_info"); + case GAUGE: + case UNKNOWN: + case HISTOGRAM: + case SUMMARY: + default: + return prometheusName; + } + } + + private static String appendSuffixIfAbsent(String metricName, String suffix) { + if (metricName.endsWith(suffix)) { + return metricName; + } + return metricName + suffix; + } + + private static String stripReservedSuffixesRepeatedly(String rawName) { + String value = rawName; + boolean stripped; + do { + stripped = false; + for (String suffix : RESERVED_SUFFIXES) { + if (value.endsWith(suffix)) { + value = value.substring(0, value.length() - suffix.length()); + stripped = true; + break; + } + } + } while (stripped); + return value; + } + + private static String sanitizePrometheusName(String value) { + String sanitized = value.replaceAll("[^a-zA-Z0-9_:]", "_"); + if (sanitized.isEmpty()) { + return "m_"; + } + if (!Character.isLetter(sanitized.charAt(0))) { + return "m_" + sanitized; + } + return sanitized; + } +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/pom.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/pom.xml new file mode 100644 index 0000000000..ba3130a579 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/pom.xml @@ -0,0 +1,112 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-samples-metrics-prometheus-springboot3 + 1.0-SNAPSHOT + + + dubbo-samples-metrics-prometheus-provider-springboot3 + dubbo-samples-metrics-prometheus-provider-springboot3 + + + + org.apache.httpcomponents + httpclient + test + + + + org.apache.dubbo + dubbo-samples-metrics-prometheus-interface-springboot3 + ${project.parent.version} + + + + org.apache.dubbo + dubbo-zookeeper-curator5-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + io.dropwizard.metrics + metrics-core + 4.1.12.1 + + + org.xerial.snappy + snappy-java + 1.1.10.5 + + + + io.micrometer + micrometer-registry-prometheus + + + org.apache.dubbo + dubbo-metrics-prometheus + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.6.14 + + org.apache.dubbo.samples.metrics.prometheus.provider.MetricsProvider + + + + + repackage + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-jar-file + package + + run + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/EmbeddedZooKeeper.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/EmbeddedZooKeeper.java new file mode 100644 index 0000000000..cf21ed28e0 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/EmbeddedZooKeeper.java @@ -0,0 +1,304 @@ + +/* + * Copyright 2014 the original author or authors. + * + * Licensed 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.samples.metrics.prometheus.provider; + +import org.apache.zookeeper.server.ServerConfig; +import org.apache.zookeeper.server.ZooKeeperServerMain; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.SmartLifecycle; +import org.springframework.util.ErrorHandler; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.ServerSocket; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * from: https://github.com/spring-projects/spring-xd/blob/v1.3.1.RELEASE/spring-xd-dirt/src/main/java/org/springframework/xd/dirt/zookeeper/ZooKeeperUtils.java + *

+ * Helper class to start an embedded instance of standalone (non clustered) ZooKeeper. + *

+ * NOTE: at least an external standalone server (if not an ensemble) are recommended, even for + */ +public class EmbeddedZooKeeper implements SmartLifecycle { + + private static final Random RANDOM = new Random(); + + /** + * Logger. + */ + private static final Logger logger = LoggerFactory.getLogger(EmbeddedZooKeeper.class); + + /** + * ZooKeeper client port. This will be determined dynamically upon startup. + */ + private final int clientPort; + + /** + * Whether to auto-start. Default is true. + */ + private boolean autoStartup = true; + + /** + * Lifecycle phase. Default is 0. + */ + private int phase = 0; + + /** + * Thread for running the ZooKeeper server. + */ + private volatile Thread zkServerThread; + + /** + * ZooKeeper server. + */ + private volatile ZooKeeperServerMain zkServer; + + /** + * {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. + */ + private ErrorHandler errorHandler; + + private boolean daemon = true; + + /** + * Construct an EmbeddedZooKeeper with a random port. + */ + public EmbeddedZooKeeper() { + clientPort = findRandomPort(30000, 65535); + } + + /** + * Construct an EmbeddedZooKeeper with the provided port. + * + * @param clientPort port for ZooKeeper server to bind to + */ + public EmbeddedZooKeeper(int clientPort, boolean daemon) { + this.clientPort = clientPort; + this.daemon = daemon; + } + + /** + * Returns the port that clients should use to connect to this embedded server. + * + * @return dynamically determined client port + */ + public int getClientPort() { + return this.clientPort; + } + + /** + * Specify whether to start automatically. Default is true. + * + * @param autoStartup whether to start automatically + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAutoStartup() { + return this.autoStartup; + } + + /** + * Specify the lifecycle phase for the embedded server. + * + * @param phase the lifecycle phase + */ + public void setPhase(int phase) { + this.phase = phase; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPhase() { + return this.phase; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunning() { + return (zkServerThread != null); + } + + /** + * Start the ZooKeeper server in a background thread. + *

+ * Register an error handler via {@link #setErrorHandler} in order to handle + * any exceptions thrown during startup or execution. + */ + @Override + public synchronized void start() { + if (zkServerThread == null) { + zkServerThread = new Thread(new ServerRunnable(), "ZooKeeper Server Starter"); + zkServerThread.setDaemon(daemon); + zkServerThread.start(); + } + } + + /** + * Shutdown the ZooKeeper server. + */ + @Override + public synchronized void stop() { + if (zkServerThread != null) { + // The shutdown method is protected...thus this hack to invoke it. + // This will log an exception on shutdown; see + // https://issues.apache.org/jira/browse/ZOOKEEPER-1873 for details. + try { + Method shutdown = ZooKeeperServerMain.class.getDeclaredMethod("shutdown"); + shutdown.setAccessible(true); + shutdown.invoke(zkServer); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // It is expected that the thread will exit after + // the server is shutdown; this will block until + // the shutdown is complete. + try { + zkServerThread.join(5000); + zkServerThread = null; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.warn("Interrupted while waiting for embedded ZooKeeper to exit"); + // abandoning zk thread + zkServerThread = null; + } + } + } + + /** + * Stop the server if running and invoke the callback when complete. + */ + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + /** + * Provide an {@link ErrorHandler} to be invoked if an Exception is thrown from the ZooKeeper server thread. If none + * is provided, only error-level logging will occur. + * + * @param errorHandler the {@link ErrorHandler} to be invoked + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Runnable implementation that starts the ZooKeeper server. + */ + private class ServerRunnable implements Runnable { + + @Override + public void run() { + try { + Properties properties = new Properties(); + File file = new File(System.getProperty("java.io.tmpdir") + + File.separator + UUID.randomUUID()); + file.deleteOnExit(); + properties.setProperty("dataDir", file.getAbsolutePath()); + properties.setProperty("clientPort", String.valueOf(clientPort)); + + QuorumPeerConfig quorumPeerConfig = new QuorumPeerConfig(); + quorumPeerConfig.parseProperties(properties); + + zkServer = new ZooKeeperServerMain(); + ServerConfig configuration = new ServerConfig(); + configuration.readFrom(quorumPeerConfig); + + System.setProperty("zookeeper.admin.enableServer", "false"); + + zkServer.runFromConfig(configuration); + } catch (Exception e) { + if (errorHandler != null) { + errorHandler.handleError(e); + } else { + logger.error("Exception running embedded ZooKeeper", e); + } + } + } + } + + /** + * Workaround for SocketUtils.findRandomPort() deprecation. + * + * @param min min port + * @param max max port + * @return a random generated available port + */ + private static int findRandomPort(int min, int max) { + if (min < 1024) { + throw new IllegalArgumentException("Max port shouldn't be less than 1024."); + } + + if (max > 65535) { + throw new IllegalArgumentException("Max port shouldn't be greater than 65535."); + } + + if (min > max) { + throw new IllegalArgumentException("Min port shouldn't be greater than max port."); + } + + int port = 0; + int counter = 0; + + // Workaround for legacy JDK doesn't support Random.nextInt(min, max). + List randomInts = RANDOM.ints(min, max + 1) + .limit(max - min) + .mapToObj(Integer::valueOf) + .collect(Collectors.toList()); + + do { + if (counter > max - min) { + throw new IllegalStateException("Unable to find a port between " + min + "-" + max); + } + + port = randomInts.get(counter); + counter++; + } while (isPortInUse(port)); + + return port; + } + + private static boolean isPortInUse(int port) { + try (ServerSocket ignored = new ServerSocket(port)) { + return false; + } catch (IOException e) { + // continue + } + return true; + } +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/MetricsProvider.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/MetricsProvider.java new file mode 100644 index 0000000000..2a117b4ec0 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/MetricsProvider.java @@ -0,0 +1,34 @@ +/* + * 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.samples.metrics.prometheus.provider; + +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.CountDownLatch; + + +public class MetricsProvider { + + public static void main(String[] args) throws InterruptedException { + new EmbeddedZooKeeper(2181, false).start(); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-provider.xml"); + context.start(); + new CountDownLatch(1).await(); + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl.java new file mode 100644 index 0000000000..c8730a02c6 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl.java @@ -0,0 +1,102 @@ +/* + * 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.samples.metrics.prometheus.provider.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.samples.metrics.prometheus.api.DemoService; +import org.apache.dubbo.samples.metrics.prometheus.api.model.Result; +import org.apache.dubbo.samples.metrics.prometheus.api.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public class DemoServiceImpl implements DemoService { + + private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class); + private String name = "Dubbo ~"; + + @Override + public CompletableFuture sayHello() { + return CompletableFuture.completedFuture(2122); + } + + @Override + public Result sayHello(String localName) { + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(name, "Hello " + localName + ", resp from provider: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result sayHello(final Long id, String name) { + return sayHello(new User(id, name)); + } + + @Override + public Result sayHello(User user) { + String localName = user.getUsername(); + Long id = user.getId(); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(name, "Hello " + id + " " + localName + + ", resp from provider: " + RpcContext.getContext().getLocalAddress()); + + } + + @Override + public String stringArray(String[] bytes) { + return bytes.toString(); + } + + @Override + public Result timeLimitedMethod(String localName) throws InterruptedException { + //Simulate a response timeout scenario + Thread.sleep(5000); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result randomResponseTime(String localName) throws InterruptedException { + Random random = new Random(); + int responseTime = Math.abs(random.nextInt() % 2999 + 1); + Thread.sleep(responseTime); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result runTimeException(String localName) { + int i = 1 / 0; + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider: " + + RpcContext.getContext().getLocalAddress()); + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl2.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl2.java new file mode 100644 index 0000000000..6b9d46b23d --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/java/org/apache/dubbo/samples/metrics/prometheus/provider/impl/DemoServiceImpl2.java @@ -0,0 +1,117 @@ +/* + * 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.samples.metrics.prometheus.provider.impl; + +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.samples.metrics.prometheus.api.DemoService; +import org.apache.dubbo.samples.metrics.prometheus.api.DemoService2; +import org.apache.dubbo.samples.metrics.prometheus.api.model.Result; +import org.apache.dubbo.samples.metrics.prometheus.api.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public class DemoServiceImpl2 implements DemoService2 { + + private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl2.class); + private String name = "Dubbo ~"; + + private Random random = new Random(200); + + @Override + public CompletableFuture sayHello() { + return CompletableFuture.completedFuture(2122); + } + + @Override + public Result sayHello(String localName) { + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + doAction(); + return new Result(name, "Hello " + localName + ", resp from provider2: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result sayHello(final Long id, String name) { + return sayHello(new User(id, name)); + } + + + + private void doAction() { + try { + Thread.sleep(random.nextInt(3000)); + }catch (InterruptedException e){ + Thread.currentThread().interrupt(); + } + } + + @Override + public Result sayHello(User user) { + String localName = user.getUsername(); + Long id = user.getId(); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + + doAction(); + return new Result(name, "Hello " + id + " " + localName + + ", resp from provider2: " + RpcContext.getContext().getLocalAddress()); + + } + + @Override + public String stringArray(String[] bytes) { + return bytes.toString(); + } + + @Override + public Result timeLimitedMethod(String localName) throws InterruptedException { + //Simulate a response timeout scenario + Thread.sleep(5000); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider2: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result randomResponseTime(String localName) throws InterruptedException { + Random random = new Random(); + int responseTime = Math.abs(random.nextInt() % 2999 + 1); + Thread.sleep(responseTime); + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider2: " + + RpcContext.getContext().getLocalAddress()); + } + + @Override + public Result runTimeException(String localName) { + int i = 1 / 0; + logger.info("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + localName + + ", request from consumer: " + RpcContext.getContext().getRemoteAddress()); + return new Result(localName, "Hello " + localName + ", resp from provider2: " + + RpcContext.getContext().getLocalAddress()); + } + +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/dubbo-demo-provider.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/dubbo-demo-provider.xml new file mode 100644 index 0000000000..bb0d6225ca --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/dubbo-demo-provider.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/log4j2.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..69e1321d22 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/log4j2.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/security/serialize.allowlist b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/security/serialize.allowlist new file mode 100644 index 0000000000..a40ac9e3b0 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/main/resources/security/serialize.allowlist @@ -0,0 +1,19 @@ +# +# +# 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. +# +# +java.lang.ArithmeticException diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/provider/ProviderMetricsIT.java b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/provider/ProviderMetricsIT.java new file mode 100644 index 0000000000..f60aa632b1 --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/dubbo-samples-metrics-prometheus-provider-springboot3/src/test/java/org/apache/dubbo/samples/metrics/prometheus/provider/ProviderMetricsIT.java @@ -0,0 +1,189 @@ +/* + * 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.samples.metrics.prometheus.provider; + +import org.apache.dubbo.samples.metrics.prometheus.util.PrometheusMetricAssert; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +public class ProviderMetricsIT { + private static final Logger logger = LoggerFactory.getLogger(ProviderMetricsIT.class); + + private final String port = "20888"; + + private final List metricExpectations = new ArrayList<>(); + + @Before + public void setUp() { + //application + addCounter("dubbo.application.info.total"); + + //provider + addCounter("dubbo.provider.requests.succeed.total"); + addGauge("dubbo.provider.rt.milliseconds.p99"); + addGauge("dubbo.provider.requests.failed.codec.total.aggregate"); + addGauge("dubbo.provider.requests.business.failed.aggregate"); + addGauge("dubbo.provider.requests.failed.aggregate"); + addGauge("dubbo.provider.requests.timeout.failed.aggregate"); + addGauge("dubbo.provider.requests.limit.aggregate"); + addGauge("dubbo.provider.requests.failed.service.unavailable.total.aggregate"); + addGauge("dubbo.provider.qps.total"); + addGauge("dubbo.provider.rt.min.milliseconds.aggregate"); + addGauge("dubbo.provider.rt.max.milliseconds.aggregate"); + addGauge("dubbo.provider.rt.milliseconds.p90"); + addGauge("dubbo.provider.rt.milliseconds.p50"); + addGauge("dubbo.provider.rt.milliseconds.p95"); + addCounter("dubbo.provider.requests.total"); + addGauge("dubbo.provider.rt.avg.milliseconds.aggregate"); + addGauge("dubbo.provider.requests.total.aggregate"); + addCounter("dubbo.provider.requests.failed.total"); + addGauge("dubbo.provider.requests.processing.total"); + addGauge("dubbo.provider.requests.failed.total.aggregate"); + addGauge("dubbo.provider.requests.failed.network.total.aggregate"); + addGauge("dubbo.provider.requests.succeed.aggregate"); + addCounter("dubbo.provider.requests.business.failed.total"); + + //consumer + addCounter("dubbo.consumer.requests.failed.service.unavailable.total"); + + //config + addGauge("dubbo.configcenter.total"); + + //register + addGauge("dubbo.register.service.rt.milliseconds.sum"); + addGauge("dubbo.registry.register.service.total"); + addGauge("dubbo.registry.register.service.succeed.total"); + addGauge("dubbo.register.service.rt.milliseconds.min"); + addGauge("dubbo.registry.register.requests.succeed.total"); + addGauge("dubbo.registry.register.requests.total"); + addGauge("dubbo.register.rt.milliseconds.last"); + addGauge("dubbo.register.rt.milliseconds.avg"); + addGauge("dubbo.register.rt.milliseconds.min"); + addGauge("dubbo.register.service.rt.milliseconds.last"); + addGauge("dubbo.register.rt.milliseconds.sum"); + addGauge("dubbo.register.service.rt.milliseconds.max"); + addGauge("dubbo.register.rt.milliseconds.max"); + addGauge("dubbo.register.service.rt.milliseconds.avg"); + + addGauge("dubbo.registry.notify.requests.total"); + addGauge("dubbo.registry.subscribe.num.total"); + addGauge("dubbo.registry.register.requests.failed.total"); + addGauge("dubbo.registry.subscribe.num.succeed.total"); + addGauge("dubbo.registry.subscribe.num.failed.total"); + + addGauge("dubbo.push.rt.milliseconds.avg"); + addGauge("dubbo.push.rt.milliseconds.min"); + addGauge("dubbo.push.rt.milliseconds.last"); + addGauge("dubbo.push.rt.milliseconds.sum"); + addGauge("dubbo.push.rt.milliseconds.max"); + + addGauge("dubbo.store.provider.interface.rt.milliseconds.min"); + addGauge("dubbo.store.provider.interface.rt.milliseconds.max"); + addGauge("dubbo.store.provider.interface.rt.milliseconds.avg"); + addGauge("dubbo.store.provider.interface.rt.milliseconds.last"); + addGauge("dubbo.store.provider.interface.rt.milliseconds.sum"); + + //metadata + addGauge("dubbo.metadata.store.provider.succeed.total"); + addGauge("dubbo.metadata.store.provider.total"); + addGauge("dubbo.metadata.subscribe.num.failed.total"); + addGauge("dubbo.metadata.push.num.total"); + addGauge("dubbo.metadata.subscribe.num.total"); + addGauge("dubbo.metadata.subscribe.num.succeed.total"); + addGauge("dubbo.metadata.push.num.succeed.total"); + addGauge("dubbo.metadata.push.num.failed.total"); + + //thread + addGauge("dubbo.thread.pool.thread.count"); + addGauge("dubbo.thread.pool.largest.size"); + addGauge("dubbo.thread.pool.active.size"); + addGauge("dubbo.thread.pool.queue.size"); + addGauge("dubbo.thread.pool.core.size"); + addGauge("dubbo.thread.pool.max.size"); + + // it usually does not appear in test case. + // add("dubbo.thread.pool.reject.thread.count"); + } + + // Use explicit kind only when it changes cross-registry compatible names. + // Example: `dubbo.application.info.total` resolves to `dubbo_application_total` on new clients. + private void addCounter(String rawName) { + metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.COUNTER)); + } + + // Example: `...qps.total` is exposed without `_total` on new clients when treated as a gauge. + private void addGauge(String rawName) { + metricExpectations.add(new MetricExpectation(rawName, PrometheusMetricAssert.MetricKind.GAUGE)); + } + + @Test + public void test() throws Exception { + new EmbeddedZooKeeper(2181, false).start(); + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-demo-provider.xml"); + context.start(); + List notExistedList = new ArrayList<>(); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpGet request = new HttpGet("http://localhost:" + port + "/metrics"); + CloseableHttpResponse response = client.execute(request); + InputStream inputStream = response.getEntity().getContent(); + String text = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)) + .lines().collect(Collectors.joining("\n")); + for (MetricExpectation expectation : metricExpectations) { + try { + PrometheusMetricAssert.assertDubboMetricExposed(text, expectation.rawName, expectation.kind); + } catch (AssertionError ignored) { + notExistedList.add(expectation); + } + } + } catch (Throwable e) { + Assert.fail(e.getMessage()); + } + context.stop(); + for (MetricExpectation metric : notExistedList) { + logger.error("metric rawName:{} with kind:{} doesn't exist", metric.rawName, metric.kind); + } + Assert.assertTrue(notExistedList.isEmpty()); + } + + private static final class MetricExpectation { + private final String rawName; + private final PrometheusMetricAssert.MetricKind kind; + + private MetricExpectation(String rawName, PrometheusMetricAssert.MetricKind kind) { + this.rawName = rawName; + this.kind = kind; + } + } +} diff --git a/4-governance/dubbo-samples-metrics-prometheus-springboot3/pom.xml b/4-governance/dubbo-samples-metrics-prometheus-springboot3/pom.xml new file mode 100644 index 0000000000..c3d9a7e37a --- /dev/null +++ b/4-governance/dubbo-samples-metrics-prometheus-springboot3/pom.xml @@ -0,0 +1,152 @@ + + + + + org.apache + apache + 23 + + + pom + 4.0.0 + + org.apache.dubbo + dubbo-samples-metrics-prometheus-springboot3 + 1.0-SNAPSHOT + + + dubbo-samples-metrics-prometheus-provider-springboot3 + dubbo-samples-metrics-prometheus-interface-springboot3 + dubbo-samples-metrics-prometheus-consumer-springboot3 + + + Dubbo Samples Metrics Prometheus for Spring Boot 3 + Dubbo Samples Metrics Prometheus for Spring Boot 3 + + + 17 + 17 + UTF-8 + + 3.3.7-SNAPSHOT + 3.5.10 + 4.13.1 + 4.5.13 + + 3.7.0 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + + + spring-boot-starter-logging + org.springframework.boot + + + + + org.apache.dubbo + dubbo-bom + ${dubbo.version} + pom + import + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + test + + + + + + + org.apache.dubbo + dubbo-spring-boot-starter + + + org.apache.dubbo + dubbo + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.apache.dubbo + dubbo-observability-spring-boot-starter + + + + org.apache.dubbo + dubbo-spring-boot-actuator + + + junit + junit + ${junit.version} + test + + + + + + + javax.annotation + + [1.11,) + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + + diff --git a/4-governance/pom.xml b/4-governance/pom.xml index a151666a72..1162e79280 100644 --- a/4-governance/pom.xml +++ b/4-governance/pom.xml @@ -31,6 +31,7 @@ dubbo-samples-configconditionrouter dubbo-samples-conditionrouterv31 dubbo-samples-metrics-prometheus + dubbo-samples-metrics-prometheus-springboot3 dubbo-samples-metrics-spring-boot dubbo-samples-sentinel dubbo-samples-servicelevel-override