package cc.mrbird.febs.monitor.endpoint; 
 | 
  
 | 
import cc.mrbird.febs.common.annotation.FebsEndPoint; 
 | 
import io.micrometer.core.instrument.Meter; 
 | 
import io.micrometer.core.instrument.MeterRegistry; 
 | 
import io.micrometer.core.instrument.Statistic; 
 | 
import io.micrometer.core.instrument.Tag; 
 | 
import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 
 | 
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; 
 | 
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 
 | 
import org.springframework.boot.actuate.endpoint.annotation.Selector; 
 | 
import org.springframework.lang.Nullable; 
 | 
  
 | 
import java.util.*; 
 | 
import java.util.function.BiFunction; 
 | 
import java.util.stream.Collectors; 
 | 
  
 | 
/** 
 | 
 * @author MrBird 
 | 
 */ 
 | 
@FebsEndPoint 
 | 
public class FebsMetricsEndpoint { 
 | 
  
 | 
    private final MeterRegistry registry; 
 | 
  
 | 
    public FebsMetricsEndpoint(MeterRegistry registry) { 
 | 
        this.registry = registry; 
 | 
    } 
 | 
  
 | 
    @ReadOperation 
 | 
    public ListNamesResponse listNames() { 
 | 
        Set<String> names = new LinkedHashSet<>(); 
 | 
        this.collectNames(names, this.registry); 
 | 
        return new ListNamesResponse(names); 
 | 
    } 
 | 
  
 | 
    private void collectNames(Set<String> names, MeterRegistry registry) { 
 | 
        if (registry instanceof CompositeMeterRegistry) { 
 | 
            ((CompositeMeterRegistry)registry).getRegistries().forEach((member) -> this.collectNames(names, member)); 
 | 
        } else { 
 | 
            registry.getMeters().stream().map(this::getName).forEach(names::add); 
 | 
        } 
 | 
  
 | 
    } 
 | 
  
 | 
    private String getName(Meter meter) { 
 | 
        return meter.getId().getName(); 
 | 
    } 
 | 
  
 | 
    @ReadOperation 
 | 
    public FebsMetricResponse metric(@Selector String requiredMetricName, @Nullable List<String> tag) { 
 | 
        List<Tag> tags = this.parseTags(tag); 
 | 
        Collection<Meter> meters = this.findFirstMatchingMeters(this.registry, requiredMetricName, tags); 
 | 
        if (meters.isEmpty()) { 
 | 
            return null; 
 | 
        } else { 
 | 
            Map<Statistic, Double> samples = this.getSamples(meters); 
 | 
            Map<String, Set<String>> availableTags = this.getAvailableTags(meters); 
 | 
            tags.forEach((t) -> availableTags.remove(t.getKey())); 
 | 
            Meter.Id meterId = meters.iterator().next().getId(); 
 | 
            return new FebsMetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(), this.asList(samples, Sample::new), this.asList(availableTags, AvailableTag::new)); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    private List<Tag> parseTags(List<String> tags) { 
 | 
        return tags == null ? Collections.emptyList() : tags.stream().map(this::parseTag).collect(Collectors.toList()); 
 | 
    } 
 | 
  
 | 
    private Tag parseTag(String tag) { 
 | 
        String[] parts = tag.split(":", 2); 
 | 
        if (parts.length != 2) { 
 | 
            throw new InvalidEndpointRequestException("Each tag parameter must be in the form 'key:value' but was: " + tag, "Each tag parameter must be in the form 'key:value'"); 
 | 
        } else { 
 | 
            return Tag.of(parts[0], parts[1]); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    private Collection<Meter> findFirstMatchingMeters(MeterRegistry registry, String name, Iterable<Tag> tags) { 
 | 
        return registry instanceof CompositeMeterRegistry ? this.findFirstMatchingMeters((CompositeMeterRegistry)registry, name, tags) : registry.find(name).tags(tags).meters(); 
 | 
    } 
 | 
  
 | 
    private Collection<Meter> findFirstMatchingMeters(CompositeMeterRegistry composite, String name, Iterable<Tag> tags) { 
 | 
        return composite.getRegistries().stream().map((registry) -> this.findFirstMatchingMeters(registry, name, tags)).filter((matching) -> !matching.isEmpty()).findFirst().orElse(Collections.emptyList()); 
 | 
    } 
 | 
  
 | 
    private Map<Statistic, Double> getSamples(Collection<Meter> meters) { 
 | 
        Map<Statistic, Double> samples = new LinkedHashMap<>(); 
 | 
        meters.forEach((meter) -> this.mergeMeasurements(samples, meter)); 
 | 
        return samples; 
 | 
    } 
 | 
  
 | 
    private void mergeMeasurements(Map<Statistic, Double> samples, Meter meter) { 
 | 
        meter.measure().forEach((measurement) -> samples.merge(measurement.getStatistic(), measurement.getValue(), this.mergeFunction(measurement.getStatistic()))); 
 | 
    } 
 | 
  
 | 
    private BiFunction<Double, Double, Double> mergeFunction(Statistic statistic) { 
 | 
        return Statistic.MAX.equals(statistic) ? Double::max : Double::sum; 
 | 
    } 
 | 
  
 | 
    private Map<String, Set<String>> getAvailableTags(Collection<Meter> meters) { 
 | 
        Map<String, Set<String>> availableTags = new HashMap<>(10); 
 | 
        meters.forEach((meter) -> this.mergeAvailableTags(availableTags, meter)); 
 | 
        return availableTags; 
 | 
    } 
 | 
  
 | 
    private void mergeAvailableTags(Map<String, Set<String>> availableTags, Meter meter) { 
 | 
        meter.getId().getTags().forEach((tag) -> { 
 | 
            Set<String> value = Collections.singleton(tag.getValue()); 
 | 
            availableTags.merge(tag.getKey(), value, this::merge); 
 | 
        }); 
 | 
    } 
 | 
  
 | 
    private <T> Set<T> merge(Set<T> set1, Set<T> set2) { 
 | 
        Set<T> result = new HashSet<>(set1.size() + set2.size()); 
 | 
        result.addAll(set1); 
 | 
        result.addAll(set2); 
 | 
        return result; 
 | 
    } 
 | 
  
 | 
    private <K, V, T> List<T> asList(Map<K, V> map, BiFunction<K, V, T> mapper) { 
 | 
        return map.entrySet().stream().map((entry) -> mapper.apply(entry.getKey(), entry.getValue())).collect(Collectors.toList()); 
 | 
    } 
 | 
  
 | 
    public static final class Sample { 
 | 
        private final Statistic statistic; 
 | 
        private final Double value; 
 | 
  
 | 
        Sample(Statistic statistic, Double value) { 
 | 
            this.statistic = statistic; 
 | 
            this.value = value; 
 | 
        } 
 | 
  
 | 
        public Statistic getStatistic() { 
 | 
            return this.statistic; 
 | 
        } 
 | 
  
 | 
        public Double getValue() { 
 | 
            return this.value; 
 | 
        } 
 | 
  
 | 
        @Override 
 | 
        public String toString() { 
 | 
            return "MeasurementSample{statistic=" + this.statistic + ", value=" + this.value + '}'; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static final class AvailableTag { 
 | 
        private final String tag; 
 | 
        private final Set<String> values; 
 | 
  
 | 
        AvailableTag(String tag, Set<String> values) { 
 | 
            this.tag = tag; 
 | 
            this.values = values; 
 | 
        } 
 | 
  
 | 
        public String getTag() { 
 | 
            return this.tag; 
 | 
        } 
 | 
  
 | 
        public Set<String> getValues() { 
 | 
            return this.values; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static final class FebsMetricResponse { 
 | 
        private final String name; 
 | 
        private final String description; 
 | 
        private final String baseUnit; 
 | 
        private final List<Sample> measurements; 
 | 
        private final List<AvailableTag> availableTags; 
 | 
  
 | 
        FebsMetricResponse(String name, String description, String baseUnit, List<Sample> measurements, List<AvailableTag> availableTags) { 
 | 
            this.name = name; 
 | 
            this.description = description; 
 | 
            this.baseUnit = baseUnit; 
 | 
            this.measurements = measurements; 
 | 
            this.availableTags = availableTags; 
 | 
        } 
 | 
  
 | 
        public String getName() { 
 | 
            return this.name; 
 | 
        } 
 | 
  
 | 
        public String getDescription() { 
 | 
            return this.description; 
 | 
        } 
 | 
  
 | 
        public String getBaseUnit() { 
 | 
            return this.baseUnit; 
 | 
        } 
 | 
  
 | 
        public List<Sample> getMeasurements() { 
 | 
            return this.measurements; 
 | 
        } 
 | 
  
 | 
        public List<AvailableTag> getAvailableTags() { 
 | 
            return this.availableTags; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    public static final class ListNamesResponse { 
 | 
        private final Set<String> names; 
 | 
  
 | 
        ListNamesResponse(Set<String> names) { 
 | 
            this.names = names; 
 | 
        } 
 | 
  
 | 
        public Set<String> getNames() { 
 | 
            return this.names; 
 | 
        } 
 | 
    } 
 | 
} 
 |