/** * Copyright 2017 Pivotal Software, Inc. * * 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 org.springframework.metrics.instrument.prometheus; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Gauge; import io.prometheus.client.SimpleCollector; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.metrics.instrument.*; import org.springframework.metrics.instrument.Timer; import org.springframework.metrics.instrument.internal.AbstractMeterRegistry; import org.springframework.metrics.instrument.internal.ImmutableTag; import org.springframework.metrics.instrument.internal.MeterId; import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.ToDoubleFunction; import java.util.stream.Collectors; import static java.util.stream.StreamSupport.stream; import static org.springframework.metrics.instrument.internal.MeterId.id; public class PrometheusMeterRegistry extends AbstractMeterRegistry { private final CollectorRegistry registry; private final Map<String, Collector> collectorMap = new ConcurrentHashMap<>(); private final Map<MeterId, Meter> meterMap = new ConcurrentHashMap<>(); public PrometheusMeterRegistry() { this(CollectorRegistry.defaultRegistry); } public PrometheusMeterRegistry(CollectorRegistry registry) { this(registry, Clock.SYSTEM); } @Autowired public PrometheusMeterRegistry(CollectorRegistry registry, Clock clock) { super(clock); this.registry = registry; } @Override public Collection<Meter> getMeters() { return meterMap.values(); } @Override public <M extends Meter> Optional<M> findMeter(Class<M> mClass, String name, Iterable<Tag> tags) { Collection<Tag> tagsToMatch = new ArrayList<>(); tags.forEach(tagsToMatch::add); //noinspection unchecked return meterMap.keySet().stream() .filter(id -> id.getName().equals(name)) .filter(id -> Arrays.asList(id.getTags()).containsAll(tagsToMatch)) .findAny() .map(meterMap::get) .map(m -> (M) m); } @Override public Counter counter(String name, Iterable<Tag> tags) { MeterId id = id(name, tags); io.prometheus.client.Counter counter = (io.prometheus.client.Counter) collectorMap.computeIfAbsent(name, i -> buildCollector(id, io.prometheus.client.Counter.build())); return (Counter) meterMap.computeIfAbsent(id, c -> new PrometheusCounter(name, child(counter, id.getTags()))); } @Override public DistributionSummary distributionSummary(String name, Iterable<Tag> tags) { MeterId id = id(name, tags); io.prometheus.client.Summary summary = (io.prometheus.client.Summary) collectorMap.computeIfAbsent(name, i -> buildCollector(id, io.prometheus.client.Summary.build())); return (DistributionSummary) meterMap.computeIfAbsent(id, s -> new PrometheusDistributionSummary(name, child(summary, id.getTags()))); } @Override public Timer timer(String name, Iterable<Tag> tags) { MeterId id = id(name, tags); io.prometheus.client.Summary summary = (io.prometheus.client.Summary) collectorMap.computeIfAbsent(name, i -> buildCollector(id, io.prometheus.client.Summary.build())); return (Timer) meterMap.computeIfAbsent(id, s -> new PrometheusTimer(name, child(summary, id.getTags()), getClock())); } @Override public LongTaskTimer longTaskTimer(String name, Iterable<Tag> tags) { return new PrometheusLongTaskTimer(name, tags, getClock()); } @SuppressWarnings("unchecked") @Override public <T> T gauge(String name, Iterable<Tag> tags, T obj, ToDoubleFunction<T> f) { final WeakReference<T> ref = new WeakReference<>(obj); MeterId id = id(name, tags); io.prometheus.client.Gauge gauge = (io.prometheus.client.Gauge) collectorMap.computeIfAbsent(name, i -> buildCollector(id, io.prometheus.client.Gauge.build())); meterMap.computeIfAbsent(id, g -> { gauge.setChild(new Gauge.Child() { @Override public double get() { final T obj = ref.get(); return (obj == null) ? Double.NaN : f.applyAsDouble(obj); } }); return new PrometheusGauge(name, child(gauge, id.getTags())); }); return obj; } private <B extends SimpleCollector.Builder<B, C>, C extends SimpleCollector<D>, D> C buildCollector(MeterId id, SimpleCollector.Builder<B, C> builder) { return builder .name(id.getName()) .help(" ") .labelNames(Arrays.stream(id.getTags()) .map(Tag::getKey) .collect(Collectors.toList()) .toArray(new String[]{})) .register(registry); } private <C extends SimpleCollector<D>, D> D child(C collector, Tag[] tags) { return collector.labels(Arrays.stream(tags) .map(Tag::getValue) .collect(Collectors.toList()) .toArray(new String[]{})); } }