001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 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 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 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 */ 016package io.jboot.support.metric.reporter.prometheus; 017 018import com.codahale.metrics.Timer; 019import com.codahale.metrics.*; 020import io.jboot.Jboot; 021import io.jboot.app.JbootApplicationConfig; 022import io.jboot.utils.ClassUtil; 023import io.jboot.utils.NetUtil; 024import io.jboot.utils.StrUtil; 025import io.prometheus.client.dropwizard.DropwizardExports; 026import io.prometheus.client.dropwizard.samplebuilder.DefaultSampleBuilder; 027import io.prometheus.client.dropwizard.samplebuilder.SampleBuilder; 028 029import java.util.*; 030import java.util.concurrent.TimeUnit; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034public class PrometheusExports extends io.prometheus.client.Collector implements io.prometheus.client.Collector.Describable { 035 private static final Logger LOGGER = Logger.getLogger(DropwizardExports.class.getName()); 036 private MetricRegistry registry; 037 private SampleBuilder sampleBuilder; 038 private JbootApplicationConfig appConfig = Jboot.config(JbootApplicationConfig.class); 039 040 /** 041 * Creates a new DropwizardExports with a {@link DefaultSampleBuilder}. 042 * 043 * @param registry a metric registry to export in prometheus. 044 */ 045 public PrometheusExports(MetricRegistry registry) { 046 this.registry = registry; 047 this.sampleBuilder = new DefaultSampleBuilder(); 048 } 049 050 /** 051 * @param registry a metric registry to export in prometheus. 052 * @param sampleBuilder sampleBuilder to use to create prometheus samples. 053 */ 054 public PrometheusExports(MetricRegistry registry, SampleBuilder sampleBuilder) { 055 this.registry = registry; 056 this.sampleBuilder = sampleBuilder; 057 } 058 059 private static String getHelpMessage(String metricName, Metric metric) { 060 return String.format("Generated from Jboot metric import (metric=%s, type=%s)", 061 metricName, ClassUtil.getUsefulClass(metric.getClass()).getName()); 062 } 063 064 // List<String> additionalLabelNames, List<String> additionalLabelValues, 065 private List<String> getDefaultAdditionalLabelNames(String... names) { 066 List<String> array = getDefaultAdditionalLabelNames(); 067 array.addAll(Arrays.asList(names)); 068 return array; 069 } 070 071 072 private List<String> getDefaultAdditionalLabelNames() { 073 List<String> newArray = new ArrayList<>(); 074 newArray.add("application"); 075 newArray.add("instance"); 076 return newArray; 077 } 078 079 080 private List<String> getDefaultAdditionalLabelValues(String... values) { 081 List<String> array = getDefaultAdditionalLabelValues(); 082 array.addAll(Arrays.asList(values)); 083 return array; 084 } 085 086 087 private List<String> getDefaultAdditionalLabelValues() { 088 List<String> newArray = new ArrayList<>(); 089 newArray.add(appConfig.getName()); 090 newArray.add(getInstance()); 091 return newArray; 092 } 093 094 private static String instance = null; 095 096 private static final String getInstance() { 097 if (instance == null) { 098 String ipAddress = NetUtil.getLocalIpAddress(); 099 if (StrUtil.isNotBlank(ipAddress)) { 100 instance = ipAddress + ":" + Jboot.configValue("undertow.port"); 101 } else { 102 instance = ""; 103 } 104 } 105 return instance; 106 } 107 108 109 /** 110 * Export counter as Prometheus <a href="https://prometheus.io/docs/concepts/metric_types/#gauge">Gauge</a>. 111 */ 112 MetricFamilySamples fromCounter(String dropwizardName, Counter counter) { 113 MetricFamilySamples.Sample sample = sampleBuilder.createSample(dropwizardName, "" 114 , getDefaultAdditionalLabelNames() 115 , getDefaultAdditionalLabelValues() 116 , new Long(counter.getCount()).doubleValue()); 117 return new MetricFamilySamples(sample.name, Type.GAUGE, getHelpMessage(dropwizardName, counter), Arrays.asList(sample)); 118 } 119 120 /** 121 * Export gauge as a prometheus gauge. 122 */ 123 MetricFamilySamples fromGauge(String dropwizardName, Gauge gauge) { 124 Object obj = gauge.getValue(); 125 double value; 126 if (obj instanceof Number) { 127 value = ((Number) obj).doubleValue(); 128 } else if (obj instanceof Boolean) { 129 value = ((Boolean) obj) ? 1 : 0; 130 } else { 131 LOGGER.log(Level.FINE, String.format("Invalid type for Gauge %s: %s", sanitizeMetricName(dropwizardName), 132 obj == null ? "null" : obj.getClass().getName())); 133 return null; 134 } 135 MetricFamilySamples.Sample sample = sampleBuilder.createSample(dropwizardName, "" 136 , getDefaultAdditionalLabelNames() 137 , getDefaultAdditionalLabelValues() 138 , value); 139 return new MetricFamilySamples(sample.name, Type.GAUGE, getHelpMessage(dropwizardName, gauge), Arrays.asList(sample)); 140 } 141 142 /** 143 * Export a histogram snapshot as a prometheus SUMMARY. 144 * 145 * @param dropwizardName metric name. 146 * @param snapshot the histogram snapshot. 147 * @param count the total sample count for this snapshot. 148 * @param factor a factor to apply to histogram values. 149 */ 150 MetricFamilySamples fromSnapshotAndCount(String dropwizardName, Snapshot snapshot, long count, double factor, String helpMessage) { 151 long sum = 0; 152 long[] values = snapshot.getValues(); 153 if (values != null){ 154 for (long value : values) { 155 sum += value; 156 } 157 } 158 List<MetricFamilySamples.Sample> samples = Arrays.asList( 159 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.5"), snapshot.getMedian() * factor), 160 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.75"), snapshot.get75thPercentile() * factor), 161 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.95"), snapshot.get95thPercentile() * factor), 162 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.98"), snapshot.get98thPercentile() * factor), 163 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.99"), snapshot.get99thPercentile() * factor), 164 sampleBuilder.createSample(dropwizardName, "", getDefaultAdditionalLabelNames("quantile"), getDefaultAdditionalLabelValues("0.999"), snapshot.get999thPercentile() * factor), 165 sampleBuilder.createSample(dropwizardName, "_count" 166 , getDefaultAdditionalLabelNames() 167 , getDefaultAdditionalLabelValues() 168 , count), 169 sampleBuilder.createSample(dropwizardName, "_sum" 170 , getDefaultAdditionalLabelNames() 171 , getDefaultAdditionalLabelValues() 172 , sum), 173 sampleBuilder.createSample(dropwizardName, "_mean" 174 , getDefaultAdditionalLabelNames() 175 , getDefaultAdditionalLabelValues() 176 , snapshot.getMean()), 177 sampleBuilder.createSample(dropwizardName, "_median" 178 , getDefaultAdditionalLabelNames() 179 , getDefaultAdditionalLabelValues() 180 , snapshot.getMedian()), 181 sampleBuilder.createSample(dropwizardName, "_min" 182 , getDefaultAdditionalLabelNames() 183 , getDefaultAdditionalLabelValues() 184 , snapshot.getMin()), 185 sampleBuilder.createSample(dropwizardName, "_max" 186 , getDefaultAdditionalLabelNames() 187 , getDefaultAdditionalLabelValues() 188 , snapshot.getMax()) 189 ); 190 return new MetricFamilySamples(samples.get(0).name, Type.SUMMARY, helpMessage, samples); 191 } 192 193 /** 194 * Convert histogram snapshot. 195 */ 196 MetricFamilySamples fromHistogram(String dropwizardName, Histogram histogram) { 197 return fromSnapshotAndCount(dropwizardName, histogram.getSnapshot(), histogram.getCount(), 1.0, 198 getHelpMessage(dropwizardName, histogram)); 199 } 200 201 /** 202 * Export Dropwizard Timer as a histogram. Use TIME_UNIT as time unit. 203 */ 204 MetricFamilySamples fromTimer(String dropwizardName, Timer timer) { 205 return fromSnapshotAndCount(dropwizardName, timer.getSnapshot(), timer.getCount(), 206 1.0D / TimeUnit.SECONDS.toNanos(1L), getHelpMessage(dropwizardName, timer)); 207 } 208 209 /** 210 * Export a Meter as as prometheus COUNTER. 211 */ 212 MetricFamilySamples fromMeter(String dropwizardName, Meter meter) { 213 final MetricFamilySamples.Sample sample = sampleBuilder.createSample(dropwizardName, "_total" 214 , getDefaultAdditionalLabelNames() 215 , getDefaultAdditionalLabelValues() 216 , meter.getCount()); 217 return new MetricFamilySamples(sample.name, Type.COUNTER, getHelpMessage(dropwizardName, meter), 218 Arrays.asList(sample)); 219 } 220 221 @Override 222 public List<MetricFamilySamples> collect() { 223 Map<String, MetricFamilySamples> mfSamplesMap = new HashMap<String, MetricFamilySamples>(); 224 225 for (SortedMap.Entry<String, Gauge> entry : registry.getGauges().entrySet()) { 226 addToMap(mfSamplesMap, fromGauge(entry.getKey(), entry.getValue())); 227 } 228 for (SortedMap.Entry<String, Counter> entry : registry.getCounters().entrySet()) { 229 addToMap(mfSamplesMap, fromCounter(entry.getKey(), entry.getValue())); 230 } 231 for (SortedMap.Entry<String, Histogram> entry : registry.getHistograms().entrySet()) { 232 addToMap(mfSamplesMap, fromHistogram(entry.getKey(), entry.getValue())); 233 } 234 for (SortedMap.Entry<String, Timer> entry : registry.getTimers().entrySet()) { 235 addToMap(mfSamplesMap, fromTimer(entry.getKey(), entry.getValue())); 236 } 237 for (SortedMap.Entry<String, Meter> entry : registry.getMeters().entrySet()) { 238 addToMap(mfSamplesMap, fromMeter(entry.getKey(), entry.getValue())); 239 } 240 return new ArrayList<MetricFamilySamples>(mfSamplesMap.values()); 241 } 242 243 private void addToMap(Map<String, MetricFamilySamples> mfSamplesMap, MetricFamilySamples newMfSamples) { 244 if (newMfSamples != null) { 245 MetricFamilySamples currentMfSamples = mfSamplesMap.get(newMfSamples.name); 246 if (currentMfSamples == null) { 247 mfSamplesMap.put(newMfSamples.name, newMfSamples); 248 } else { 249 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(currentMfSamples.samples); 250 samples.addAll(newMfSamples.samples); 251 mfSamplesMap.put(newMfSamples.name, new MetricFamilySamples(newMfSamples.name, currentMfSamples.type, currentMfSamples.help, samples)); 252 } 253 } 254 } 255 256 @Override 257 public List<MetricFamilySamples> describe() { 258 return new ArrayList<MetricFamilySamples>(); 259 } 260}