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