-
Notifications
You must be signed in to change notification settings - Fork 917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
jmx metrics attribute lowercase modifier #13385
Changes from all commits
588f6d3
ac96916
66d676c
ced88cc
2b297e4
c1e8801
80cfcad
3ad0f04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,36 +136,84 @@ private List<MetricAttribute> addMetricAttributes(Map<String, Object> metricAttr | |
return list; | ||
} | ||
|
||
private static MetricAttribute buildMetricAttribute(String key, String target) { | ||
// package protected for testing | ||
static MetricAttribute buildMetricAttribute(String key, String target) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [for reviewer] making this package-private makes unit-testing easier in |
||
String errorMsg = | ||
String.format("Invalid metric attribute expression for '%s' : '%s'", key, target); | ||
|
||
String targetExpr = target; | ||
|
||
// an optional modifier may wrap the target | ||
// - lowercase(param(STRING)) | ||
boolean lowercase = false; | ||
String lowerCaseExpr = tryParseFunction("lowercase", targetExpr, target); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess for now it doesn't matter, but if you plan to add more transforms then it would be beneficial to restructure the parsing logic so that |
||
if (lowerCaseExpr != null) { | ||
lowercase = true; | ||
targetExpr = lowerCaseExpr; | ||
} | ||
|
||
// | ||
// The recognized forms of target are: | ||
// - param(STRING) | ||
// - beanattr(STRING) | ||
// - const(STRING) | ||
// where STRING is the name of the corresponding parameter key, attribute name, | ||
// or the direct value to use | ||
int k = target.indexOf(')'); | ||
|
||
// Check for one of the cases as above | ||
if (target.startsWith("param(")) { | ||
if (k > 0) { | ||
String jmxAttribute = target.substring(6, k).trim(); | ||
return new MetricAttribute( | ||
key, MetricAttributeExtractor.fromObjectNameParameter(jmxAttribute)); | ||
} | ||
} else if (target.startsWith("beanattr(")) { | ||
if (k > 0) { | ||
String jmxAttribute = target.substring(9, k).trim(); | ||
return new MetricAttribute(key, MetricAttributeExtractor.fromBeanAttribute(jmxAttribute)); | ||
// or the constant value to use | ||
|
||
MetricAttributeExtractor extractor = null; | ||
|
||
String paramName = tryParseFunction("param", targetExpr, target); | ||
if (paramName != null) { | ||
extractor = MetricAttributeExtractor.fromObjectNameParameter(paramName); | ||
} | ||
|
||
if (extractor == null) { | ||
String attributeName = tryParseFunction("beanattr", targetExpr, target); | ||
if (attributeName != null) { | ||
extractor = MetricAttributeExtractor.fromBeanAttribute(attributeName); | ||
} | ||
} else if (target.startsWith("const(")) { | ||
if (k > 0) { | ||
String constantValue = target.substring(6, k).trim(); | ||
return new MetricAttribute(key, MetricAttributeExtractor.fromConstant(constantValue)); | ||
} | ||
|
||
if (extractor == null) { | ||
String constantValue = tryParseFunction("const", targetExpr, target); | ||
if (constantValue != null) { | ||
extractor = MetricAttributeExtractor.fromConstant(constantValue); | ||
} | ||
} | ||
|
||
String msg = "Invalid metric attribute specification for '" + key + "': " + target; | ||
throw new IllegalArgumentException(msg); | ||
if (extractor == null) { | ||
// expression did not match any supported syntax | ||
throw new IllegalArgumentException(errorMsg); | ||
} | ||
|
||
if (lowercase) { | ||
extractor = MetricAttributeExtractor.toLowerCase(extractor); | ||
} | ||
return new MetricAttribute(key, extractor); | ||
} | ||
|
||
/** | ||
* Parses a function expression for metric attributes | ||
* | ||
* @param function function name to attempt parsing from expression | ||
* @param expression expression to parse | ||
* @param errorMsg error message to use when syntax error is present | ||
* @return {@literal null} if expression does not start with function | ||
* @throws IllegalArgumentException if expression syntax is invalid | ||
*/ | ||
private static String tryParseFunction(String function, String expression, String errorMsg) { | ||
if (!expression.startsWith(function)) { | ||
return null; | ||
} | ||
String expr = expression.substring(function.length()).trim(); | ||
if (expr.charAt(0) != '(' || expr.charAt(expr.length() - 1) != ')') { | ||
throw new IllegalArgumentException(errorMsg); | ||
} | ||
expr = expr.substring(1, expr.length() - 1).trim(); | ||
if (expr.isEmpty()) { | ||
throw new IllegalArgumentException(errorMsg); | ||
} | ||
return expr.trim(); | ||
} | ||
|
||
private MetricAttribute buildStateMetricAttribute(String key, Map<?, ?> stateMap) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -379,7 +379,6 @@ void testConf8() throws Exception { | |
@Test | ||
void testStateMetricConf() throws Exception { | ||
JmxConfig config = parseConf(CONF9); | ||
assertThat(config).isNotNull(); | ||
|
||
List<JmxRule> rules = config.getRules(); | ||
assertThat(rules).hasSize(1); | ||
|
@@ -452,6 +451,39 @@ void testStateMetricConf() throws Exception { | |
}); | ||
} | ||
|
||
private static final String CONF10 = | ||
"--- # keep stupid spotlessJava at bay\n" | ||
+ "rules:\n" | ||
+ " - bean: my-test:type=10_Hello\n" | ||
+ " mapping:\n" | ||
+ " jmxAttribute:\n" | ||
+ " type: counter\n" | ||
+ " metric: my_metric\n" | ||
+ " metricAttribute:\n" | ||
+ " to_lower_const: lowercase(const(Hello))\n" | ||
+ " to_lower_attribute: lowercase(beanattr(beanAttribute))\n" | ||
+ " to_lower_param: lowercase(param(type))\n"; | ||
|
||
@Test | ||
void attributeValueLowercase() { | ||
|
||
JmxConfig config = parseConf(CONF10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [for reviewer] here we only test the parsing as we can rely on unit tests in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, however it would be nice to add also some negative test to verify parsing errors related to use of lowercase(...) |
||
|
||
List<JmxRule> rules = config.getRules(); | ||
assertThat(rules).hasSize(1); | ||
JmxRule jmxRule = rules.get(0); | ||
|
||
assertThat(jmxRule.getBean()).isEqualTo("my-test:type=10_Hello"); | ||
Metric metric = jmxRule.getMapping().get("jmxAttribute"); | ||
assertThat(metric.getMetricType()).isEqualTo(MetricInfo.Type.COUNTER); | ||
assertThat(metric.getMetric()).isEqualTo("my_metric"); | ||
assertThat(metric.getMetricAttribute()) | ||
.hasSize(3) | ||
.containsEntry("to_lower_const", "lowercase(const(Hello))") | ||
.containsEntry("to_lower_attribute", "lowercase(beanattr(beanAttribute))") | ||
.containsEntry("to_lower_param", "lowercase(param(type))"); | ||
} | ||
|
||
@Test | ||
void testEmptyConf() { | ||
JmxConfig config = parseConf(EMPTY_CONF); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.jmx.yaml; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import io.opentelemetry.instrumentation.jmx.engine.MetricAttribute; | ||
import javax.management.MBeanAttributeInfo; | ||
import javax.management.MBeanInfo; | ||
import javax.management.MBeanServerConnection; | ||
import javax.management.ObjectName; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.CsvSource; | ||
import org.junit.jupiter.params.provider.ValueSource; | ||
|
||
public class MetricStructureTest { | ||
|
||
@ParameterizedTest | ||
@CsvSource({"const(Hello),Hello", "lowercase(const(Hello)),hello"}) | ||
void metricAttribute_constant(String target, String expectedValue) { | ||
MetricAttribute ma = MetricStructure.buildMetricAttribute("name", target); | ||
assertThat(ma.getAttributeName()).isEqualTo("name"); | ||
assertThat(ma.isStateAttribute()).isFalse(); | ||
assertThat(ma.acquireAttributeValue(null, null)).isEqualTo(expectedValue); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"beanattr(beanAttribute),Hello,Hello", | ||
"lowercase(beanattr(beanAttribute)),Hello,hello", | ||
}) | ||
void metricAttribute_beanAttribute(String target, String value, String expectedValue) | ||
throws Exception { | ||
MetricAttribute ma = MetricStructure.buildMetricAttribute("name", target); | ||
assertThat(ma.getAttributeName()).isEqualTo("name"); | ||
assertThat(ma.isStateAttribute()).isFalse(); | ||
|
||
ObjectName objectName = new ObjectName("test:name=_beanAttribute"); | ||
MBeanServerConnection mockConnection = mock(MBeanServerConnection.class); | ||
|
||
MBeanInfo mockBeanInfo = mock(MBeanInfo.class); | ||
when(mockBeanInfo.getAttributes()) | ||
.thenReturn( | ||
new MBeanAttributeInfo[] { | ||
new MBeanAttributeInfo("beanAttribute", "java.lang.String", "", true, false, false) | ||
}); | ||
when(mockConnection.getMBeanInfo(objectName)).thenReturn(mockBeanInfo); | ||
when(mockConnection.getAttribute(objectName, "beanAttribute")).thenReturn(value); | ||
|
||
assertThat(ma.acquireAttributeValue(mockConnection, objectName)).isEqualTo(expectedValue); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ | ||
"param(name),Hello,Hello", | ||
"lowercase(param(name)),Hello,hello", | ||
}) | ||
void metricAttribute_beanParam(String target, String value, String expectedValue) | ||
throws Exception { | ||
MetricAttribute ma = MetricStructure.buildMetricAttribute("name", target); | ||
assertThat(ma.getAttributeName()).isEqualTo("name"); | ||
assertThat(ma.isStateAttribute()).isFalse(); | ||
|
||
ObjectName objectName = new ObjectName("test:name=" + value); | ||
MBeanServerConnection mockConnection = mock(MBeanServerConnection.class); | ||
|
||
assertThat(ma.acquireAttributeValue(mockConnection, objectName)).isEqualTo(expectedValue); | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource( | ||
strings = { | ||
"missing(name)", // non-existing target | ||
"param()", // missing parameter | ||
"param( )", // missing parameter with empty string | ||
"param(name)a", // something after parenthesis | ||
"lowercase()", // misng target in modifier | ||
"lowercase(param(name)", // missing parenthesis for modifier | ||
"lowercase(missing(name))", // non-existing target within modifier | ||
"lowercase(param())", // missing parameter in modifier | ||
"lowercase(param( ))", // missing parameter in modifier with empty string | ||
"lowercase(param))", // missing parenthesis within modifier | ||
}) | ||
void invalidTargetSyntax(String target) { | ||
assertThatThrownBy(() -> MetricStructure.buildMetricAttribute("metric_attribute", target)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.describedAs( | ||
"exception should be thrown with original expression to help end-user understand the syntax error") | ||
.hasMessageContaining(target); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[for reviewer] extractor return value can be
null