Skip to content

Commit 5e57c52

Browse files
authored
Merge pull request #980 from microsoft/buildme/add-custom-instrumentation
Bring back custom agent instrumentation
2 parents a3dc07d + 0736ae1 commit 5e57c52

File tree

19 files changed

+978
-42
lines changed

19 files changed

+978
-42
lines changed

agent/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ dependencies {
136136
runtime "org.glowroot.instrumentation:instrumentation-http-url-connection:$instrumentationVersion"
137137
runtime "org.glowroot.instrumentation:instrumentation-log4j:$instrumentationVersion"
138138
runtime "org.glowroot.instrumentation:instrumentation-logback:$instrumentationVersion"
139+
140+
testCompile 'junit:junit:4.12'
139141
}
140142

141143
// region Publishing properties

agent/src/main/java/com/microsoft/applicationinsights/agent/internal/AIAgentXmlLoader.java

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,40 @@
2929
import java.util.List;
3030
import java.util.Map;
3131

32+
import com.google.common.annotations.VisibleForTesting;
33+
import com.google.common.base.Splitter;
34+
import com.microsoft.applicationinsights.agent.internal.config.AgentConfiguration;
3235
import com.microsoft.applicationinsights.agent.internal.config.BuiltInInstrumentation;
33-
import com.microsoft.applicationinsights.agent.internal.config.XmlAgentConfigurationBuilder;
36+
import com.microsoft.applicationinsights.agent.internal.config.ClassInstrumentationData;
37+
import com.microsoft.applicationinsights.agent.internal.config.MethodInfo;
38+
import com.microsoft.applicationinsights.agent.internal.config.builder.XmlAgentConfigurationBuilder;
39+
import org.glowroot.instrumentation.engine.config.AdviceConfig;
40+
import org.glowroot.instrumentation.engine.config.AdviceConfig.CaptureKind;
41+
import org.glowroot.instrumentation.engine.config.ImmutableAdviceConfig;
42+
import org.glowroot.instrumentation.engine.config.ImmutableInstrumentationDescriptor;
3443
import org.glowroot.instrumentation.engine.config.InstrumentationDescriptor;
3544
import org.glowroot.instrumentation.engine.config.InstrumentationDescriptors;
45+
import org.objectweb.asm.Type;
46+
import org.objectweb.asm.commons.Method;
47+
import org.slf4j.Logger;
48+
import org.slf4j.LoggerFactory;
3649

3750
class AIAgentXmlLoader {
3851

39-
static BuiltInInstrumentation load(File agentJarParentFile) {
52+
private static final Logger logger = LoggerFactory.getLogger(AIAgentXmlLoader.class);
53+
54+
static AgentConfiguration load(File agentJarParentFile) {
4055
return new XmlAgentConfigurationBuilder().parseConfigurationFile(agentJarParentFile.getAbsolutePath());
4156
}
4257

43-
static List<InstrumentationDescriptor> getInstrumentationDescriptors(
44-
BuiltInInstrumentation builtInInstrumentation) throws IOException {
58+
static List<InstrumentationDescriptor> getInstrumentationDescriptors(AgentConfiguration agentConfiguration)
59+
throws IOException {
4560

46-
boolean httpEnabled = builtInInstrumentation.isHttpEnabled();
47-
boolean jdbcEnabled = builtInInstrumentation.isJdbcEnabled();
48-
boolean loggingEnabled = builtInInstrumentation.isLoggingEnabled();
49-
boolean redisEnabled = builtInInstrumentation.isJedisEnabled();
61+
BuiltInInstrumentation builtInConfiguration = agentConfiguration.getBuiltInInstrumentation();
62+
boolean httpEnabled = builtInConfiguration.isHttpEnabled();
63+
boolean jdbcEnabled = builtInConfiguration.isJdbcEnabled();
64+
boolean loggingEnabled = builtInConfiguration.isLoggingEnabled();
65+
boolean redisEnabled = builtInConfiguration.isJedisEnabled();
5066

5167
List<InstrumentationDescriptor> instrumentationDescriptors = new ArrayList<>();
5268
for (InstrumentationDescriptor instrumentationDescriptor : InstrumentationDescriptors.read()) {
@@ -79,6 +95,12 @@ static List<InstrumentationDescriptor> getInstrumentationDescriptors(
7995
break;
8096
}
8197
}
98+
99+
InstrumentationDescriptor instrumentationDescriptor = buildCustomInstrumentation(agentConfiguration);
100+
if (instrumentationDescriptor != null) {
101+
System.out.println(instrumentationDescriptor);
102+
instrumentationDescriptors.add(instrumentationDescriptor);
103+
}
82104
return instrumentationDescriptors;
83105
}
84106

@@ -102,4 +124,106 @@ static Map<String, Map<String, Object>> getInstrumentationConfig(BuiltInInstrume
102124

103125
return instrumentationConfiguration;
104126
}
127+
128+
129+
private static InstrumentationDescriptor buildCustomInstrumentation(AgentConfiguration agentConfiguration) {
130+
131+
List<AdviceConfig> adviceConfigs = new ArrayList<>();
132+
133+
for (Map.Entry<String, ClassInstrumentationData> classEntry : agentConfiguration.getClassesToInstrument()
134+
.entrySet()) {
135+
136+
String className = classEntry.getKey();
137+
if (!validJavaFqcn(className)) {
138+
// this is needed to prevent glowroot wildcard from being used for now
139+
// and also to prevent commas in the class name which would cause parsing issues in LocalSpanImpl
140+
logger.warn("Invalid class name: {}", className);
141+
continue;
142+
}
143+
ClassInstrumentationData classInstrumentationData = classEntry.getValue();
144+
145+
for (Map.Entry<String, Map<String, MethodInfo>> methodNameEntry :
146+
classInstrumentationData.getMethodInfos().entrySet()) {
147+
148+
String methodName = methodNameEntry.getKey();
149+
if (!validJavaIdentifier(methodName)) {
150+
// this is needed to prevent glowroot wildcard from being used for now
151+
// and also to prevent commas in the method name which would cause parsing issues in LocalSpanImpl
152+
logger.warn("Invalid method name: {}", methodName);
153+
continue;
154+
}
155+
156+
Map<String, MethodInfo> methodInfos = methodNameEntry.getValue();
157+
158+
for (Map.Entry<String, MethodInfo> entry : methodInfos.entrySet()) {
159+
160+
MethodInfo methodInfo = entry.getValue();
161+
String signature = entry.getKey();
162+
163+
ImmutableAdviceConfig.Builder adviceConfig = ImmutableAdviceConfig.builder()
164+
.className(className)
165+
.methodName(methodName);
166+
167+
if (signature.equals(ClassInstrumentationData.ANY_SIGNATURE_MARKER)) {
168+
adviceConfig.addMethodParameterTypes("..");
169+
} else {
170+
Method method = new Method(methodName, signature);
171+
for (Type type : method.getArgumentTypes()) {
172+
adviceConfig.addMethodParameterTypes(type.getClassName());
173+
}
174+
adviceConfig.methodReturnType(method.getReturnType().getClassName());
175+
}
176+
177+
adviceConfigs.add(adviceConfig.captureKind(CaptureKind.LOCAL_SPAN)
178+
// advice config doesn't support threshold, so threshold is embedded into message
179+
// and then parsed out by the agent to decide whether to report telemetry
180+
.spanMessageTemplate(
181+
"__custom," + className + "," + methodName + "," + methodInfo.getThresholdInMS() +
182+
"," + classInstrumentationData.getClassType())
183+
.timerName("custom")
184+
.build());
185+
}
186+
}
187+
}
188+
189+
if (adviceConfigs.isEmpty()) {
190+
return null;
191+
} else {
192+
return ImmutableInstrumentationDescriptor.builder()
193+
.id("__custom")
194+
.name("__custom")
195+
.addAllAdviceConfigs(adviceConfigs)
196+
.build();
197+
}
198+
}
199+
200+
@VisibleForTesting
201+
static boolean validJavaFqcn(String fqcn) {
202+
List<String> parts = Splitter.on('.').splitToList(fqcn);
203+
if (parts.isEmpty()) {
204+
return false;
205+
}
206+
for (int i = 0; i < parts.size() - 1; i++) {
207+
if (!validJavaIdentifier(parts.get(i))) {
208+
return false;
209+
}
210+
}
211+
return validJavaIdentifier(parts.get(parts.size() - 1));
212+
}
213+
214+
@VisibleForTesting
215+
static boolean validJavaIdentifier(String identifier) {
216+
if (identifier.isEmpty()) {
217+
return false;
218+
}
219+
if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
220+
return false;
221+
}
222+
for (int i = 1; i < identifier.length(); i++) {
223+
if (!Character.isJavaIdentifierPart(identifier.charAt(i))) {
224+
return false;
225+
}
226+
}
227+
return true;
228+
}
105229
}

agent/src/main/java/com/microsoft/applicationinsights/agent/internal/MainEntryPoint.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Collections;
2727
import java.util.List;
2828

29+
import com.microsoft.applicationinsights.agent.internal.config.AgentConfiguration;
2930
import com.microsoft.applicationinsights.agent.internal.config.BuiltInInstrumentation;
3031
import com.microsoft.applicationinsights.agent.internal.model.Global;
3132
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -78,7 +79,9 @@ private static void start(Instrumentation instrumentation, File agentJarFile) th
7879
throw new Exception("Could not create directory: " + tmpDir.getAbsolutePath());
7980
}
8081

81-
BuiltInInstrumentation builtInInstrumentation = AIAgentXmlLoader.load(agentJarParentFile);
82+
AgentConfiguration agentConfiguration = AIAgentXmlLoader.load(agentJarParentFile);
83+
84+
BuiltInInstrumentation builtInInstrumentation = agentConfiguration.getBuiltInInstrumentation();
8285

8386
if (!builtInInstrumentation.isEnabled()) {
8487
return;
@@ -88,7 +91,7 @@ private static void start(Instrumentation instrumentation, File agentJarFile) th
8891
Global.setOutboundW3CBackCompatEnabled(builtInInstrumentation.isW3cBackCompatEnabled());
8992

9093
List<InstrumentationDescriptor> instrumentationDescriptors =
91-
AIAgentXmlLoader.getInstrumentationDescriptors(builtInInstrumentation);
94+
AIAgentXmlLoader.getInstrumentationDescriptors(agentConfiguration);
9295

9396
ConfigServiceFactory configServiceFactory = new SimpleConfigServiceFactory(instrumentationDescriptors,
9497
AIAgentXmlLoader.getInstrumentationConfig(builtInInstrumentation));
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.config;
23+
24+
import java.util.Map;
25+
26+
import com.microsoft.applicationinsights.agent.internal.config.builder.BuiltInInstrumentationBuilder;
27+
28+
public class AgentConfiguration {
29+
30+
private Map<String, ClassInstrumentationData> classesToInstrument;
31+
private BuiltInInstrumentation builtInInstrumentation = new BuiltInInstrumentationBuilder().create();
32+
33+
public void setClassesToInstrument(Map<String, ClassInstrumentationData> classesToInstrument) {
34+
this.classesToInstrument = classesToInstrument;
35+
}
36+
37+
public Map<String, ClassInstrumentationData> getClassesToInstrument() {
38+
return classesToInstrument;
39+
}
40+
41+
public BuiltInInstrumentation getBuiltInInstrumentation() {
42+
return builtInInstrumentation;
43+
}
44+
45+
public void setBuiltInData(BuiltInInstrumentation builtInInstrumentation) {
46+
this.builtInInstrumentation = builtInInstrumentation;
47+
}
48+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.config;
23+
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
27+
import com.google.common.base.Strings;
28+
29+
public class ClassInstrumentationData {
30+
31+
public static final String OTHER_TYPE = "OTHER";
32+
33+
public static final String ANY_SIGNATURE_MARKER = "";
34+
35+
private final String classType;
36+
37+
private final long thresholdInMS;
38+
39+
// first key is method name, second key is signature or ANY_SIGNATURE_MARKER
40+
private final Map<String, Map<String, MethodInfo>> methodInfos = new HashMap<>();
41+
42+
public ClassInstrumentationData(String classType, long thresholdInMS) {
43+
this.classType = classType;
44+
this.thresholdInMS = thresholdInMS;
45+
}
46+
47+
public String getClassType() {
48+
return classType;
49+
}
50+
51+
public long getThresholdInMS() {
52+
return thresholdInMS;
53+
}
54+
55+
public Map<String, Map<String, MethodInfo>> getMethodInfos() {
56+
return methodInfos;
57+
}
58+
59+
public void addMethod(String methodName, String signature, long thresholdInMS) {
60+
61+
Map<String, MethodInfo> innerMap = methodInfos.get(methodName);
62+
if (innerMap == null) {
63+
innerMap = new HashMap<>();
64+
methodInfos.put(methodName, innerMap);
65+
}
66+
67+
MethodInfo methodInfo = new MethodInfo(thresholdInMS);
68+
69+
if (Strings.isNullOrEmpty(signature)) {
70+
innerMap.put(ANY_SIGNATURE_MARKER, methodInfo);
71+
} else {
72+
innerMap.put(signature, methodInfo);
73+
}
74+
}
75+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.config;
23+
24+
/**
25+
* The class holds the type of actions that should be done on an instrumented method.
26+
* The class is the 'decision' for the method after taking into consideration init/configuration data.
27+
*/
28+
public class MethodInfo {
29+
30+
private final long thresholdInMS;
31+
32+
public MethodInfo(long thresholdInMS) {
33+
this.thresholdInMS = thresholdInMS;
34+
}
35+
36+
public long getThresholdInMS() {
37+
return thresholdInMS;
38+
}
39+
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
* DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
package com.microsoft.applicationinsights.agent.internal.config;
22+
package com.microsoft.applicationinsights.agent.internal.config.builder;
2323

24+
import com.microsoft.applicationinsights.agent.internal.config.BuiltInInstrumentation;
2425
import org.slf4j.Logger;
2526
import org.slf4j.LoggerFactory;
2627

27-
class BuiltInInstrumentationBuilder {
28+
public class BuiltInInstrumentationBuilder {
2829

2930
private static final Logger logger = LoggerFactory.getLogger(BuiltInInstrumentationBuilder.class);
3031

0 commit comments

Comments
 (0)