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