All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.metrics.PrometheusMeterFacade Maven / Gradle / Ivy

There is a newer version: 3.1.72
Show newest version
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * 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 com.wl4g.infra.common.metrics;

import com.google.common.collect.Lists;
import com.wl4g.infra.common.net.InetUtils;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
import io.micrometer.prometheus.PrometheusCounter;
import io.micrometer.prometheus.PrometheusDistributionSummary;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micrometer.prometheus.PrometheusTimer;
import io.prometheus.client.Collector.MetricFamilySamples;
import io.prometheus.client.CounterMetricFamily;
import io.prometheus.client.GaugeMetricFamily;
import io.prometheus.client.SummaryMetricFamily;
import lombok.AllArgsConstructor;
import lombok.CustomLog;
import lombok.Getter;

import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.wl4g.infra.common.lang.Assert2.hasTextOf;
import static com.wl4g.infra.common.lang.Assert2.notNullOf;
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.util.Arrays.asList;
import static java.util.Objects.isNull;

/**
 * {@link PrometheusMeterFacade}, Tends to use Prometheus standards.
 * 

* Counter, Timer, Gauge, DistributionSummary, etc. *

* {@link PrometheusMeterRegistry} * * @author <James Wong James Wong > * @version 2021-11-16 v1.0.0 * @since v1.0.0 */ @Getter @CustomLog @SuppressWarnings("unused") public class PrometheusMeterFacade { private final String serviceId; private final boolean secure; private final int port; private final PrometheusMeterRegistry meterRegistry; private final Map sampleRegistry = new ConcurrentHashMap<>(16); private final InetUtils inet; private final LocalInstanceSpec localSpec; public PrometheusMeterFacade(PrometheusMeterRegistry meterRegistry, String serviceId, boolean secure, InetUtils inet, int port) { this.meterRegistry = notNullOf(meterRegistry, "meterRegistry"); this.serviceId = hasTextOf(serviceId, "serviceId"); this.secure = secure; this.inet = notNullOf(inet, "inet"); this.port = port; this.localSpec = createLocalInstanceSpec(); } protected LocalInstanceSpec createLocalInstanceSpec() { String host = inet.findFirstNonLoopbackHostInfo().getHostname(); String instanceId = host.concat(":").concat(valueOf(port)); return new LocalInstanceSpec(instanceId, serviceId, host, port, secure); } protected String[] applyDefaultTags(String... tags) { List _tags = Lists.newArrayList(tags); _tags.add(TAG_SELF_ID); _tags.add(localSpec.getInstanceId()); return _tags.toArray(new String[0]); } // // --- The Meter Metrics Active Recorder. --- // // --- Counter. --- public Counter counter(String name, String help, String... tags) { try { return Counter.builder(name).description(help).tags(applyDefaultTags(tags)).register(meterRegistry); } catch (Throwable e) { log.error(format("Unable to counter meter for metrics: '%s', help: '%s', tags: %s", name, help, asList(tags)), e); return EMPTY_COUNTER; } } // --- Gauge. --- public Gauge gauge(String name, String help, double number, String... tags) { try { return gauge(name, help, () -> number, tags); } catch (Throwable e) { log.error(format("Unable to gauge meter for metrics: '%s', help: '%s', tags: %s", name, help, asList(tags)), e); return EMPTY_GAUGE; } } public Gauge gauge(String name, String help, Supplier supplier, String... tags) { try { return Gauge.builder(name, supplier).description(help).tags(applyDefaultTags(tags)).register(meterRegistry); } catch (Throwable e) { log.error(format("Unable to gauge meter for metrics: '%s', help: '%s', tags: %s", name, help, asList(tags)), e); return EMPTY_GAUGE; } } // --- Timer. --- /** * It is recommended to use {@link #timer(String, String, Duration[], String...)} because * it generates data distributed by original buckets, which is more conducive to global analysis * of multiple Pods metrics on the metrics server instead of letting the client (single pod calculation) */ @Deprecated public Timer timer(String name, String help, double[] percentiles, String... tags) { try { return Timer.builder(name) .description(help) .tags(applyDefaultTags(tags)) .publishPercentiles(percentiles) .publishPercentileHistogram() .register(meterRegistry); } catch (Throwable e) { log.error(format("Unable to timer meter for metrics: '%s', help: '%s', tags: %s", name, help, asList(tags)), e); return EMPTY_TIMER; } } /** * io.micrometer first hard-codes added the default distribution bucket (which may be very unreasonable * and does not conform to the actual situation), but allows custom settings of slos (service level objectives). * The purpose of its design should be to prevent users from arbitrarily settings to ensure possible rationality. *

* see to {@link io.micrometer.core.instrument.distribution.PercentileHistogramBuckets#buckets(DistributionStatisticConfig)} * and {@link io.micrometer.core.instrument.distribution.DistributionStatisticConfig#getHistogramBuckets(boolean)} *

*/ public Timer timer(String name, String help, @Nullable Duration[] slos, String... tags) { try { return Timer.builder(name) .description(help) .tags(applyDefaultTags(tags)) .publishPercentileHistogram() .serviceLevelObjectives(slos) .register(meterRegistry); } catch (Throwable e) { log.error(format("Unable to timer meter for metrics: '%s', help: '%s', tags: %s", name, help, asList(tags)), e); return EMPTY_TIMER; } } // --- Summary. --- /** * Used default configuration refer to: * {@link DistributionStatisticConfig#DEFAULT} */ public DistributionSummary summary(String name, String help, double scale, double[] percentiles, String... tags) { return DistributionSummary.builder(name) .description(help) .tags(applyDefaultTags(tags)) .publishPercentiles(percentiles) .publishPercentileHistogram() .scale(scale) .register(meterRegistry); } /** * Used default configuration refer to: * {@link DistributionStatisticConfig#DEFAULT} */ public DistributionSummary summarySlos(String name, String help, double scale, double[] slos, String... tags) { return DistributionSummary.builder(name) .description(help) .tags(applyDefaultTags(tags)) .serviceLevelObjectives(slos) .scale(scale) .register(meterRegistry); } public DistributionSummary summary(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double scale) { List tags = Lists.newArrayList(id.getTags()); tags.add(new ImmutableTag(TAG_SELF_ID, localSpec.getInstanceId())); Meter.Id newId = new Meter.Id(id.getName(), Tags.of(tags), id.getBaseUnit(), id.getDescription(), id.getType()); return meterRegistry.newDistributionSummary(newId, distributionStatisticConfig, scale); } // // --- The Metrics Passive Collector. --- // public GaugeMetricFamily getGauge(String name, String help, String... labelNames) { MetricFamilySamples samples = sampleRegistry.get(name); if (isNull(samples)) { synchronized (this) { samples = sampleRegistry.get(name); if (isNull(samples)) { sampleRegistry.put(name, samples = new GaugeMetricFamily(name, help, asList(labelNames))); } } } samples = new GaugeMetricFamily(name, help, asList(labelNames)); return (GaugeMetricFamily) samples; } public CounterMetricFamily getCounter(String name, String help, String... labelNames) { MetricFamilySamples samples = sampleRegistry.get(name); if (isNull(samples)) { synchronized (this) { samples = sampleRegistry.get(name); if (isNull(samples)) { sampleRegistry.put(name, samples = new CounterMetricFamily(name, help, asList(labelNames))); } } } return (CounterMetricFamily) samples; } public SummaryMetricFamily getSummary(String name, String help, String... labelNames) { MetricFamilySamples samples = sampleRegistry.get(name); if (isNull(samples)) { synchronized (this) { samples = sampleRegistry.get(name); if (isNull(samples)) { sampleRegistry.put(name, samples = new SummaryMetricFamily(name, help, asList(labelNames))); } } } return (SummaryMetricFamily) samples; } // // --- Tools Function. --- // public static Counter newPrometheusCounter(Meter.Id id) { return newConstructor(PrometheusCounter.class, id); } public static Timer newPrometheusTimer(Meter.Id id) { return newConstructor(PrometheusTimer.class, id); } public static DistributionSummary newPrometheusDistributionSummary(Meter.Id id) { return newConstructor(PrometheusDistributionSummary.class, id); } @SuppressWarnings("unchecked") public static T newConstructor(Class clazz, Object... args) { try { Constructor constructor = (Constructor) CONSTRUCTOR_MAP.get(clazz); if (isNull(constructor)) { synchronized (PrometheusMeterFacade.class) { if (isNull(constructor)) { CONSTRUCTOR_MAP.put(clazz, constructor = getConstructor0(clazz)); } } } return (T) constructor.newInstance(args); } catch (Exception e) { throw new IllegalStateException(e); } } static Constructor getConstructor0(Class clazz) throws NoSuchMethodException, SecurityException { Constructor constructor = clazz.getDeclaredConstructor(Meter.Id.class); constructor.setAccessible(true); return constructor; } static final Map, Constructor> CONSTRUCTOR_MAP = new ConcurrentHashMap<>(8); @Getter @AllArgsConstructor protected static class LocalInstanceSpec { private String instanceId; private String serviceId; private String host; private int port; private boolean secure; } public static final String TAG_SELF_ID = "self"; public static final Counter EMPTY_COUNTER = new Counter() { @Override public Id getId() { return null; } @Override public void increment(double amount) { } @Override public double count() { return 0; } }; public static final Gauge EMPTY_GAUGE = new Gauge() { @Override public Id getId() { return null; } @Override public double value() { return 0; } }; public static final Timer EMPTY_TIMER = new Timer() { @Override public Id getId() { return null; } @Override public HistogramSnapshot takeSnapshot() { return null; } @Override public void record(long amount, TimeUnit unit) { } @Override public T record(Supplier f) { return null; } @Override public T recordCallable(Callable f) throws Exception { return null; } @Override public void record(Runnable f) { } @Override public long count() { return 0; } @Override public double totalTime(TimeUnit unit) { return 0; } @Override public double max(TimeUnit unit) { return 0; } @Override public TimeUnit baseTimeUnit() { return null; } }; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy