Skip to content

Commit 5973b01

Browse files
committed
impl + test
1 parent 3f2f151 commit 5973b01

File tree

6 files changed

+250
-59
lines changed

6 files changed

+250
-59
lines changed

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/BeanAttributeExtractor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ private Object extractAttributeValue(MBeanServerConnection connection, ObjectNam
253253
}
254254

255255
@Nullable
256-
Number extractNumericalAttribute(MBeanServerConnection connection, ObjectName objectName) {
256+
protected Number extractNumericalAttribute(
257+
MBeanServerConnection connection, ObjectName objectName) {
257258
Object value = extractAttributeValue(connection, objectName);
258259
if (value instanceof Number) {
259260
return (Number) value;

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/MetricAttribute.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public MetricAttribute(String name, MetricAttributeExtractor extractor) {
2222
this.extractor = extractor;
2323
}
2424

25+
public boolean isStateAttribute() {
26+
return extractor == null;
27+
}
28+
2529
public String getAttributeName() {
2630
return name;
2731
}

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/JmxRule.java

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.opentelemetry.instrumentation.jmx.engine.BeanAttributeExtractor;
1010
import io.opentelemetry.instrumentation.jmx.engine.BeanGroup;
1111
import io.opentelemetry.instrumentation.jmx.engine.MetricAttribute;
12+
import io.opentelemetry.instrumentation.jmx.engine.MetricAttributeExtractor;
1213
import io.opentelemetry.instrumentation.jmx.engine.MetricDef;
1314
import io.opentelemetry.instrumentation.jmx.engine.MetricExtractor;
1415
import io.opentelemetry.instrumentation.jmx.engine.MetricInfo;
@@ -17,7 +18,9 @@
1718
import java.util.List;
1819
import java.util.Map;
1920
import java.util.Set;
21+
import java.util.stream.Collectors;
2022
import javax.annotation.Nullable;
23+
import javax.management.MBeanServerConnection;
2124
import javax.management.MalformedObjectNameException;
2225
import javax.management.ObjectName;
2326

@@ -143,8 +146,7 @@ public MetricDef buildMetricDef() throws Exception {
143146
}
144147

145148
Set<String> attrNames = mapping.keySet();
146-
MetricExtractor[] metricExtractors = new MetricExtractor[attrNames.size()];
147-
int n = 0;
149+
List<MetricExtractor> metricExtractors = new ArrayList<>();
148150
for (String attributeName : attrNames) {
149151
BeanAttributeExtractor attrExtractor = BeanAttributeExtractor.fromName(attributeName);
150152
// This is essentially the same as 'attributeName' but with escape characters removed
@@ -162,44 +164,85 @@ public MetricDef buildMetricDef() throws Exception {
162164
metricInfo = m.buildMetricInfo(prefix, niceAttributeName, getUnit(), getMetricType());
163165
}
164166

165-
List<MetricAttribute> attributeList;
166167
List<MetricAttribute> ownAttributes = getAttributeList();
167-
if (ownAttributes != null && m != null && m.getAttributeList() != null) {
168-
// MetricAttributes have been specified at two levels, need to combine them
169-
attributeList = combineMetricAttributes(ownAttributes, m.getAttributeList());
170-
} else if (ownAttributes != null) {
171-
attributeList = ownAttributes;
172-
} else if (m != null && m.getAttributeList() != null) {
173-
// Get the attributes from the metric
174-
attributeList = m.getAttributeList();
168+
List<MetricAttribute> metricAttributes = m != null ? m.getAttributeList() : null;
169+
List<MetricAttribute> attributeList =
170+
combineMetricAttributes(ownAttributes, metricAttributes);
171+
172+
// higher priority to metric level mapping, then jmx rule as fallback
173+
final StateMapping stateMapping = getEffectiveStateMapping(m, this);
174+
175+
if (stateMapping.isEmpty()) {
176+
MetricExtractor metricExtractor =
177+
new MetricExtractor(
178+
attrExtractor, metricInfo, attributeList.toArray(new MetricAttribute[0]));
179+
metricExtractors.add(metricExtractor);
175180
} else {
176-
// There are no attributes at all
177-
attributeList = new ArrayList<MetricAttribute>();
178-
}
179181

180-
MetricExtractor metricExtractor =
181-
new MetricExtractor(
182-
attrExtractor,
183-
metricInfo,
184-
attributeList.toArray(new MetricAttribute[attributeList.size()]));
185-
metricExtractors[n++] = metricExtractor;
182+
// generate one metric extractor per state metric key
183+
// each metric extractor will have the state attribute replaced with a constant
184+
for (String key : stateMapping.getStateKeys()) {
185+
List<MetricAttribute> stateMetricAttributes =
186+
attributeList.stream()
187+
.map(
188+
ma -> {
189+
if (!ma.isStateAttribute()) {
190+
return ma;
191+
} else {
192+
return new MetricAttribute(
193+
ma.getAttributeName(), MetricAttributeExtractor.fromConstant(key));
194+
}
195+
})
196+
.collect(Collectors.toList());
197+
198+
BeanAttributeExtractor stateMetricExtractor =
199+
new BeanAttributeExtractor(attrExtractor.getAttributeName()) {
200+
201+
@Override
202+
protected Number extractNumericalAttribute(
203+
MBeanServerConnection connection, ObjectName objectName) {
204+
String rawStateValue = attrExtractor.extractValue(connection, objectName);
205+
String mappedStateValue = stateMapping.getStateValue(rawStateValue);
206+
return key.equals(mappedStateValue) ? 1 : 0;
207+
}
208+
};
209+
210+
metricExtractors.add(
211+
new MetricExtractor(
212+
stateMetricExtractor,
213+
metricInfo,
214+
stateMetricAttributes.toArray(new MetricAttribute[0])));
215+
}
216+
}
186217
}
187218

188-
return new MetricDef(group, metricExtractors);
219+
return new MetricDef(group, metricExtractors.toArray(new MetricExtractor[0]));
189220
}
190221

191222
private static List<MetricAttribute> combineMetricAttributes(
192223
List<MetricAttribute> ownAttributes, List<MetricAttribute> metricAttributes) {
224+
193225
Map<String, MetricAttribute> set = new HashMap<>();
194-
for (MetricAttribute ownAttribute : ownAttributes) {
195-
set.put(ownAttribute.getAttributeName(), ownAttribute);
226+
if (ownAttributes != null) {
227+
for (MetricAttribute ownAttribute : ownAttributes) {
228+
set.put(ownAttribute.getAttributeName(), ownAttribute);
229+
}
196230
}
197-
198-
// Let the metric level defined attributes override own attributes
199-
for (MetricAttribute metricAttribute : metricAttributes) {
200-
set.put(metricAttribute.getAttributeName(), metricAttribute);
231+
if (metricAttributes != null) {
232+
// Let the metric level defined attributes override own attributes
233+
for (MetricAttribute metricAttribute : metricAttributes) {
234+
set.put(metricAttribute.getAttributeName(), metricAttribute);
235+
}
201236
}
202237

203-
return new ArrayList<MetricAttribute>(set.values());
238+
return new ArrayList<>(set.values());
239+
}
240+
241+
private static StateMapping getEffectiveStateMapping(Metric m, JmxRule rule) {
242+
if (m == null || m.getStateMapping().isEmpty()) {
243+
return rule.getStateMapping();
244+
} else {
245+
return m.getStateMapping();
246+
}
204247
}
205248
}

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/MetricStructure.java

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
* <li>the metric attributes
2121
* <li>the unit
2222
*
23-
* <p>Known subclasses are JmxRule and Metric.
23+
* <p>Known subclasses are {@link JmxRule} and {@link Metric}.
2424
*/
2525
abstract class MetricStructure {
2626

@@ -30,14 +30,22 @@ abstract class MetricStructure {
3030
// KEY1: SPECIFICATION1
3131
// KEY2: SPECIFICATION2
3232
// unit: UNIT
33-
34-
private Map<String, String> metricAttribute; // unused, for YAML parser only
33+
// stateMapping:
34+
// state1: [a,b]
35+
// state2: c
36+
// state3: '*'
37+
38+
private Map<String, String> metricAttribute;
39+
private StateMapping stateMapping = StateMapping.empty();
40+
private static final String STATE_MAPPING_WILDCARD = "*";
3541
private String unit;
3642

3743
private MetricInfo.Type metricType;
3844
private List<MetricAttribute> metricAttributes;
3945

40-
public void setType(String t) {
46+
MetricStructure() {}
47+
48+
void setType(String t) {
4149
// Do not complain about case variations
4250
t = t.trim().toUpperCase(Locale.ROOT);
4351
this.metricType = MetricInfo.Type.valueOf(t);
@@ -51,6 +59,36 @@ public void setUnit(String unit) {
5159
this.unit = validateUnit(unit.trim());
5260
}
5361

62+
void setStateMapping(Map<String, Object> stateMapping) {
63+
StateMapping.Builder builder = StateMapping.builder();
64+
for (Map.Entry<String, Object> entry : stateMapping.entrySet()) {
65+
String stateKey = entry.getKey();
66+
Object stateValue = entry.getValue();
67+
if (stateValue instanceof String) {
68+
addMappedValue(builder, (String) stateValue, stateKey);
69+
} else if (stateValue instanceof List) {
70+
for (Object listEntry : (List<?>) stateValue) {
71+
if (!(listEntry instanceof String)) {
72+
throw new IllegalArgumentException("unexpected state list value: " + stateKey);
73+
}
74+
addMappedValue(builder, (String) listEntry, stateKey);
75+
}
76+
} else {
77+
throw new IllegalArgumentException("unexpected state value: " + stateValue);
78+
}
79+
}
80+
this.stateMapping = builder.build();
81+
}
82+
83+
private static void addMappedValue(
84+
StateMapping.Builder builder, String stateValue, String stateKey) {
85+
if (stateValue.equals(STATE_MAPPING_WILDCARD)) {
86+
builder.withDefaultState(stateKey);
87+
} else {
88+
builder.withMappedValue(stateValue, stateKey);
89+
}
90+
}
91+
5492
@CanIgnoreReturnValue
5593
private String validateUnit(String unit) {
5694
requireNonEmpty(unit, "Metric unit is empty");
@@ -67,9 +105,7 @@ private String validateUnit(String unit) {
67105
public void setMetricAttribute(Map<String, String> map) {
68106
this.metricAttribute = map;
69107
// pre-build the MetricAttributes
70-
List<MetricAttribute> attrList = new ArrayList<>();
71-
addMetricAttributes(attrList, map);
72-
this.metricAttributes = attrList;
108+
this.metricAttributes = addMetricAttributes(map);
73109
}
74110

75111
// Used only for testing
@@ -91,47 +127,66 @@ protected void requireNonEmpty(String s, String msg) {
91127
}
92128
}
93129

94-
private static void addMetricAttributes(
95-
List<MetricAttribute> list, Map<String, String> metricAttributeMap) {
96-
if (metricAttributeMap != null) {
97-
for (String key : metricAttributeMap.keySet()) {
98-
String target = metricAttributeMap.get(key);
99-
if (target == null) {
100-
throw new IllegalStateException(
101-
"nothing specified for metric attribute key '" + key + "'");
130+
private List<MetricAttribute> addMetricAttributes(Map<String, String> metricAttributeMap) {
131+
132+
List<MetricAttribute> list = new ArrayList<>();
133+
boolean hasStateAttribute = false;
134+
for (String key : metricAttributeMap.keySet()) {
135+
String target = metricAttributeMap.get(key);
136+
if (target == null) {
137+
throw new IllegalStateException("nothing specified for metric attribute key '" + key + "'");
138+
}
139+
MetricAttribute attribute = buildMetricAttribute(key, target.trim());
140+
if (attribute.isStateAttribute()) {
141+
if (hasStateAttribute) {
142+
throw new IllegalArgumentException("only one state attribute is allowed");
102143
}
103-
list.add(buildMetricAttribute(key, target.trim()));
144+
hasStateAttribute = true;
104145
}
146+
list.add(attribute);
147+
}
148+
if (hasStateAttribute && stateMapping.isEmpty()) {
149+
throw new IllegalArgumentException("statekey() usage without stateMapping");
105150
}
151+
return list;
106152
}
107153

108154
private static MetricAttribute buildMetricAttribute(String key, String target) {
109155
// The recognized forms of target are:
110156
// - param(STRING)
111157
// - beanattr(STRING)
112158
// - const(STRING)
159+
// - statekey()
113160
// where STRING is the name of the corresponding parameter key, attribute name,
114161
// or the direct value to use
115162
int k = target.indexOf(')');
116163

117164
// Check for one of the cases as above
118165
if (target.startsWith("param(")) {
119166
if (k > 0) {
167+
String jmxAttribute = target.substring(6, k).trim();
120168
return new MetricAttribute(
121-
key, MetricAttributeExtractor.fromObjectNameParameter(target.substring(6, k).trim()));
169+
key, MetricAttributeExtractor.fromObjectNameParameter(jmxAttribute));
122170
}
123171
} else if (target.startsWith("beanattr(")) {
124172
if (k > 0) {
125-
return new MetricAttribute(
126-
key, MetricAttributeExtractor.fromBeanAttribute(target.substring(9, k).trim()));
173+
String jmxAttribute = target.substring(9, k).trim();
174+
return new MetricAttribute(key, MetricAttributeExtractor.fromBeanAttribute(jmxAttribute));
127175
}
128176
} else if (target.startsWith("const(")) {
129177
if (k > 0) {
130-
return new MetricAttribute(
131-
key, MetricAttributeExtractor.fromConstant(target.substring(6, k).trim()));
178+
String constantValue = target.substring(6, k).trim();
179+
return new MetricAttribute(key, MetricAttributeExtractor.fromConstant(constantValue));
132180
}
181+
} else if (target.equals("statekey()")) {
182+
return new MetricAttribute(key, null);
133183
}
134184

135-
throw new IllegalArgumentException("Invalid metric attribute specification for '" + key + "'");
185+
String msg = "Invalid metric attribute specification for '" + key + "': " + target;
186+
throw new IllegalArgumentException(msg);
187+
}
188+
189+
public StateMapping getStateMapping() {
190+
return stateMapping;
136191
}
137192
}

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,13 @@ private static Map<String, Metric> parseMappings(@Nullable Map<String, Object> m
9797
Map<String, Metric> mappings = new LinkedHashMap<>();
9898
if (mappingYaml != null) {
9999
mappingYaml.forEach(
100-
(name, metricYaml) ->
101-
mappings.put(
102-
name, metricYaml == null ? null : parseMetric((Map<String, Object>) metricYaml)));
100+
(name, metricYaml) -> {
101+
Metric m = null;
102+
if (metricYaml != null) {
103+
m = parseMetric((Map<String, Object>) metricYaml);
104+
}
105+
mappings.put(name, m);
106+
});
103107
}
104108
return mappings;
105109
}
@@ -124,10 +128,18 @@ private static Metric parseMetric(Map<String, Object> metricYaml) {
124128
@SuppressWarnings("unchecked")
125129
private static void parseMetricStructure(
126130
Map<String, Object> metricStructureYaml, MetricStructure out) {
131+
127132
String type = (String) metricStructureYaml.remove("type");
128133
if (type != null) {
129134
out.setType(type);
130135
}
136+
137+
Map<String, Object> stateMapping =
138+
(Map<String, Object>) metricStructureYaml.remove("stateMapping");
139+
if (stateMapping != null) {
140+
out.setStateMapping(stateMapping);
141+
}
142+
131143
Map<String, String> metricAttribute =
132144
(Map<String, String>) metricStructureYaml.remove("metricAttribute");
133145
if (metricAttribute != null) {

0 commit comments

Comments
 (0)