001package io.prometheus.metrics.instrumentation.dropwizard5.labels; 002 003import java.util.HashMap; 004import java.util.Map; 005import java.util.regex.Pattern; 006import javax.annotation.Nullable; 007 008/** 009 * POJO containing info on how to map a graphite metric to a prometheus one. Example mapping in yaml 010 * format: 011 * 012 * <p>match: test.dispatcher.*.*.* name: dispatcher_events_total labels: action: ${1} outcome: 013 * ${2}_out processor: ${0} status: ${1}_${2} 014 * 015 * <p>Dropwizard metrics that match the "match" pattern will be further processed to have a new name 016 * and new labels based on this config. 017 */ 018public final class MapperConfig { 019 // each part of the metric name between dots 020 private static final String METRIC_PART_REGEX = "[a-zA-Z_0-9](-?[a-zA-Z0-9_])+"; 021 // Simplified GLOB: we can have "*." at the beginning and "*" only at the end 022 static final String METRIC_GLOB_REGEX = 023 "^(\\*\\.|" + METRIC_PART_REGEX + "\\.)+(\\*|" + METRIC_PART_REGEX + ")$"; 024 // Labels validation. 025 private static final String LABEL_REGEX = "^[a-zA-Z_][a-zA-Z0-9_]+$"; 026 private static final Pattern MATCH_EXPRESSION_PATTERN = Pattern.compile(METRIC_GLOB_REGEX); 027 private static final Pattern LABEL_PATTERN = Pattern.compile(LABEL_REGEX); 028 029 /** 030 * Regex used to match incoming metric name. Uses a simplified glob syntax where only '*' are 031 * allowed. E.g: org.company.controller.*.status.* Will be used to match 032 * org.company.controller.controller1.status.200 and org.company.controller.controller2.status.400 033 */ 034 @Nullable private String match; 035 036 /** 037 * New metric name. Can contain placeholders to be replaced with actual values from the incoming 038 * metric name. Placeholders are in the ${n} format where n is the zero based index of the group 039 * to extract from the original metric name. E.g.: match: test.dispatcher.*.*.* name: 040 * dispatcher_events_total_${1} 041 * 042 * <p>A metric "test.dispatcher.old.test.yay" will be converted in a new metric with name 043 * "dispatcher_events_total_test" 044 */ 045 @Nullable private String name; 046 047 /** 048 * Labels to be extracted from the metric name. They should contain placeholders to be replaced 049 * with actual values from the incoming metric name. Placeholders are in the ${n} format where n 050 * is the zero based index of the group to extract from the original metric name. E.g.: match: 051 * test.dispatcher.*.* name: dispatcher_events_total_${0} labels: label1: ${1}_t 052 * 053 * <p>A metric "test.dispatcher.sp1.yay" will be converted in a new metric with name 054 * "dispatcher_events_total_sp1" with label {label1: yay_t} 055 * 056 * <p>Label names have to match the regex ^[a-zA-Z_][a-zA-Z0-9_]+$ 057 */ 058 private Map<String, String> labels = new HashMap<>(); 059 060 public MapperConfig() { 061 // empty constructor 062 } 063 064 // for tests 065 MapperConfig(String match) { 066 validateMatch(match); 067 this.match = match; 068 } 069 070 public MapperConfig(String match, String name, Map<String, String> labels) { 071 this.name = name; 072 validateMatch(match); 073 this.match = match; 074 validateLabels(labels); 075 this.labels = labels; 076 } 077 078 @Override 079 public String toString() { 080 return String.format("MapperConfig{match=%s, name=%s, labels=%s}", match, name, labels); 081 } 082 083 @Nullable 084 public String getMatch() { 085 return match; 086 } 087 088 public void setMatch(String match) { 089 validateMatch(match); 090 this.match = match; 091 } 092 093 @Nullable 094 public String getName() { 095 return name; 096 } 097 098 public void setName(String name) { 099 this.name = name; 100 } 101 102 public Map<String, String> getLabels() { 103 return labels; 104 } 105 106 public void setLabels(Map<String, String> labels) { 107 validateLabels(labels); 108 this.labels = labels; 109 } 110 111 private void validateMatch(String match) { 112 if (!MATCH_EXPRESSION_PATTERN.matcher(match).matches()) { 113 throw new IllegalArgumentException( 114 String.format( 115 "Match expression [%s] does not match required pattern %s", 116 match, MATCH_EXPRESSION_PATTERN)); 117 } 118 } 119 120 private void validateLabels(Map<String, String> labels) { 121 if (labels != null) { 122 for (String key : labels.keySet()) { 123 if (!LABEL_PATTERN.matcher(key).matches()) { 124 throw new IllegalArgumentException( 125 String.format("Label [%s] does not match required pattern %s", match, LABEL_PATTERN)); 126 } 127 } 128 } 129 } 130 131 @Override 132 public boolean equals(Object o) { 133 if (this == o) { 134 return true; 135 } 136 if (o == null || getClass() != o.getClass()) { 137 return false; 138 } 139 140 final MapperConfig that = (MapperConfig) o; 141 142 if (match != null ? !match.equals(that.match) : that.match != null) { 143 return false; 144 } 145 if (name != null ? !name.equals(that.name) : that.name != null) { 146 return false; 147 } 148 return labels != null ? labels.equals(that.labels) : that.labels == null; 149 } 150 151 @Override 152 public int hashCode() { 153 int result = match != null ? match.hashCode() : 0; 154 result = 31 * result + (name != null ? name.hashCode() : 0); 155 result = 31 * result + (labels != null ? labels.hashCode() : 0); 156 return result; 157 } 158}