001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.actuate.metrics.export.prometheus; 018 019import java.net.UnknownHostException; 020import java.time.Duration; 021import java.util.Map; 022import java.util.concurrent.Executors; 023import java.util.concurrent.ScheduledExecutorService; 024import java.util.concurrent.ScheduledFuture; 025 026import io.prometheus.client.CollectorRegistry; 027import io.prometheus.client.exporter.PushGateway; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030 031import org.springframework.scheduling.TaskScheduler; 032import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 033import org.springframework.util.Assert; 034import org.springframework.util.StringUtils; 035 036/** 037 * Class that can be used to manage the pushing of metrics to a {@link PushGateway 038 * Prometheus PushGateway}. Handles the scheduling of push operations, error handling and 039 * shutdown operations. 040 * 041 * @author David J. M. Karlsen 042 * @author Phillip Webb 043 * @since 2.1.0 044 */ 045public class PrometheusPushGatewayManager { 046 047 private static final Log logger = LogFactory 048 .getLog(PrometheusPushGatewayManager.class); 049 050 private final PushGateway pushGateway; 051 052 private final CollectorRegistry registry; 053 054 private final String job; 055 056 private final Map<String, String> groupingKey; 057 058 private final ShutdownOperation shutdownOperation; 059 060 private final TaskScheduler scheduler; 061 062 private ScheduledFuture<?> scheduled; 063 064 /** 065 * Create a new {@link PrometheusPushGatewayManager} instance using a single threaded 066 * {@link TaskScheduler}. 067 * @param pushGateway the source push gateway 068 * @param registry the collector registry to push 069 * @param pushRate the rate at which push operations occur 070 * @param job the job ID for the operation 071 * @param groupingKeys an optional set of grouping keys for the operation 072 * @param shutdownOperation the shutdown operation that should be performed when 073 * context is closed. 074 */ 075 public PrometheusPushGatewayManager(PushGateway pushGateway, 076 CollectorRegistry registry, Duration pushRate, String job, 077 Map<String, String> groupingKeys, ShutdownOperation shutdownOperation) { 078 this(pushGateway, registry, new PushGatewayTaskScheduler(), pushRate, job, 079 groupingKeys, shutdownOperation); 080 } 081 082 /** 083 * Create a new {@link PrometheusPushGatewayManager} instance. 084 * @param pushGateway the source push gateway 085 * @param registry the collector registry to push 086 * @param scheduler the scheduler used for operations 087 * @param pushRate the rate at which push operations occur 088 * @param job the job ID for the operation 089 * @param groupingKey an optional set of grouping keys for the operation 090 * @param shutdownOperation the shutdown operation that should be performed when 091 * context is closed. 092 */ 093 public PrometheusPushGatewayManager(PushGateway pushGateway, 094 CollectorRegistry registry, TaskScheduler scheduler, Duration pushRate, 095 String job, Map<String, String> groupingKey, 096 ShutdownOperation shutdownOperation) { 097 Assert.notNull(pushGateway, "PushGateway must not be null"); 098 Assert.notNull(registry, "Registry must not be null"); 099 Assert.notNull(scheduler, "Scheduler must not be null"); 100 Assert.notNull(pushRate, "PushRate must not be null"); 101 Assert.hasLength(job, "Job must not be empty"); 102 this.pushGateway = pushGateway; 103 this.registry = registry; 104 this.job = job; 105 this.groupingKey = groupingKey; 106 this.shutdownOperation = (shutdownOperation != null) ? shutdownOperation 107 : ShutdownOperation.NONE; 108 this.scheduler = scheduler; 109 this.scheduled = this.scheduler.scheduleAtFixedRate(this::push, pushRate); 110 } 111 112 private void push() { 113 try { 114 this.pushGateway.pushAdd(this.registry, this.job, this.groupingKey); 115 } 116 catch (UnknownHostException ex) { 117 String host = ex.getMessage(); 118 String message = "Unable to locate prometheus push gateway host" 119 + (StringUtils.hasLength(host) ? " '" + host + "'" : "") 120 + ". No longer attempting metrics publication to this host"; 121 logger.error(message, ex); 122 shutdown(ShutdownOperation.NONE); 123 } 124 catch (Throwable ex) { 125 logger.error("Unable to push metrics to Prometheus Pushgateway", ex); 126 } 127 } 128 129 private void delete() { 130 try { 131 this.pushGateway.delete(this.job, this.groupingKey); 132 } 133 catch (Throwable ex) { 134 logger.error("Unable to delete metrics from Prometheus Pushgateway", ex); 135 } 136 } 137 138 /** 139 * Shutdown the manager, running any {@link ShutdownOperation}. 140 */ 141 public void shutdown() { 142 shutdown(this.shutdownOperation); 143 } 144 145 private void shutdown(ShutdownOperation shutdownOperation) { 146 if (this.scheduler instanceof PushGatewayTaskScheduler) { 147 ((PushGatewayTaskScheduler) this.scheduler).shutdown(); 148 } 149 this.scheduled.cancel(false); 150 switch (shutdownOperation) { 151 case PUSH: 152 push(); 153 break; 154 case DELETE: 155 delete(); 156 break; 157 } 158 } 159 160 /** 161 * The operation that should be performed on shutdown. 162 */ 163 public enum ShutdownOperation { 164 165 /** 166 * Don't perform any shutdown operation. 167 */ 168 NONE, 169 170 /** 171 * Perform a 'push' before shutdown. 172 */ 173 PUSH, 174 175 /** 176 * Perform a 'delete' before shutdown. 177 */ 178 DELETE 179 180 } 181 182 /** 183 * {@link TaskScheduler} used when the user doesn't specify one. 184 */ 185 static class PushGatewayTaskScheduler extends ThreadPoolTaskScheduler { 186 187 PushGatewayTaskScheduler() { 188 setPoolSize(1); 189 setDaemon(true); 190 setThreadGroupName("prometheus-push-gateway"); 191 } 192 193 @Override 194 public ScheduledExecutorService getScheduledExecutor() 195 throws IllegalStateException { 196 return Executors.newSingleThreadScheduledExecutor(this::newThread); 197 } 198 199 } 200 201}