diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index ed81b2de0..1a9e97889 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -79,6 +79,7 @@ 3.16.1 1.69 31.0.1-jre + 0.12.0 3.2.0 @@ -252,6 +253,12 @@ byte-buddy ${byte-buddy.version} + + + io.prometheus + simpleclient_pushgateway + ${pushgateway.version} + diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index e311cbff0..e15674c47 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -69,6 +69,11 @@ mockito-inline test + + + io.prometheus + simpleclient_pushgateway + \ No newline at end of file diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatProperties.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatProperties.java index b8b918202..8e91f512a 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatProperties.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatProperties.java @@ -17,6 +17,10 @@ package com.tencent.cloud.rpc.enhancement.stat.config; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -47,6 +51,10 @@ public class PolarisStatProperties { */ private String path = "/metrics"; + /** + * PushGatewayProperties. + */ + private PushGatewayProperties pushgateway; public boolean isEnabled() { return enabled; @@ -79,4 +87,108 @@ public class PolarisStatProperties { public void setPath(String path) { this.path = path; } + + public PushGatewayProperties getPushgateway() { + return pushgateway; + } + + public void setPushgateway(PushGatewayProperties pushgateway) { + this.pushgateway = pushgateway; + } + + public static class PushGatewayProperties { + /** + * Enable publishing via a Prometheus pushGateway. + */ + private Boolean enabled = false; + + /** + * Required host:port or ip:port of the pushGateway. + */ + private String address = "localhost:9091"; + + /** + * Required identifier for this application instance. + */ + private String job; + + /** + * Frequency with which to push metrics to pushGateway,default 1 minutes. + */ + private Duration pushRate = Duration.ofMinutes(1); + + /** + * PushGateway shutDownStrategy when application is is shut-down. + */ + private ShutDownStrategy shutDownStrategy; + + /** + * Used to group metrics in pushGateway. eg:instance:instanceName + */ + private Map groupingKeys = new HashMap<>(); + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public Duration getPushRate() { + return pushRate; + } + + public void setPushRate(Duration pushRate) { + this.pushRate = pushRate; + } + + public Map getGroupingKeys() { + return groupingKeys; + } + + public void setGroupingKeys(Map groupingKeys) { + this.groupingKeys = groupingKeys; + } + + public ShutDownStrategy getShutDownStrategy() { + return shutDownStrategy; + } + + public void setShutDownStrategy(ShutDownStrategy shutDownStrategy) { + this.shutDownStrategy = shutDownStrategy; + } + } + + /** + * PushGateway shutDownStrategy when application is is shut-down. + */ + public enum ShutDownStrategy { + + /** + * Delete metrics from pushGateway when application is shut-down. + */ + DELETE, + + /** + * Push metrics right before shut-down. Mostly useful for batch jobs. + */ + PUSH + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesAutoConfiguration.java index d21c21fba..652429fad 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/PolarisStatPropertiesAutoConfiguration.java @@ -17,9 +17,15 @@ package com.tencent.cloud.rpc.enhancement.stat.config; +import java.time.Duration; +import java.util.Map; + import com.tencent.cloud.polaris.context.ConditionalOnPolarisEnabled; +import com.tencent.cloud.rpc.enhancement.stat.config.plugin.PrometheusPushGatewayContainer; +import org.apache.commons.lang.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -40,4 +46,36 @@ public class PolarisStatPropertiesAutoConfiguration { public StatConfigModifier statReporterConfigModifier(PolarisStatProperties polarisStatProperties, Environment environment) { return new StatConfigModifier(polarisStatProperties, environment); } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "spring.cloud.polaris.stat.pushgateway.enabled", havingValue = "true") + public static class PolarisStatPrometheusPushGatewayAutoConfiguration { + + private static final String DEFAULT_JOB_NAME = "spring-cloud-tencent-application"; + + @Bean + @ConditionalOnMissingBean + public PrometheusPushGatewayContainer prometheusPushGatewayContainer(PolarisStatProperties polarisStatProperties, + Environment environment) { + PolarisStatProperties.PushGatewayProperties pushGatewayProperties = polarisStatProperties.getPushgateway(); + String job = job(pushGatewayProperties, environment); + Duration pushRate = pushGatewayProperties.getPushRate(); + String address = pushGatewayProperties.getAddress(); + PolarisStatProperties.ShutDownStrategy shutDownStrategy = pushGatewayProperties.getShutDownStrategy(); + Map groupingKeys = pushGatewayProperties.getGroupingKeys(); + return new PrometheusPushGatewayContainer(address, pushRate, job, shutDownStrategy, groupingKeys); + + } + + private String job(PolarisStatProperties.PushGatewayProperties pushGatewayProperties, Environment environment) { + String job = pushGatewayProperties.getJob(); + if (StringUtils.isBlank(job)) { + job = environment.getProperty("spring.application.name"); + } + if (StringUtils.isBlank(job)) { + job = DEFAULT_JOB_NAME; + } + return job; + } + } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/plugin/PrometheusPushGatewayContainer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/plugin/PrometheusPushGatewayContainer.java new file mode 100644 index 000000000..4a689a90f --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/stat/config/plugin/PrometheusPushGatewayContainer.java @@ -0,0 +1,129 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.rpc.enhancement.stat.config.plugin; + +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties; +import com.tencent.polaris.client.util.NamedThreadFactory; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.PushGateway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PrometheusPushGatewayContainer will push the metrics aggregated by + * {@link com.tencent.polaris.plugins.stat.prometheus.handler.PrometheusHandler} to Prometheus pushGateway. + * + * @author lingxiao.wlx + */ +public class PrometheusPushGatewayContainer { + + private final Logger logger = LoggerFactory.getLogger(PrometheusPushGatewayContainer.class); + /** + * {@link com.tencent.polaris.plugins.stat.prometheus.handler.PrometheusHandler} register the Collector with + * CollectorRegistry.defaultRegistry. + */ + private final CollectorRegistry collectorRegistry = CollectorRegistry.defaultRegistry; + private final String address; + private final String job; + private final PolarisStatProperties.ShutDownStrategy shutDownStrategy; + private final PushGateway pushGateway; + private final Map groupingKey; + + private final ScheduledExecutorService executorService; + private final ScheduledFuture scheduledFuture; + + public PrometheusPushGatewayContainer(String address, Duration pushRate, String job, + PolarisStatProperties.ShutDownStrategy shutDownStrategy, Map groupingKey) { + this.address = address; + this.job = job; + this.shutDownStrategy = shutDownStrategy; + this.pushGateway = new PushGateway(address); + this.groupingKey = groupingKey; + this.executorService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("PrometheusPushGateway") { + @Override + public Thread newThread(Runnable r) { + Thread thread = super.newThread(r); + thread.setDaemon(true); + return thread; + } + }); + this.scheduledFuture = executorService.scheduleAtFixedRate(() -> { + this.push(); + if (logger.isDebugEnabled()) { + logger.debug("push metrics to Prometheus pushGateway success!"); + } + }, 0, pushRate.toMillis(), TimeUnit.MILLISECONDS); + + } + + /** + * Call by Spring to destroy PrometheusPushGatewayContainer instance. + */ + public void shutdown() { + shutdown(this.shutDownStrategy); + } + + private void shutdown(PolarisStatProperties.ShutDownStrategy shutDownStrategy) { + executorService.shutdown(); + scheduledFuture.cancel(false); + if (Objects.isNull(shutDownStrategy)) { + return; + } + switch (shutDownStrategy) { + case PUSH: + push(); + break; + case DELETE: + delete(); + break; + } + } + + private void push() { + try { + pushGateway.pushAdd(collectorRegistry, this.job, this.groupingKey); + } + catch (UnknownHostException e) { + logger.error("Unable to locate host {}. No longer attempting metrics publication to this host", this.address); + // if cache UnknownHostException,shutdown task + this.shutdown(null); + } + catch (Throwable t) { + logger.error("Unable to push metrics to Prometheus pushGateway", t); + } + } + + private void delete() { + try { + pushGateway.delete(this.job, this.groupingKey); + } + catch (Throwable t) { + logger.error("Unable to delete metrics from Prometheus pushGateway", t); + } + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json index c76a18566..dd36d920b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-tencent-rpc-enhancement/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -46,6 +46,45 @@ "type": "java.lang.String", "defaultValue": "/metrics", "description": "Path for prometheus to pull." + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.address", + "type": "java.lang.String", + "description": "Required host:port or ip:port of the pushGateway.", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties", + "defaultValue": "localhost:9091" + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.enabled", + "type": "java.lang.Boolean", + "description": "Enable publishing via a Prometheus pushGateway.", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties", + "defaultValue": false + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.grouping-keys", + "type": "java.util.Map", + "description": "Used to group metrics in pushGateway. eg:instance:instanceName", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties" + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.job", + "type": "java.lang.String", + "description": "Required identifier for this application instance.", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties" + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.push-rate", + "type": "java.time.Duration", + "description": "Frequency with which to push metrics to pushGateway,default 1 minutes.", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties", + "defaultValue": "1m" + }, + { + "name": "spring.cloud.polaris.stat.pushgateway.shut-down-strategy", + "type": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$ShutDownStrategy", + "description": "PushGateway shutDownStrategy when application is is shut-down.", + "sourceType": "com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties$PushGatewayProperties" } ] } diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/plugin/PrometheusPushGatewayContainerTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/plugin/PrometheusPushGatewayContainerTest.java new file mode 100644 index 000000000..cce877df5 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/stat/plugin/PrometheusPushGatewayContainerTest.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.rpc.enhancement.stat.plugin; + +import java.util.Objects; + +import com.tencent.cloud.rpc.enhancement.stat.config.PolarisStatProperties; +import com.tencent.cloud.rpc.enhancement.stat.config.plugin.PrometheusPushGatewayContainer; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test for {@link PrometheusPushGatewayContainer}. + * + * @author lingxiao.wlx + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, + classes = PrometheusPushGatewayContainerTest.TestApplication.class, + properties = {"spring.cloud.polaris.stat.pushgateway.enabled=true", + "spring.cloud.polaris.stat.pushgateway.shut-down-strategy=DELETE", + "spring.cloud.polaris.stat.pushgateway.push-rate=1m", + "spring.cloud.polaris.stat.pushgateway.job=test", + "spring.cloud.polaris.stat.pushgateway.grouping-keys.instance=test"}) +public class PrometheusPushGatewayContainerTest { + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void prometheusPushGatewayContainerTest() { + PolarisStatProperties polarisStatProperties = applicationContext.getBean(PolarisStatProperties.class); + PolarisStatProperties.PushGatewayProperties pushgateway = polarisStatProperties.getPushgateway(); + Assertions.assertFalse(Objects.isNull(pushgateway)); + Assertions.assertEquals(pushgateway.getJob(), "test"); + Assertions.assertEquals(pushgateway.getPushRate().toMillis(), 60000); + Assertions.assertEquals(pushgateway.getShutDownStrategy(), PolarisStatProperties.ShutDownStrategy.DELETE); + applicationContext.getBean(PrometheusPushGatewayContainer.class); + } + + @SpringBootApplication + protected static class TestApplication { + } +}