001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
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 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
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 */
016
017package org.springframework.boot.actuate.autoconfigure.metrics;
018
019import java.util.Arrays;
020import java.util.Map;
021import java.util.Objects;
022import java.util.function.Supplier;
023import java.util.stream.Collectors;
024
025import io.micrometer.core.instrument.Meter;
026import io.micrometer.core.instrument.Meter.Id;
027import io.micrometer.core.instrument.Tag;
028import io.micrometer.core.instrument.Tags;
029import io.micrometer.core.instrument.config.MeterFilter;
030import io.micrometer.core.instrument.config.MeterFilterReply;
031import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
032
033import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Distribution;
034import org.springframework.util.Assert;
035import org.springframework.util.StringUtils;
036
037/**
038 * {@link MeterFilter} to apply settings from {@link MetricsProperties}.
039 *
040 * @author Jon Schneider
041 * @author Phillip Webb
042 * @author Stephane Nicoll
043 * @author Artsiom Yudovin
044 * @author Alexander Abramov
045 * @since 2.0.0
046 */
047public class PropertiesMeterFilter implements MeterFilter {
048
049        private final MetricsProperties properties;
050
051        private final MeterFilter mapFilter;
052
053        public PropertiesMeterFilter(MetricsProperties properties) {
054                Assert.notNull(properties, "Properties must not be null");
055                this.properties = properties;
056                this.mapFilter = createMapFilter(properties.getTags());
057        }
058
059        private static MeterFilter createMapFilter(Map<String, String> tags) {
060                if (tags.isEmpty()) {
061                        return new MeterFilter() {
062                        };
063                }
064                Tags commonTags = Tags.of(tags.entrySet().stream()
065                                .map((entry) -> Tag.of(entry.getKey(), entry.getValue()))
066                                .collect(Collectors.toList()));
067                return MeterFilter.commonTags(commonTags);
068        }
069
070        @Override
071        public MeterFilterReply accept(Meter.Id id) {
072                boolean enabled = lookupWithFallbackToAll(this.properties.getEnable(), id, true);
073                return enabled ? MeterFilterReply.NEUTRAL : MeterFilterReply.DENY;
074        }
075
076        @Override
077        public Id map(Id id) {
078                return this.mapFilter.map(id);
079        }
080
081        @Override
082        public DistributionStatisticConfig configure(Meter.Id id,
083                        DistributionStatisticConfig config) {
084                Distribution distribution = this.properties.getDistribution();
085                return DistributionStatisticConfig.builder()
086                                .percentilesHistogram(lookupWithFallbackToAll(
087                                                distribution.getPercentilesHistogram(), id, null))
088                                .percentiles(
089                                                lookupWithFallbackToAll(distribution.getPercentiles(), id, null))
090                                .sla(convertSla(id.getType(), lookup(distribution.getSla(), id, null)))
091                                .minimumExpectedValue(convertMeterValue(id.getType(),
092                                                lookup(distribution.getMinimumExpectedValue(), id, null)))
093                                .maximumExpectedValue(convertMeterValue(id.getType(),
094                                                lookup(distribution.getMaximumExpectedValue(), id, null)))
095                                .build().merge(config);
096        }
097
098        private long[] convertSla(Meter.Type meterType, ServiceLevelAgreementBoundary[] sla) {
099                if (sla == null) {
100                        return null;
101                }
102                long[] converted = Arrays.stream(sla)
103                                .map((candidate) -> candidate.getValue(meterType))
104                                .filter(Objects::nonNull).mapToLong(Long::longValue).toArray();
105                return (converted.length != 0) ? converted : null;
106        }
107
108        private Long convertMeterValue(Meter.Type meterType, String value) {
109                return (value != null) ? MeterValue.valueOf(value).getValue(meterType) : null;
110        }
111
112        private <T> T lookup(Map<String, T> values, Id id, T defaultValue) {
113                if (values.isEmpty()) {
114                        return defaultValue;
115                }
116                return doLookup(values, id, () -> defaultValue);
117        }
118
119        private <T> T lookupWithFallbackToAll(Map<String, T> values, Id id, T defaultValue) {
120                if (values.isEmpty()) {
121                        return defaultValue;
122                }
123                return doLookup(values, id, () -> values.getOrDefault("all", defaultValue));
124        }
125
126        private <T> T doLookup(Map<String, T> values, Id id, Supplier<T> defaultValue) {
127                String name = id.getName();
128                while (StringUtils.hasLength(name)) {
129                        T result = values.get(name);
130                        if (result != null) {
131                                return result;
132                        }
133                        int lastDot = name.lastIndexOf('.');
134                        name = (lastDot != -1) ? name.substring(0, lastDot) : "";
135                }
136
137                return defaultValue.get();
138        }
139
140}