001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.metrics; 020 021import io.micrometer.core.instrument.Meter; 022import io.micrometer.core.instrument.MeterRegistry; 023import io.micrometer.core.instrument.Tags; 024import io.micrometer.core.instrument.Timer; 025import io.micrometer.core.instrument.Counter; 026import io.micrometer.core.instrument.Gauge; 027import io.micrometer.core.instrument.search.Search; 028 029import java.time.Duration; 030import java.util.Arrays; 031import java.util.Objects; 032import java.util.concurrent.Callable; 033import java.util.concurrent.locks.Lock; 034import java.util.concurrent.locks.ReentrantLock; 035import java.util.function.Supplier; 036 037/** 038 * Factory helpers that create or look up Micrometer meters defined by 039 * {@link MeterInfo}, ensuring duplicate registrations resolve to the same instance. 040 */ 041public class MeterFactory { 042 /** Default constructor; no instance state to initialise. */ 043 public MeterFactory() {} 044 private static final Lock metersLock = new ReentrantLock(); 045 046 /** 047 * Returns the {@link Timer} associated with {@code meterInfo} and {@code tags}, 048 * creating it (with histogram and 50/95 percentiles) when absent. 049 * 050 * @param registry the Micrometer registry 051 * @param meterInfo meter id/description/default-tag descriptor 052 * @param tags extra tags to combine with {@link MeterInfo#add(Tags)} 053 * @return the (possibly existing) Timer 054 */ 055 public static Timer timer(MeterRegistry registry, MeterInfo meterInfo, Tags tags) { 056 return createMeter(registry, meterInfo, tags, 057 () -> Timer.builder(meterInfo.id()).tags(meterInfo.add(tags)).description(meterInfo.description()) 058 .publishPercentiles(0.5, 0.95) 059 .publishPercentileHistogram() 060 .minimumExpectedValue(Duration.ofMillis(1)) 061 .maximumExpectedValue(Duration.ofSeconds(60)) 062 .register(registry)); 063 } 064 065 /** 066 * Returns the {@link Counter} associated with {@code meterInfo} and {@code tags}, 067 * creating it when absent. 068 * 069 * @param registry the Micrometer registry 070 * @param meterInfo meter id/description/default-tag descriptor 071 * @param tags extra tags to combine with {@link MeterInfo#add(Tags)} 072 * @return the (possibly existing) Counter 073 */ 074 public static Counter counter(MeterRegistry registry, MeterInfo meterInfo, Tags tags) { 075 return createMeter(registry, meterInfo, tags, 076 () -> Counter.builder(meterInfo.id()).tags(meterInfo.add(tags)).description(meterInfo.description()).register(registry)); 077 } 078 079 /** 080 * Registers (or updates) a freely-named {@link Counter}, bypassing the 081 * {@link MeterInfo} catalog. 082 * 083 * @param registry the Micrometer registry 084 * @param meterName meter id 085 * @param tags meter tags 086 * @param description meter description 087 * @return the registered Counter 088 */ 089 public static Counter updateCounter(MeterRegistry registry, String meterName, Tags tags, String description) { 090 return Counter.builder(meterName).tags(tags).description(description).register(registry); 091 } 092 093 /** 094 * Registers (or updates) the {@link Counter} identified by {@code meterInfo}. 095 * 096 * @param registry the Micrometer registry 097 * @param meterInfo meter id/description/default-tag descriptor 098 * @param tags extra tags to combine with {@link MeterInfo#add(Tags)} 099 * @return the registered Counter 100 */ 101 public static Counter updateCounter(MeterRegistry registry, MeterInfo meterInfo, Tags tags) { 102 return updateCounter(registry, meterInfo.id(), meterInfo.add(tags), meterInfo.description()); 103 } 104 105 /** 106 * Returns the {@link Gauge} associated with {@code meterInfo} and {@code tags}, 107 * creating one bound to {@code n} when absent. 108 * 109 * @param registry the Micrometer registry 110 * @param meterInfo meter id/description/default-tag descriptor 111 * @param tags extra tags to combine with {@link MeterInfo#add(Tags)} 112 * @param unit base unit, or {@code null} for none 113 * @param n supplier called to read the current gauge value 114 * @return the (possibly existing) Gauge 115 */ 116 public static Gauge gauge(MeterRegistry registry, MeterInfo meterInfo, Tags tags, String unit, Supplier<Number> n) { 117 return createMeter(registry, meterInfo, tags, 118 () -> Gauge.builder(meterInfo.id(), n) 119 .tags(meterInfo.add(tags)) 120 .description(meterInfo.description()) 121 .baseUnit(unit) 122 .register(registry)); 123 } 124 125 /** 126 * Removes the supplied meters from the registry, skipping {@code null} entries. 127 * 128 * @param registry the Micrometer registry 129 * @param meters meters to remove 130 */ 131 public static void remove (MeterRegistry registry, Meter... meters) { 132 Arrays.stream(meters).filter(Objects::nonNull).forEach(registry::remove); 133 } 134 135 @SuppressWarnings("unchecked") 136 private static <T extends Meter> T createMeter(MeterRegistry registry, MeterInfo meterInfo, Tags tags, Callable<T> creator) { 137 metersLock.lock(); 138 try { 139 T meter = (T) Search.in(registry).name(meterInfo.id()).tags(meterInfo.add(tags)).meter(); 140 if (meter == null) { 141 try { 142 meter = creator.call(); 143 } catch (Exception e) { 144 throw new RuntimeException (e); 145 } 146 } 147 return meter; 148 } finally { 149 metersLock.unlock(); 150 } 151 } 152}