Skip to content

Commit c4e0b40

Browse files
authored
Log all available jmx metrics at debug level (#1826)
* Add option to log available jmx metrics * Spotless * Change from special config to normal debug logging * Reduce log level for expected errors
1 parent 04d666f commit c4e0b40

File tree

4 files changed

+327
-0
lines changed

4 files changed

+327
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public static void initialize(TelemetryClient telemetryClient, Configuration con
6565
PerformanceCounterContainer.INSTANCE.setCollectionFrequencyInSec(
6666
configuration.preview.metricIntervalSeconds);
6767

68+
if (logger.isDebugEnabled()) {
69+
PerformanceCounterContainer.INSTANCE.setLogAvailableJmxMetrics();
70+
}
71+
6872
loadCustomJmxPerfCounters(configuration.jmxMetrics);
6973

7074
PerformanceCounterContainer.INSTANCE.register(new ProcessCpuPerformanceCounter());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.perfcounter;
23+
24+
import static java.util.Arrays.asList;
25+
import static java.util.Collections.emptyList;
26+
import static java.util.Collections.singletonList;
27+
28+
import java.lang.management.ManagementFactory;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.HashMap;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.Set;
36+
import java.util.stream.Collectors;
37+
import javax.annotation.Nullable;
38+
import javax.management.MBeanAttributeInfo;
39+
import javax.management.MBeanInfo;
40+
import javax.management.MBeanServer;
41+
import javax.management.ObjectName;
42+
import javax.management.openmbean.CompositeData;
43+
import javax.management.openmbean.CompositeDataSupport;
44+
import javax.management.openmbean.CompositeType;
45+
import javax.management.openmbean.OpenType;
46+
import org.checkerframework.checker.lock.qual.GuardedBy;
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
49+
50+
class AvailableJmxMetricLogger {
51+
52+
private static final Logger logger = LoggerFactory.getLogger(AvailableJmxMetricLogger.class);
53+
54+
private static final Set<String> NUMERIC_ATTRIBUTE_TYPES =
55+
new HashSet<>(
56+
asList(
57+
"long",
58+
"int",
59+
"double",
60+
"float",
61+
"java.lang.Long",
62+
"java.lang.Integer",
63+
"java.lang.Double",
64+
"java.lang.Float"));
65+
66+
private static final Set<String> BOOLEAN_ATTRIBUTE_TYPES =
67+
new HashSet<>(asList("boolean", "java.lang.Boolean"));
68+
69+
@GuardedBy("lock")
70+
private Map<String, Set<String>> priorAvailableJmxAttributes = new HashMap<>();
71+
72+
private final Object lock = new Object();
73+
74+
void logAvailableJmxMetrics() {
75+
synchronized (lock) {
76+
Map<String, Set<String>> availableJmxAttributes = getAvailableJmxAttributes();
77+
logDifference(priorAvailableJmxAttributes, availableJmxAttributes);
78+
priorAvailableJmxAttributes = availableJmxAttributes;
79+
}
80+
}
81+
82+
private static void logDifference(
83+
Map<String, Set<String>> priorAvailableJmxAttributes,
84+
Map<String, Set<String>> currentAvailableJmxAttributes) {
85+
if (priorAvailableJmxAttributes.isEmpty()) {
86+
// first time
87+
logger.info("available jmx metrics:\n{}", toString(currentAvailableJmxAttributes));
88+
return;
89+
}
90+
Map<String, Set<String>> newlyAvailable =
91+
difference(currentAvailableJmxAttributes, priorAvailableJmxAttributes);
92+
if (!newlyAvailable.isEmpty()) {
93+
logger.info("newly available jmx metrics since last output:\n{}", toString(newlyAvailable));
94+
}
95+
Map<String, Set<String>> noLongerAvailable =
96+
difference(priorAvailableJmxAttributes, currentAvailableJmxAttributes);
97+
if (!noLongerAvailable.isEmpty()) {
98+
logger.info(
99+
"no longer available jmx metrics since last output:\n{}", toString(noLongerAvailable));
100+
}
101+
}
102+
103+
private static String toString(Map<String, Set<String>> jmxAttributes) {
104+
StringBuilder sb = new StringBuilder();
105+
for (Map.Entry<String, Set<String>> entry : jmxAttributes.entrySet()) {
106+
sb.append(" - object name: ")
107+
.append(entry.getKey())
108+
.append("\n")
109+
.append(" numeric attributes: ")
110+
.append(entry.getValue().stream().sorted().collect(Collectors.joining(", ")))
111+
.append("\n");
112+
}
113+
return sb.toString();
114+
}
115+
116+
private static Map<String, Set<String>> getAvailableJmxAttributes() {
117+
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
118+
Set<ObjectName> objectNames = server.queryNames(null, null);
119+
Map<String, Set<String>> availableJmxMetrics = new HashMap<>();
120+
for (ObjectName objectName : objectNames) {
121+
String name = objectName.toString();
122+
try {
123+
Set<String> attrs = getJmxAttributes(server, objectName);
124+
if (!attrs.isEmpty()) {
125+
availableJmxMetrics.put(name, attrs);
126+
}
127+
} catch (Exception e) {
128+
// log exception at trace level since this is expected in several cases, e.g.
129+
// "java.lang.UnsupportedOperationException: CollectionUsage threshold is not supported"
130+
// and available jmx metrics are already only logged at debug
131+
logger.trace(e.getMessage(), e);
132+
availableJmxMetrics.put(name, Collections.singleton("<error getting attributes: " + e));
133+
}
134+
}
135+
return availableJmxMetrics;
136+
}
137+
138+
private static Set<String> getJmxAttributes(MBeanServer server, ObjectName objectName)
139+
throws Exception {
140+
MBeanInfo mbeanInfo = server.getMBeanInfo(objectName);
141+
Set<String> attributeNames = new HashSet<>();
142+
for (MBeanAttributeInfo attribute : mbeanInfo.getAttributes()) {
143+
if (attribute.isReadable()) {
144+
try {
145+
Object value = server.getAttribute(objectName, attribute.getName());
146+
attributeNames.addAll(getNumericAttributes(attribute, value));
147+
} catch (Exception e) {
148+
// log exception at trace level since this is expected in several cases, e.g.
149+
// "java.lang.UnsupportedOperationException: CollectionUsage threshold is not supported"
150+
// and available jmx metrics are already only logged at debug
151+
logger.trace(e.getMessage(), e);
152+
}
153+
}
154+
}
155+
return attributeNames;
156+
}
157+
158+
private static List<String> getNumericAttributes(MBeanAttributeInfo attribute, Object value) {
159+
String attributeType = attribute.getType();
160+
if (NUMERIC_ATTRIBUTE_TYPES.contains(attributeType) && value instanceof Number) {
161+
return singletonList(attribute.getName());
162+
}
163+
if (BOOLEAN_ATTRIBUTE_TYPES.contains(attributeType) && value instanceof Boolean) {
164+
return singletonList(attribute.getName());
165+
}
166+
if (attributeType.equals("java.lang.Object") && value instanceof Number) {
167+
return singletonList(attribute.getName());
168+
}
169+
if (attributeType.equals("java.lang.String") && value instanceof String) {
170+
try {
171+
Double.parseDouble((String) value);
172+
return singletonList(attribute.getName());
173+
} catch (NumberFormatException e) {
174+
// this is expected for non-numeric attributes
175+
return emptyList();
176+
}
177+
}
178+
if (attributeType.equals(CompositeData.class.getName())) {
179+
Object openType = attribute.getDescriptor().getFieldValue("openType");
180+
CompositeType compositeType = null;
181+
if (openType instanceof CompositeType) {
182+
compositeType = (CompositeType) openType;
183+
} else if (openType == null && value instanceof CompositeDataSupport) {
184+
compositeType = ((CompositeDataSupport) value).getCompositeType();
185+
}
186+
if (compositeType != null) {
187+
return getCompositeTypeAttributeNames(attribute, value, compositeType);
188+
}
189+
}
190+
return emptyList();
191+
}
192+
193+
private static List<String> getCompositeTypeAttributeNames(
194+
MBeanAttributeInfo attribute, Object compositeData, CompositeType compositeType) {
195+
List<String> attributeNames = new ArrayList<>();
196+
for (String itemName : compositeType.keySet()) {
197+
OpenType<?> itemType = compositeType.getType(itemName);
198+
if (itemType == null) {
199+
continue;
200+
}
201+
String className = itemType.getClassName();
202+
Class<?> clazz;
203+
try {
204+
clazz = Class.forName(className);
205+
} catch (ClassNotFoundException e) {
206+
logger.warn(e.getMessage(), e);
207+
continue;
208+
}
209+
if (Number.class.isAssignableFrom(clazz)) {
210+
attributeNames.add(attribute.getName() + '.' + itemName);
211+
} else if (clazz == String.class && compositeData instanceof CompositeData) {
212+
Object val = ((CompositeData) compositeData).get(itemName);
213+
if (val instanceof String) {
214+
try {
215+
Double.parseDouble((String) val);
216+
attributeNames.add(attribute.getName() + '.' + itemName);
217+
} catch (NumberFormatException e) {
218+
// this is expected for non-numeric attributes
219+
}
220+
}
221+
}
222+
}
223+
return attributeNames;
224+
}
225+
226+
// visible for testing
227+
static Map<String, Set<String>> difference(
228+
Map<String, Set<String>> map1, Map<String, Set<String>> map2) {
229+
Map<String, Set<String>> difference = new HashMap<>();
230+
for (Map.Entry<String, Set<String>> entry : map1.entrySet()) {
231+
String key = entry.getKey();
232+
Set<String> diff = difference(entry.getValue(), map2.get(key));
233+
if (!diff.isEmpty()) {
234+
difference.put(entry.getKey(), diff);
235+
}
236+
}
237+
return difference;
238+
}
239+
240+
private static Set<String> difference(Set<String> set1, @Nullable Set<String> set2) {
241+
if (set2 == null) {
242+
return set1;
243+
}
244+
HashSet<String> difference = new HashSet<>(set1);
245+
difference.removeAll(set2);
246+
return difference;
247+
}
248+
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/perfcounter/PerformanceCounterContainer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.concurrent.ConcurrentMap;
2828
import java.util.concurrent.ScheduledThreadPoolExecutor;
2929
import java.util.concurrent.TimeUnit;
30+
import org.checkerframework.checker.nullness.qual.Nullable;
3031
import org.slf4j.Logger;
3132
import org.slf4j.LoggerFactory;
3233

@@ -64,6 +65,8 @@ public enum PerformanceCounterContainer {
6465
private final ConcurrentMap<String, PerformanceCounter> performanceCounters =
6566
new ConcurrentHashMap<>();
6667

68+
private volatile @Nullable AvailableJmxMetricLogger availableJmxMetricLogger;
69+
6770
private volatile boolean initialized = false;
6871

6972
private long collectionFrequencyInMillis = DEFAULT_COLLECTION_FREQUENCY_IN_SEC * 1000;
@@ -117,6 +120,10 @@ public void setCollectionFrequencyInSec(long collectionFrequencyInSec) {
117120
this.collectionFrequencyInMillis = collectionFrequencyInSec * 1000;
118121
}
119122

123+
public void setLogAvailableJmxMetrics() {
124+
availableJmxMetricLogger = new AvailableJmxMetricLogger();
125+
}
126+
120127
/**
121128
* A private method that is called only when the container needs to start collecting performance
122129
* counters data. The method will schedule a callback to be called, it will initialize a {@link
@@ -141,6 +148,10 @@ private void scheduleWork() {
141148
new Runnable() {
142149
@Override
143150
public void run() {
151+
if (availableJmxMetricLogger != null) {
152+
availableJmxMetricLogger.logAvailableJmxMetrics();
153+
}
154+
144155
TelemetryClient telemetryClient = TelemetryClient.getActive();
145156

146157
for (PerformanceCounter performanceCounter : performanceCounters.values()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* ApplicationInsights-Java
3+
* Copyright (c) Microsoft Corporation
4+
* All rights reserved.
5+
*
6+
* MIT License
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
8+
* software and associated documentation files (the ""Software""), to deal in the Software
9+
* without restriction, including without limitation the rights to use, copy, modify, merge,
10+
* publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11+
* persons to whom the Software is furnished to do so, subject to the following conditions:
12+
* The above copyright notice and this permission notice shall be included in all copies or
13+
* substantial portions of the Software.
14+
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
16+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
17+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
18+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
* DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
package com.microsoft.applicationinsights.agent.internal.perfcounter;
23+
24+
import static java.util.Arrays.asList;
25+
import static java.util.Collections.singleton;
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.Map;
31+
import java.util.Set;
32+
import org.junit.jupiter.api.Test;
33+
34+
public class AvailableJmxMetricLoggerTest {
35+
36+
@Test
37+
public void testDifference() {
38+
// given
39+
Map<String, Set<String>> map1 = new HashMap<>();
40+
map1.put("one", singleton("1"));
41+
map1.put("two", new HashSet<>(asList("2", "22")));
42+
map1.put("three", new HashSet<>(asList("3", "33", "333")));
43+
44+
Map<String, Set<String>> map2 = new HashMap<>();
45+
map2.put("one", singleton("1"));
46+
map2.put("two", singleton("22"));
47+
48+
// when
49+
Map<String, Set<String>> difference = AvailableJmxMetricLogger.difference(map1, map2);
50+
51+
// then
52+
assertThat(difference).containsOnlyKeys("two", "three");
53+
assertThat(difference.get("two")).containsExactly("2");
54+
assertThat(difference.get("three")).containsExactlyInAnyOrder("3", "33", "333");
55+
}
56+
57+
@Test
58+
public void test() {
59+
AvailableJmxMetricLogger availableJmxMetricLogger = new AvailableJmxMetricLogger();
60+
61+
availableJmxMetricLogger.logAvailableJmxMetrics();
62+
availableJmxMetricLogger.logAvailableJmxMetrics();
63+
}
64+
}

0 commit comments

Comments
 (0)