Skip to content

Commit

Permalink
Added transformation closure to MBeanHelper (#960)
Browse files Browse the repository at this point in the history
  • Loading branch information
akats7 authored Aug 4, 2023
1 parent d50127b commit aa0ca10
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 8 deletions.
20 changes: 20 additions & 0 deletions jmx-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,26 @@ In cases where you'd like to share instrument names while creating datapoints fo
- `otel.instrument(MBeanHelper mBeanHelper, String name, String description, Map<String, Map<String, Closure>> attributeLabelFuncs, Closure instrument)` - `unit` is "1" and `labelFuncs` are empty map.
- `otel.instrument(MBeanHelper mBeanHelper, String name, Map<String, Map<String, Closure>> attributeLabelFuncs, Closure instrument)` - `description` is empty string, `unit` is "1" and `labelFuncs` are empty map

### MBeans with non-numeric attributes

In cases where you'd like to create metrics based on non-numeric MBean attributes, the mbean helper methods provide the ability to pass a map of closures, to transform the original extracted attribute into one that can be consumed by the instrument callbacks.

- `otel.mbean(String objectNameStr, Map<String,Closure<?>> attributeTransformation)`

- `otel.mbeans(String objectNameStr, Map<String,Closure<?>> attributeTransformation)`

- `otel.mbeans(List<String> objectNameStrs, Map<String,Closure<?>> attributeTransformation)`

These methods provide the ability to easily convert the attributes you will be extracting from the mbeans, at the time of creation for the MBeanHelper.

```groovy
// In this example a String based health attribute is converted to a numeric binary value
def someBean = otel.mbean(
"SomeMBean", ["CustomAttrFromString": { mbean -> mbean.getProperty("Attribute") == "running" ? 1 : 0 }]
)
otel.instrument(someBean, "my-metric", "CustomAttrFromString", otel.&longUpDownCounterCallback)
```

## OpenTelemetry Synchronous Instrument Helpers

- `otel.doubleCounter(String name, String description, String unit)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,39 @@ class MBeanHelper {
private final JmxClient jmxClient
private final boolean isSingle
private final List<String> objectNames
private final Map<String, Closure> attributeTransformation

private List<GroovyMBean> mbeans

MBeanHelper(JmxClient jmxClient, String objectName, boolean isSingle) {
this.jmxClient = jmxClient
this.objectNames = Collections.unmodifiableList([objectName])
this.isSingle = isSingle
this.attributeTransformation = [:] as Map<String,Closure<?>>
}

MBeanHelper(JmxClient jmxClient, List<String> objectNames) {
this.jmxClient = jmxClient
this.objectNames = Collections.unmodifiableList(objectNames)
this.isSingle = false
this.attributeTransformation = [:] as Map<String,Closure<?>>
}

@PackageScope static List<GroovyMBean> queryJmx(JmxClient jmxClient, String objNameStr) {
MBeanHelper(JmxClient jmxClient, String objectName, boolean isSingle, Map<String,Closure<?>> attributeTransformation ) {
this.jmxClient = jmxClient
this.objectNames = Collections.unmodifiableList([objectName])
this.isSingle = isSingle
this.attributeTransformation = attributeTransformation
}

MBeanHelper(JmxClient jmxClient, List<String> objectNames, Map<String,Closure<?>> attributeTransformation) {
this.jmxClient = jmxClient
this.objectNames = Collections.unmodifiableList(objectNames)
this.isSingle = false
this.attributeTransformation = attributeTransformation
}

@PackageScope static List<GroovyMBean> queryJmx(JmxClient jmxClient, String objNameStr) {
return queryJmx(jmxClient, new ObjectName(objNameStr))
}

Expand Down Expand Up @@ -88,7 +105,7 @@ class MBeanHelper {

def ofInterest = isSingle ? [mbeans[0]]: mbeans
return ofInterest.collect {
getBeanAttribute(it, attribute)
getBeanAttributeWithTransform(it, attribute)
}
}

Expand All @@ -100,13 +117,18 @@ class MBeanHelper {
def ofInterest = isSingle ? [mbeans[0]]: mbeans
return [ofInterest, attributes].combinations().collect { pair ->
def (bean, attribute) = pair
new Tuple3(bean, attribute, getBeanAttribute(bean, attribute))
new Tuple3(bean, attribute, getBeanAttributeWithTransform(bean, attribute))
}
}

Object getBeanAttributeWithTransform(GroovyMBean bean, String attribute){
def transformationClosure = attributeTransformation.get(attribute);
return transformationClosure != null ? transformationClosure(bean) : getBeanAttribute(bean, attribute)
}

static Object getBeanAttribute(GroovyMBean bean, String attribute) {
try {
bean.getProperty(attribute)
bean.getProperty(attribute)
} catch (AttributeNotFoundException e) {
logger.warning("Expected attribute ${attribute} not found in mbean ${bean.name()}")
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ class OtelHelper {
return mbeanHelper
}

MBeanHelper mbean(String objNameStr, Map<String,Closure<?>> attributeTransformation) {
def mbeanHelper = new MBeanHelper(jmxClient, objNameStr, true, attributeTransformation)
mbeanHelper.fetch()
return mbeanHelper
}

MBeanHelper mbeans(List<String> objNameStrs, Map<String,Closure<?>> attributeTransformation) {
def mbeanHelper = new MBeanHelper(jmxClient, objNameStrs, attributeTransformation)
mbeanHelper.fetch()
return mbeanHelper
}
/**
* Returns an updated @{link InstrumentHelper} associated with the provided {@link MBeanHelper} and its specified
* attribute value(s). The parameters map to the InstrumentHelper constructor.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;

import groovy.lang.Closure;
import groovy.util.Eval;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
Expand Down Expand Up @@ -81,7 +85,6 @@ void multiObj() throws Exception {
MBeanHelper mBeanHelper =
new MBeanHelper(jmxClient, Arrays.asList(thingName + ",thing=0", thingName + ",thing=1"));
mBeanHelper.fetch();

assertThat(mBeanHelper.getAttribute("SomeAttribute"))
.hasSameElementsAs(
IntStream.range(0, 2).mapToObj(Integer::toString).collect(Collectors.toList()));
Expand Down Expand Up @@ -116,6 +119,77 @@ void multiple() throws Exception {
IntStream.range(0, 100).mapToObj(unused -> null).collect(Collectors.toList()));
}

@Test
void transform() throws Exception {
String thingName = "io.opentelemetry.contrib.jmxmetrics:type=transform";
Thing thing = new Thing("someValue");
mbeanServer.registerMBean(thing, new ObjectName(thingName));
Map<String, Closure<?>> map =
Stream.of(
new Object[][] {
{
"SomeAttribute",
Eval.me(
"{mbean -> mbean.getProperty(\"SomeAttribute\") == 'someValue' ? 'otherValue' : 'someValue'}")
},
})
.collect(Collectors.toMap(data -> (String) data[0], data -> (Closure<?>) data[1]));
MBeanHelper mBeanHelper = new MBeanHelper(jmxClient, thingName + ",*", true, map);
mBeanHelper.fetch();

assertThat(mBeanHelper.getAttribute("SomeAttribute"))
.hasSameElementsAs(Stream.of(new String[] {"otherValue"}).collect(Collectors.toList()));
}

@Test
void transformMultipleAttributes() throws Exception {
String thingName = "io.opentelemetry.contrib.jmxmetrics:type=transformMultiple";
Thing thing1 = new Thing("someValue", "anotherValue");
ObjectName mbeanName = new ObjectName(thingName);
mbeanServer.registerMBean(thing1, mbeanName);
Map<String, Closure<?>> map =
Stream.of(
new Object[][] {
{
"SomeAttribute",
Eval.me(
"{mbean -> mbean.getProperty(\"SomeAttribute\") == 'someValue' ? 'newValue' : 'someValue'}")
},
{
"AnotherAttribute",
Eval.me(
"{mbean -> mbean.getProperty(\"AnotherAttribute\") == 'anotherValue' ? 'anotherNewValue' : 'anotherValue'}")
},
})
.collect(Collectors.toMap(data -> (String) data[0], data -> (Closure<?>) data[1]));
MBeanHelper mBeanHelper = new MBeanHelper(jmxClient, thingName + ",*", true, map);
mBeanHelper.fetch();

assertThat(mBeanHelper.getAttribute("SomeAttribute"))
.hasSameElementsAs(Stream.of(new String[] {"newValue"}).collect(Collectors.toList()));
assertThat(mBeanHelper.getAttribute("AnotherAttribute"))
.hasSameElementsAs(
Stream.of(new String[] {"anotherNewValue"}).collect(Collectors.toList()));
}

@Test
void customAttribute() throws Exception {
String thingName = "io.opentelemetry.contrib.jmxmetrics:type=custom";
Thing thing = new Thing("");
mbeanServer.registerMBean(thing, new ObjectName(thingName));
Map<String, Closure<?>> map =
Stream.of(
new Object[][] {
{"CustomAttribute", Eval.me("{mbean -> 'customValue'}")},
})
.collect(Collectors.toMap(data -> (String) data[0], data -> (Closure<?>) data[1]));
MBeanHelper mBeanHelper = new MBeanHelper(jmxClient, thingName, true, map);
mBeanHelper.fetch();

assertThat(mBeanHelper.getAttribute("CustomAttribute"))
.hasSameElementsAs(Stream.of(new String[] {"customValue"}).collect(Collectors.toList()));
}

private static void registerThings(String thingName) throws Exception {
for (int i = 0; i < 100; i++) {
Thing thing = new Thing(Integer.toString(i));
Expand All @@ -127,19 +201,34 @@ private static void registerThings(String thingName) throws Exception {
public interface ThingMBean {

String getSomeAttribute();

String getAnotherAttribute();
}

static class Thing implements ThingMBean {

private final String attrValue;
private final String attrValue1;

private final String attrValue2;

Thing(String attrValue) {
this.attrValue = attrValue;
this.attrValue1 = attrValue;
this.attrValue2 = "";
}

Thing(String attrValue1, String attrValue2) {
this.attrValue1 = attrValue1;
this.attrValue2 = attrValue2;
}

@Override
public String getSomeAttribute() {
return attrValue;
return attrValue1;
}

@Override
public String getAnotherAttribute() {
return attrValue2;
}
}
}

0 comments on commit aa0ca10

Please sign in to comment.