metrics series 4 : add PrometheusMetricsReporter (#10515)

* metrics series 4 : add PrometheusMetricsReporter

* check style
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
index ea8ca77..a379856 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/config/MetricsConfig.java
@@ -17,7 +17,6 @@
 package org.apache.dubbo.config;
 
 import org.apache.dubbo.common.URL;
-import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.common.utils.UrlUtils;
 import org.apache.dubbo.config.nested.AggregationConfig;
 import org.apache.dubbo.config.nested.PrometheusConfig;
@@ -27,9 +26,6 @@
 import java.util.HashMap;
 import java.util.Map;
 
-import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
-import static org.apache.dubbo.common.constants.MetricsConstants.PROTOCOL_PROMETHEUS;
-
 /**
  * MetricsConfig
  */
@@ -40,6 +36,11 @@
     private String protocol;
 
     /**
+     * Enable jvm metrics when collecting.
+     */
+    private Boolean enableJvmMetrics;
+
+    /**
      * @deprecated After metrics config is refactored.
      * This parameter should no longer use and will be deleted in the future.
      */
@@ -69,14 +70,11 @@
         Map<String, String> map = new HashMap<>();
         appendParameters(map, this);
 
-        // use 'prometheus' as the default metrics service.
-        if (StringUtils.isEmpty(map.get(PROTOCOL_KEY))) {
-            map.put(PROTOCOL_KEY, PROTOCOL_PROMETHEUS);
-        }
-
         // ignore address parameter, use specified url in each metrics server config
         // the address "localhost" here is meaningless
-        return UrlUtils.parseURL("localhost", map);
+        URL url = UrlUtils.parseURL("localhost", map);
+        url = url.setScopeModel(getScopeModel());
+        return url;
     }
 
     public String getProtocol() {
@@ -87,6 +85,14 @@
         this.protocol = protocol;
     }
 
+    public Boolean getEnableJvmMetrics() {
+        return enableJvmMetrics;
+    }
+
+    public void setEnableJvmMetrics(Boolean enableJvmMetrics) {
+        this.enableJvmMetrics = enableJvmMetrics;
+    }
+
     public String getPort() {
         return port;
     }
diff --git a/dubbo-metrics/dubbo-metrics-api/pom.xml b/dubbo-metrics/dubbo-metrics-api/pom.xml
index 445787d..3c31ae3 100644
--- a/dubbo-metrics/dubbo-metrics-api/pom.xml
+++ b/dubbo-metrics/dubbo-metrics-api/pom.xml
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.dubbo</groupId>
diff --git a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java
index 5e65180..2e86a83 100644
--- a/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java
+++ b/dubbo-metrics/dubbo-metrics-api/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java
@@ -61,7 +61,7 @@
         applicationModel.getApplicationConfigManager().setApplication(config);
 
         invocation = new RpcInvocation();
-        filter =  new MetricsFilter();
+        filter = new MetricsFilter();
 
         collector = applicationModel.getBeanFactory().getOrRegisterBean(DefaultMetricsCollector.class);
         filter.setApplicationModel(applicationModel);
@@ -132,7 +132,7 @@
         given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
         invocation.setTargetServiceUniqueName(INTERFACE_NAME + ":" + VERSION);
         invocation.setMethodName(METHOD_NAME);
-        invocation.setParameterTypes(new Class[]{ String.class });
+        invocation.setParameterTypes(new Class[]{String.class});
 
         filter.invoke(invoker, invocation);
         Map<String, MetricSample> metricsMap = getMetricsMap();
@@ -152,7 +152,7 @@
         given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
         invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME);
         invocation.setMethodName(METHOD_NAME);
-        invocation.setParameterTypes(new Class[]{ String.class });
+        invocation.setParameterTypes(new Class[]{String.class});
 
         filter.invoke(invoker, invocation);
         Map<String, MetricSample> metricsMap = getMetricsMap();
@@ -172,7 +172,7 @@
         given(invoker.invoke(invocation)).willReturn(new AppResponse("success"));
         invocation.setTargetServiceUniqueName(INTERFACE_NAME);
         invocation.setMethodName(METHOD_NAME);
-        invocation.setParameterTypes(new Class[]{ String.class });
+        invocation.setParameterTypes(new Class[]{String.class});
 
         filter.invoke(invoker, invocation);
         Map<String, MetricSample> metricsMap = getMetricsMap();
@@ -189,7 +189,7 @@
     private void initParam() {
         invocation.setTargetServiceUniqueName(GROUP + "/" + INTERFACE_NAME + ":" + VERSION);
         invocation.setMethodName(METHOD_NAME);
-        invocation.setParameterTypes(new Class[]{ String.class });
+        invocation.setParameterTypes(new Class[]{String.class});
     }
 
     private Map<String, MetricSample> getMetricsMap() {
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/pom.xml b/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
index 0020915..18f90e1 100644
--- a/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
+++ b/dubbo-metrics/dubbo-metrics-prometheus/pom.xml
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.dubbo</groupId>
@@ -35,5 +36,23 @@
             <artifactId>dubbo-common</artifactId>
             <version>${project.parent.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-metrics-api</artifactId>
+            <version>${project.parent.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>simpleclient_pushgateway</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java
new file mode 100644
index 0000000..89684e7
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporter.java
@@ -0,0 +1,162 @@
+/*
+ * 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.prometheus;
+
+import com.sun.net.httpserver.HttpServer;
+import io.micrometer.prometheus.PrometheusConfig;
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import io.prometheus.client.exporter.BasicAuthHttpConnectionFactory;
+import io.prometheus.client.exporter.PushGateway;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.NamedThreadFactory;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.metrics.AbstractMetricsReporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_ENABLED_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_METRICS_PORT_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_METRICS_PORT;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_EXPORTER_METRICS_PATH_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_METRICS_PATH;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_ENABLED_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_JOB_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_JOB_NAME;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_DEFAULT_PUSH_INTERVAL;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_USERNAME_KEY;
+import static org.apache.dubbo.common.constants.MetricsConstants.PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY;
+
+/**
+ * Metrics reporter for prometheus.
+ */
+public class PrometheusMetricsReporter extends AbstractMetricsReporter {
+
+    private final Logger logger = LoggerFactory.getLogger(PrometheusMetricsReporter.class);
+
+    private final PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
+    private ScheduledExecutorService pushJobExecutor = null;
+    private HttpServer prometheusExporterHttpServer = null;
+    private Thread httpServerThread = null;
+
+    public PrometheusMetricsReporter(URL url, ApplicationModel applicationModel) {
+        super(url, applicationModel);
+    }
+
+    @Override
+    public void doInit() {
+        addMeterRegistry(prometheusRegistry);
+        exportHttpServer();
+        schedulePushJob();
+    }
+
+    private void exportHttpServer() {
+        boolean exporterEnabled = url.getParameter(PROMETHEUS_EXPORTER_ENABLED_KEY, false);
+        if (exporterEnabled) {
+            int port = url.getParameter(PROMETHEUS_EXPORTER_METRICS_PORT_KEY, PROMETHEUS_DEFAULT_METRICS_PORT);
+            String path = url.getParameter(PROMETHEUS_EXPORTER_METRICS_PATH_KEY, PROMETHEUS_DEFAULT_METRICS_PATH);
+            if (!path.startsWith("/")) {
+                path = "/" + path;
+            }
+
+            try {
+                prometheusExporterHttpServer = HttpServer.create(new InetSocketAddress(port), 0);
+                prometheusExporterHttpServer.createContext(path, httpExchange -> {
+                    String response = prometheusRegistry.scrape();
+                    httpExchange.sendResponseHeaders(200, response.getBytes().length);
+                    try (OutputStream os = httpExchange.getResponseBody()) {
+                        os.write(response.getBytes());
+                    }
+                });
+
+                httpServerThread = new Thread(prometheusExporterHttpServer::start);
+                httpServerThread.start();
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private void schedulePushJob() {
+        boolean pushEnabled = url.getParameter(PROMETHEUS_PUSHGATEWAY_ENABLED_KEY, false);
+        if (pushEnabled) {
+            String baseUrl = url.getParameter(PROMETHEUS_PUSHGATEWAY_BASE_URL_KEY);
+            String job = url.getParameter(PROMETHEUS_PUSHGATEWAY_JOB_KEY, PROMETHEUS_DEFAULT_JOB_NAME);
+            int pushInterval = url.getParameter(PROMETHEUS_PUSHGATEWAY_PUSH_INTERVAL_KEY, PROMETHEUS_DEFAULT_PUSH_INTERVAL);
+            String username = url.getParameter(PROMETHEUS_PUSHGATEWAY_USERNAME_KEY);
+            String password = url.getParameter(PROMETHEUS_PUSHGATEWAY_PASSWORD_KEY);
+
+            NamedThreadFactory threadFactory = new NamedThreadFactory("prometheus-push-job", true);
+            pushJobExecutor = Executors.newScheduledThreadPool(1, threadFactory);
+            PushGateway pushGateway = new PushGateway(baseUrl);
+            if (!StringUtils.isBlank(username)) {
+                pushGateway.setConnectionFactory(new BasicAuthHttpConnectionFactory(username, password));
+            }
+
+            pushJobExecutor.scheduleWithFixedDelay(() -> push(pushGateway, job), pushInterval, pushInterval, TimeUnit.SECONDS);
+        }
+    }
+
+    protected void push(PushGateway pushGateway, String job) {
+        try {
+            pushGateway.pushAdd(prometheusRegistry.getPrometheusRegistry(), job);
+        } catch (IOException e) {
+            logger.error("Error occurred when pushing metrics to prometheus: ", e);
+        }
+    }
+
+    @Override
+    public void doDestroy() {
+        if (prometheusExporterHttpServer != null) {
+            prometheusExporterHttpServer.stop(1);
+        }
+
+        if (httpServerThread != null) {
+            httpServerThread.interrupt();
+        }
+
+        if (pushJobExecutor != null) {
+            pushJobExecutor.shutdownNow();
+        }
+    }
+
+    /**
+     * ut only
+     */
+    @Deprecated
+    public ScheduledExecutorService getPushJobExecutor() {
+        return pushJobExecutor;
+    }
+
+    /**
+     * ut only
+     */
+    @Deprecated
+    public PrometheusMeterRegistry getPrometheusRegistry() {
+        return prometheusRegistry;
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java
new file mode 100644
index 0000000..f28aa74
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/main/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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.prometheus;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.metrics.AbstractMetricsReporterFactory;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+
+/**
+ * MetricsReporterFactory to create PrometheusMetricsReporter.
+ */
+public class PrometheusMetricsReporterFactory extends AbstractMetricsReporterFactory {
+
+    public PrometheusMetricsReporterFactory(ApplicationModel applicationModel) {
+        super(applicationModel);
+    }
+
+    @Override
+    public MetricsReporter createMetricsReporter(URL url) {
+        return new PrometheusMetricsReporter(url, getApplicationModel());
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java
new file mode 100644
index 0000000..f0f87a8
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterFactoryTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.prometheus;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.metrics.MetricsReporter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PrometheusMetricsReporterFactoryTest {
+
+    @Test
+    public void test() {
+        ApplicationModel applicationModel = ApplicationModel.defaultModel();
+        PrometheusMetricsReporterFactory factory = new PrometheusMetricsReporterFactory(applicationModel);
+        MetricsReporter reporter = factory.createMetricsReporter(URL.valueOf("prometheus://localhost:9090/"));
+
+        Assertions.assertTrue(reporter instanceof PrometheusMetricsReporter);
+    }
+}
diff --git a/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java
new file mode 100644
index 0000000..a429ce3
--- /dev/null
+++ b/dubbo-metrics/dubbo-metrics-prometheus/src/test/java/org/apache/dubbo/metrics/prometheus/PrometheusMetricsReporterTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.prometheus;
+
+import io.micrometer.prometheus.PrometheusMeterRegistry;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.config.MetricsConfig;
+import org.apache.dubbo.config.nested.PrometheusConfig;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+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.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.stream.Collectors;
+
+import static org.apache.dubbo.common.constants.MetricsConstants.PROTOCOL_PROMETHEUS;
+
+public class PrometheusMetricsReporterTest {
+
+    private MetricsConfig metricsConfig;
+    private ApplicationModel applicationModel;
+
+    @BeforeEach
+    public void setup() {
+        metricsConfig = new MetricsConfig();
+        applicationModel = ApplicationModel.defaultModel();
+        metricsConfig.setProtocol(PROTOCOL_PROMETHEUS);
+    }
+
+    @AfterEach
+    public void teardown() {
+        applicationModel.destroy();
+    }
+
+    @Test
+    public void testJvmMetrics() {
+        metricsConfig.setEnableJvmMetrics(true);
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        PrometheusMeterRegistry prometheusRegistry = reporter.getPrometheusRegistry();
+        Double d1 = prometheusRegistry.getPrometheusRegistry().getSampleValue("none_exist_metric");
+        Double d2 = prometheusRegistry.getPrometheusRegistry().getSampleValue("jvm_gc_memory_promoted_bytes_total");
+
+        Assertions.assertNull(d1);
+        Assertions.assertNotNull(d2);
+    }
+
+    @Test
+    public void testExporter() {
+        int port = NetUtils.getAvailablePort();
+
+        PrometheusConfig prometheusConfig = new PrometheusConfig();
+        PrometheusConfig.Exporter exporter = new PrometheusConfig.Exporter();
+        exporter.setMetricsPort(port);
+        exporter.setMetricsPath("/metrics");
+        exporter.setEnabled(true);
+        prometheusConfig.setExporter(exporter);
+        metricsConfig.setPrometheus(prometheusConfig);
+        metricsConfig.setEnableJvmMetrics(true);
+
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        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"));
+            Assertions.assertTrue(text.contains("jvm_gc_memory_promoted_bytes_total"));
+        } catch (Exception e) {
+            Assertions.fail(e);
+        } finally {
+            reporter.destroy();
+        }
+    }
+
+    @Test
+    public void testPushgateway() {
+        PrometheusConfig prometheusConfig = new PrometheusConfig();
+        PrometheusConfig.Pushgateway pushgateway = new PrometheusConfig.Pushgateway();
+        pushgateway.setJob("mock");
+        pushgateway.setBaseUrl("localhost:9091");
+        pushgateway.setEnabled(true);
+        pushgateway.setPushInterval(1);
+        prometheusConfig.setPushgateway(pushgateway);
+        metricsConfig.setPrometheus(prometheusConfig);
+
+        PrometheusMetricsReporter reporter = new PrometheusMetricsReporter(metricsConfig.toUrl(), applicationModel);
+        reporter.init();
+
+        ScheduledExecutorService executor = reporter.getPushJobExecutor();
+        Assertions.assertTrue(executor != null && !executor.isTerminated() && !executor.isShutdown());
+
+        reporter.destroy();
+        Assertions.assertTrue(executor.isTerminated() || executor.isShutdown());
+    }
+}
diff --git a/dubbo-metrics/pom.xml b/dubbo-metrics/pom.xml
index 8c4291a..2b101fc 100644
--- a/dubbo-metrics/pom.xml
+++ b/dubbo-metrics/pom.xml
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <modules>
         <module>dubbo-metrics-api</module>