001package io.prometheus.metrics.model.registry; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.Collection; 006import java.util.function.Predicate; 007 008import static java.util.Collections.unmodifiableCollection; 009 010/** 011 * Filter samples (i.e. time series) by name. 012 */ 013public class MetricNameFilter implements Predicate<String> { 014 015 /** 016 * For convenience, a filter that allows all names. 017 */ 018 public static final Predicate<String> ALLOW_ALL = name -> true; 019 020 private final Collection<String> nameIsEqualTo; 021 private final Collection<String> nameIsNotEqualTo; 022 private final Collection<String> nameStartsWith; 023 private final Collection<String> nameDoesNotStartWith; 024 025 private MetricNameFilter(Collection<String> nameIsEqualTo, Collection<String> nameIsNotEqualTo, Collection<String> nameStartsWith, Collection<String> nameDoesNotStartWith) { 026 this.nameIsEqualTo = unmodifiableCollection(new ArrayList<>(nameIsEqualTo)); 027 this.nameIsNotEqualTo = unmodifiableCollection(new ArrayList<>(nameIsNotEqualTo)); 028 this.nameStartsWith = unmodifiableCollection(new ArrayList<>(nameStartsWith)); 029 this.nameDoesNotStartWith = unmodifiableCollection(new ArrayList<>(nameDoesNotStartWith)); 030 } 031 032 @Override 033 public boolean test(String sampleName) { 034 return matchesNameEqualTo(sampleName) 035 && !matchesNameNotEqualTo(sampleName) 036 && matchesNameStartsWith(sampleName) 037 && !matchesNameDoesNotStartWith(sampleName); 038 } 039 040 private boolean matchesNameEqualTo(String metricName) { 041 if (nameIsEqualTo.isEmpty()) { 042 return true; 043 } 044 for (String name : nameIsEqualTo) { 045 // The following ignores suffixes like _total. 046 // "request_count" and "request_count_total" both match a metric named "request_count". 047 if (name.startsWith(metricName)) { 048 return true; 049 } 050 } 051 return false; 052 } 053 054 private boolean matchesNameNotEqualTo(String metricName) { 055 if (nameIsNotEqualTo.isEmpty()) { 056 return false; 057 } 058 for (String name : nameIsNotEqualTo) { 059 // The following ignores suffixes like _total. 060 // "request_count" and "request_count_total" both match a metric named "request_count". 061 if (name.startsWith(metricName)) { 062 return true; 063 } 064 } 065 return false; 066 } 067 068 private boolean matchesNameStartsWith(String metricName) { 069 if (nameStartsWith.isEmpty()) { 070 return true; 071 } 072 for (String prefix : nameStartsWith) { 073 if (metricName.startsWith(prefix)) { 074 return true; 075 } 076 } 077 return false; 078 } 079 080 private boolean matchesNameDoesNotStartWith(String metricName) { 081 if (nameDoesNotStartWith.isEmpty()) { 082 return false; 083 } 084 for (String prefix : nameDoesNotStartWith) { 085 if (metricName.startsWith(prefix)) { 086 return true; 087 } 088 } 089 return false; 090 } 091 092 public static Builder builder() { 093 return new Builder(); 094 } 095 096 public static class Builder { 097 098 private final Collection<String> nameEqualTo = new ArrayList<>(); 099 private final Collection<String> nameNotEqualTo = new ArrayList<>(); 100 private final Collection<String> nameStartsWith = new ArrayList<>(); 101 private final Collection<String> nameDoesNotStartWith = new ArrayList<>(); 102 103 private Builder() { 104 } 105 106 /** 107 * @see #nameMustBeEqualTo(Collection) 108 */ 109 public Builder nameMustBeEqualTo(String... names) { 110 return nameMustBeEqualTo(Arrays.asList(names)); 111 } 112 113 /** 114 * Only samples with one of the {@code names} will be included. 115 * <p> 116 * Note that the provided {@code names} will be matched against the sample name (i.e. the time series name) 117 * and not the metric name. For instance, to retrieve all samples from a histogram, you must include the 118 * '_count', '_sum' and '_bucket' names. 119 * <p> 120 * This method should be used by HTTP exporters to implement the {@code ?name[]=} URL parameters. 121 * 122 * @param names empty means no restriction. 123 */ 124 public Builder nameMustBeEqualTo(Collection<String> names) { 125 if (names != null) { 126 nameEqualTo.addAll(names); 127 } 128 return this; 129 } 130 131 /** 132 * @see #nameMustNotBeEqualTo(Collection) 133 */ 134 public Builder nameMustNotBeEqualTo(String... names) { 135 return nameMustNotBeEqualTo(Arrays.asList(names)); 136 } 137 138 /** 139 * All samples that are not in {@code names} will be excluded. 140 * <p> 141 * Note that the provided {@code names} will be matched against the sample name (i.e. the time series name) 142 * and not the metric name. For instance, to exclude all samples from a histogram, you must exclude the 143 * '_count', '_sum' and '_bucket' names. 144 * 145 * @param names empty means no name will be excluded. 146 */ 147 public Builder nameMustNotBeEqualTo(Collection<String> names) { 148 if (names != null) { 149 nameNotEqualTo.addAll(names); 150 } 151 return this; 152 } 153 154 /** 155 * @see #nameMustStartWith(Collection) 156 */ 157 public Builder nameMustStartWith(String... prefixes) { 158 return nameMustStartWith(Arrays.asList(prefixes)); 159 } 160 161 /** 162 * Only samples whose name starts with one of the {@code prefixes} will be included. 163 * 164 * @param prefixes empty means no restriction. 165 */ 166 public Builder nameMustStartWith(Collection<String> prefixes) { 167 if (prefixes != null) { 168 nameStartsWith.addAll(prefixes); 169 } 170 return this; 171 } 172 173 /** 174 * @see #nameMustNotStartWith(Collection) 175 */ 176 public Builder nameMustNotStartWith(String... prefixes) { 177 return nameMustNotStartWith(Arrays.asList(prefixes)); 178 } 179 180 /** 181 * Samples with names starting with one of the {@code prefixes} will be excluded. 182 * 183 * @param prefixes empty means no time series will be excluded. 184 */ 185 public Builder nameMustNotStartWith(Collection<String> prefixes) { 186 if (prefixes != null) { 187 nameDoesNotStartWith.addAll(prefixes); 188 } 189 return this; 190 } 191 192 public MetricNameFilter build() { 193 return new MetricNameFilter(nameEqualTo, nameNotEqualTo, nameStartsWith, nameDoesNotStartWith); 194 } 195 } 196}