Skip to content

Commit 5d68a0f

Browse files
committed
Ability to regularly refresh JMX metrics
1 parent 326a3d3 commit 5d68a0f

File tree

6 files changed

+297
-196
lines changed

6 files changed

+297
-196
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.agent.internal.init;
5+
6+
import static com.microsoft.applicationinsights.agent.internal.diagnostics.MsgId.CUSTOM_JMX_METRIC_ERROR;
7+
8+
import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings;
9+
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
10+
import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxAttributeData;
11+
import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxDataFetcher;
12+
import io.opentelemetry.api.GlobalOpenTelemetry;
13+
import io.opentelemetry.api.common.AttributeKey;
14+
import io.opentelemetry.api.common.Attributes;
15+
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
16+
import java.util.ArrayList;
17+
import java.util.Collection;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
23+
import org.slf4j.MDC;
24+
25+
public class JmxPerformanceCounterLoader {
26+
27+
private static final Logger logger = LoggerFactory.getLogger(JmxPerformanceCounterLoader.class);
28+
29+
private static final String METRIC_NAME_REGEXP = "[a-zA-Z0-9_.-/]+";
30+
private static final String INVALID_CHARACTER_REGEXP = "[^a-zA-Z0-9_.-/]";
31+
32+
private JmxPerformanceCounterLoader() {}
33+
34+
/**
35+
* The method will load the Jmx performance counters requested by the user to the system: 1. Build
36+
* a map where the key is the Jmx object name and the value is a list of requested attributes. 2.
37+
* Go through all the requested Jmx counters: a. If the object name is not in the map, add it with
38+
* an empty list Else get the list b. Add the attribute to the list. 3. Go through the map For
39+
* every entry (object name and attributes) to build a meter per attribute & for each meter
40+
* register a callback to report the metric value.
41+
*/
42+
public static void loadCustomJmxPerfCounters(List<Configuration.JmxMetric> jmxXmlElements) {
43+
HashMap<String, Collection<JmxAttributeData>> data = new HashMap<>();
44+
45+
// Build a map of object name to its requested attributes
46+
for (Configuration.JmxMetric jmxElement : jmxXmlElements) {
47+
Collection<JmxAttributeData> collection =
48+
data.computeIfAbsent(jmxElement.objectName, k -> new ArrayList<>());
49+
50+
if (Strings.isNullOrEmpty(jmxElement.objectName)) {
51+
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
52+
logger.error("JMX object name is empty, will be ignored");
53+
}
54+
continue;
55+
}
56+
57+
if (Strings.isNullOrEmpty(jmxElement.attribute)) {
58+
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
59+
logger.error("JMX attribute is empty for '{}'", jmxElement.objectName);
60+
}
61+
continue;
62+
}
63+
64+
if (Strings.isNullOrEmpty(jmxElement.name)) {
65+
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
66+
logger.error("JMX name is empty for '{}', will be ignored", jmxElement.objectName);
67+
}
68+
continue;
69+
}
70+
71+
collection.add(new JmxAttributeData(jmxElement.name, jmxElement.attribute));
72+
}
73+
74+
createMeterPerAttribute(data);
75+
}
76+
77+
// Create a meter for each attribute & declare the callback that reports the metric in the meter.
78+
public static void createMeterPerAttribute(
79+
Map<String, Collection<JmxAttributeData>> objectAndAttributesMap) {
80+
for (Map.Entry<String, Collection<JmxAttributeData>> entry :
81+
objectAndAttributesMap.entrySet()) {
82+
String objectName = entry.getKey();
83+
84+
for (JmxAttributeData jmxAttributeData : entry.getValue()) {
85+
86+
String otelMetricName;
87+
if (jmxAttributeData.metricName.matches(METRIC_NAME_REGEXP)) {
88+
otelMetricName = jmxAttributeData.metricName;
89+
} else {
90+
otelMetricName = jmxAttributeData.metricName.replaceAll(INVALID_CHARACTER_REGEXP, "_");
91+
}
92+
93+
GlobalOpenTelemetry.getMeter("com.microsoft.applicationinsights.jmx")
94+
.gaugeBuilder(otelMetricName)
95+
.buildWithCallback(
96+
observableDoubleMeasurement -> {
97+
calculateAndRecordValueForAttribute(
98+
observableDoubleMeasurement, objectName, jmxAttributeData);
99+
});
100+
}
101+
}
102+
}
103+
104+
private static void calculateAndRecordValueForAttribute(
105+
ObservableDoubleMeasurement observableDoubleMeasurement,
106+
String objectName,
107+
JmxAttributeData jmxAttributeData) {
108+
try {
109+
List<Object> result =
110+
JmxDataFetcher.fetch(
111+
objectName, jmxAttributeData.attribute); // should return the [val, ...] here
112+
113+
logger.trace(
114+
"Size of the JmxDataFetcher.fetch result: {}, for objectName:{} and metricName:{}",
115+
result.size(),
116+
objectName,
117+
jmxAttributeData.metricName);
118+
119+
boolean ok = true;
120+
double value = 0.0;
121+
for (Object obj : result) {
122+
try {
123+
if (obj instanceof Boolean) {
124+
value = ((Boolean) obj).booleanValue() ? 1 : 0;
125+
} else {
126+
value += Double.parseDouble(String.valueOf(obj));
127+
}
128+
} catch (RuntimeException e) {
129+
ok = false;
130+
break;
131+
}
132+
}
133+
if (ok) {
134+
logger.trace(
135+
"value {} for objectName:{} and metricName{}",
136+
value,
137+
objectName,
138+
jmxAttributeData.metricName);
139+
observableDoubleMeasurement.record(
140+
value,
141+
Attributes.of(
142+
AttributeKey.stringKey("applicationinsights.internal.metric_name"),
143+
jmxAttributeData.metricName));
144+
}
145+
} catch (Exception e) {
146+
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
147+
logger.error(
148+
"Failed to calculate the metric value for objectName {} and metric name {}",
149+
objectName,
150+
jmxAttributeData.metricName);
151+
logger.error("Exception: {}", e.toString());
152+
}
153+
}
154+
}
155+
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/PerformanceCounterInitializer.java

Lines changed: 2 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,36 @@
33

44
package com.microsoft.applicationinsights.agent.internal.init;
55

6-
import static com.microsoft.applicationinsights.agent.internal.diagnostics.MsgId.CUSTOM_JMX_METRIC_ERROR;
7-
86
import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.PropertyHelper;
9-
import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.Strings;
107
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
118
import com.microsoft.applicationinsights.agent.internal.perfcounter.DeadLockDetectorPerformanceCounter;
129
import com.microsoft.applicationinsights.agent.internal.perfcounter.FreeMemoryPerformanceCounter;
1310
import com.microsoft.applicationinsights.agent.internal.perfcounter.GcPerformanceCounter;
1411
import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxAttributeData;
15-
import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxDataFetcher;
1612
import com.microsoft.applicationinsights.agent.internal.perfcounter.JmxMetricPerformanceCounter;
1713
import com.microsoft.applicationinsights.agent.internal.perfcounter.JvmHeapMemoryUsedPerformanceCounter;
1814
import com.microsoft.applicationinsights.agent.internal.perfcounter.OshiPerformanceCounter;
1915
import com.microsoft.applicationinsights.agent.internal.perfcounter.PerformanceCounterContainer;
2016
import com.microsoft.applicationinsights.agent.internal.perfcounter.ProcessCpuPerformanceCounter;
2117
import com.microsoft.applicationinsights.agent.internal.perfcounter.ProcessMemoryPerformanceCounter;
22-
import io.opentelemetry.api.GlobalOpenTelemetry;
23-
import io.opentelemetry.api.common.AttributeKey;
24-
import io.opentelemetry.api.common.Attributes;
25-
import io.opentelemetry.api.metrics.ObservableDoubleMeasurement;
2618
import java.lang.management.ManagementFactory;
2719
import java.lang.management.ThreadMXBean;
28-
import java.util.ArrayList;
2920
import java.util.Arrays;
30-
import java.util.Collection;
31-
import java.util.HashMap;
3221
import java.util.List;
33-
import java.util.Map;
3422
import org.slf4j.Logger;
3523
import org.slf4j.LoggerFactory;
36-
import org.slf4j.MDC;
3724

3825
public class PerformanceCounterInitializer {
3926

4027
private static final Logger logger = LoggerFactory.getLogger(PerformanceCounterInitializer.class);
41-
private static final String METRIC_NAME_REGEXP = "[a-zA-Z0-9_.-/]+";
42-
private static final String INVALID_CHARACTER_REGEXP = "[^a-zA-Z0-9_.-/]";
4328

4429
public static void initialize(Configuration configuration) {
4530

4631
PerformanceCounterContainer.INSTANCE.setCollectionFrequencyInSec(
4732
configuration.metricIntervalSeconds);
4833

4934
if (logger.isDebugEnabled()) {
50-
PerformanceCounterContainer.INSTANCE.setLogAvailableJmxMetrics();
35+
PerformanceCounterContainer.INSTANCE.setLogAvailableJmxMetrics(configuration.jmxMetrics);
5136
}
5237

5338
// We don't want these two to be flowing to the OTLP endpoint
@@ -65,7 +50,7 @@ public static void initialize(Configuration configuration) {
6550
"LoadedClassCount",
6651
configuration.jmxMetrics);
6752

68-
loadCustomJmxPerfCounters(configuration.jmxMetrics);
53+
JmxPerformanceCounterLoader.loadCustomJmxPerfCounters(configuration.jmxMetrics);
6954

7055
PerformanceCounterContainer.INSTANCE.register(
7156
new ProcessCpuPerformanceCounter(
@@ -114,127 +99,5 @@ private static boolean isMetricInConfig(
11499
return false;
115100
}
116101

117-
/**
118-
* The method will load the Jmx performance counters requested by the user to the system: 1. Build
119-
* a map where the key is the Jmx object name and the value is a list of requested attributes. 2.
120-
* Go through all the requested Jmx counters: a. If the object name is not in the map, add it with
121-
* an empty list Else get the list b. Add the attribute to the list. 3. Go through the map For
122-
* every entry (object name and attributes) to build a meter per attribute & for each meter
123-
* register a callback to report the metric value.
124-
*/
125-
private static void loadCustomJmxPerfCounters(List<Configuration.JmxMetric> jmxXmlElements) {
126-
HashMap<String, Collection<JmxAttributeData>> data = new HashMap<>();
127-
128-
// Build a map of object name to its requested attributes
129-
for (Configuration.JmxMetric jmxElement : jmxXmlElements) {
130-
Collection<JmxAttributeData> collection =
131-
data.computeIfAbsent(jmxElement.objectName, k -> new ArrayList<>());
132-
133-
if (Strings.isNullOrEmpty(jmxElement.objectName)) {
134-
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
135-
logger.error("JMX object name is empty, will be ignored");
136-
}
137-
continue;
138-
}
139-
140-
if (Strings.isNullOrEmpty(jmxElement.attribute)) {
141-
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
142-
logger.error("JMX attribute is empty for '{}'", jmxElement.objectName);
143-
}
144-
continue;
145-
}
146-
147-
if (Strings.isNullOrEmpty(jmxElement.name)) {
148-
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
149-
logger.error("JMX name is empty for '{}', will be ignored", jmxElement.objectName);
150-
}
151-
continue;
152-
}
153-
154-
collection.add(new JmxAttributeData(jmxElement.name, jmxElement.attribute));
155-
}
156-
157-
createMeterPerAttribute(data);
158-
}
159-
160-
// Create a meter for each attribute & declare the callback that reports the metric in the meter.
161-
private static void createMeterPerAttribute(
162-
Map<String, Collection<JmxAttributeData>> objectAndAttributesMap) {
163-
for (Map.Entry<String, Collection<JmxAttributeData>> entry :
164-
objectAndAttributesMap.entrySet()) {
165-
String objectName = entry.getKey();
166-
167-
for (JmxAttributeData jmxAttributeData : entry.getValue()) {
168-
169-
String otelMetricName;
170-
if (jmxAttributeData.metricName.matches(METRIC_NAME_REGEXP)) {
171-
otelMetricName = jmxAttributeData.metricName;
172-
} else {
173-
otelMetricName = jmxAttributeData.metricName.replaceAll(INVALID_CHARACTER_REGEXP, "_");
174-
}
175-
176-
GlobalOpenTelemetry.getMeter("com.microsoft.applicationinsights.jmx")
177-
.gaugeBuilder(otelMetricName)
178-
.buildWithCallback(
179-
observableDoubleMeasurement -> {
180-
calculateAndRecordValueForAttribute(
181-
observableDoubleMeasurement, objectName, jmxAttributeData);
182-
});
183-
}
184-
}
185-
}
186-
187-
private static void calculateAndRecordValueForAttribute(
188-
ObservableDoubleMeasurement observableDoubleMeasurement,
189-
String objectName,
190-
JmxAttributeData jmxAttributeData) {
191-
try {
192-
List<Object> result =
193-
JmxDataFetcher.fetch(
194-
objectName, jmxAttributeData.attribute); // should return the [val, ...] here
195-
196-
logger.trace(
197-
"Size of the JmxDataFetcher.fetch result: {}, for objectName:{} and metricName:{}",
198-
result.size(),
199-
objectName,
200-
jmxAttributeData.metricName);
201-
202-
boolean ok = true;
203-
double value = 0.0;
204-
for (Object obj : result) {
205-
try {
206-
if (obj instanceof Boolean) {
207-
value = ((Boolean) obj).booleanValue() ? 1 : 0;
208-
} else {
209-
value += Double.parseDouble(String.valueOf(obj));
210-
}
211-
} catch (RuntimeException e) {
212-
ok = false;
213-
break;
214-
}
215-
}
216-
if (ok) {
217-
logger.trace(
218-
"value {} for objectName:{} and metricName{}",
219-
value,
220-
objectName,
221-
jmxAttributeData.metricName);
222-
observableDoubleMeasurement.record(
223-
value,
224-
Attributes.of(
225-
AttributeKey.stringKey("applicationinsights.internal.metric_name"),
226-
jmxAttributeData.metricName));
227-
}
228-
} catch (Exception e) {
229-
try (MDC.MDCCloseable ignored = CUSTOM_JMX_METRIC_ERROR.makeActive()) {
230-
logger.error(
231-
"Failed to calculate the metric value for objectName {} and metric name {}",
232-
objectName,
233-
jmxAttributeData.metricName);
234-
logger.error("Exception: {}", e.toString());
235-
}
236-
}
237-
}
238-
239102
private PerformanceCounterInitializer() {}
240103
}

0 commit comments

Comments
 (0)