Skip to content

Commit ecb14dd

Browse files
adrianprecubCalamarBicefalo
authored andcommitted
BAEL-1728: add java instrumentation
1 parent 2fec9da commit ecb14dd

9 files changed

Lines changed: 365 additions & 0 deletions

File tree

core-java/pom.xml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@
173173
<artifactId>c3p0</artifactId>
174174
<version>${c3p0.version}</version>
175175
</dependency>
176+
<!-- instrumentation -->
177+
<dependency>
178+
<groupId>org.javassist</groupId>
179+
<artifactId>javassist</artifactId>
180+
<version>${javaassist.version}</version>
181+
</dependency>
182+
<dependency>
183+
<groupId>com.sun</groupId>
184+
<artifactId>tools</artifactId>
185+
<version>1.8.0</version>
186+
<scope>system</scope>
187+
<systemPath>${java.home}/../lib/tools.jar</systemPath>
188+
</dependency>
176189
</dependencies>
177190

178191
<build>
@@ -400,6 +413,111 @@
400413
</plugins>
401414
</build>
402415
</profile>
416+
417+
<!-- java instrumentation profiles to build jars -->
418+
<profile>
419+
<id>buildAgentLoader</id>
420+
<build>
421+
<plugins>
422+
<plugin>
423+
<groupId>org.apache.maven.plugins</groupId>
424+
<artifactId>maven-jar-plugin</artifactId>
425+
<executions>
426+
<execution>
427+
<phase>package</phase>
428+
<goals>
429+
<goal>jar</goal>
430+
</goals>
431+
<configuration>
432+
<classifier>agentLoader</classifier>
433+
<classesDirectory>target/classes</classesDirectory>
434+
<archive>
435+
<manifest>
436+
<addClasspath>true</addClasspath>
437+
</manifest>
438+
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
439+
</archive>
440+
441+
<includes>
442+
<include>com/baeldung/instrumentation/application/AgentLoader.class</include>
443+
<include>com/baeldung/instrumentation/application/Launcher.class</include>
444+
</includes>
445+
</configuration>
446+
</execution>
447+
</executions>
448+
</plugin>
449+
</plugins>
450+
</build>
451+
</profile>
452+
<profile>
453+
<id>buildApplication</id>
454+
<build>
455+
<plugins>
456+
<plugin>
457+
<groupId>org.apache.maven.plugins</groupId>
458+
<artifactId>maven-jar-plugin</artifactId>
459+
<executions>
460+
<execution>
461+
<phase>package</phase>
462+
<goals>
463+
<goal>jar</goal>
464+
</goals>
465+
<configuration>
466+
<classifier>application</classifier>
467+
<classesDirectory>target/classes</classesDirectory>
468+
<archive>
469+
<manifest>
470+
<addClasspath>true</addClasspath>
471+
</manifest>
472+
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
473+
</archive>
474+
475+
<includes>
476+
<include>com/baeldung/instrumentation/application/MyAtm.class</include>
477+
<include>com/baeldung/instrumentation/application/MyAtmApplication.class</include>
478+
<include>com/baeldung/instrumentation/application/Launcher.class</include>
479+
</includes>
480+
</configuration>
481+
</execution>
482+
</executions>
483+
</plugin>
484+
</plugins>
485+
</build>
486+
</profile>
487+
<profile>
488+
<id>buildAgent</id>
489+
<build>
490+
<plugins>
491+
<plugin>
492+
<groupId>org.apache.maven.plugins</groupId>
493+
<artifactId>maven-jar-plugin</artifactId>
494+
<executions>
495+
<execution>
496+
<phase>package</phase>
497+
<goals>
498+
<goal>jar</goal>
499+
</goals>
500+
<configuration>
501+
<classifier>agent</classifier>
502+
<classesDirectory>target/classes</classesDirectory>
503+
<archive>
504+
<manifest>
505+
<addClasspath>true</addClasspath>
506+
</manifest>
507+
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
508+
</archive>
509+
510+
<includes>
511+
<include>com/baeldung/instrumentation/agent/AtmTransformer.class</include>
512+
<include>com/baeldung/instrumentation/agent/MyInstrumentationAgent.class</include>
513+
</includes>
514+
</configuration>
515+
</execution>
516+
</executions>
517+
</plugin>
518+
</plugins>
519+
</build>
520+
</profile>
403521
</profiles>
404522

405523
<properties>
@@ -453,6 +571,8 @@
453571
<!-- Mime Type Libraries -->
454572
<tika.version>1.18</tika.version>
455573
<jmime-magic.version>0.1.5</jmime-magic.version>
574+
<!-- instrumentation -->
575+
<javaassist.version>3.21.0-GA</javaassist.version>
456576
</properties>
457577

458578
</project>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.baeldung.instrumentation.agent;
2+
3+
import javassist.CannotCompileException;
4+
import javassist.ClassPool;
5+
import javassist.CtClass;
6+
import javassist.CtMethod;
7+
import javassist.NotFoundException;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
import java.io.IOException;
12+
import java.lang.instrument.ClassFileTransformer;
13+
import java.lang.instrument.IllegalClassFormatException;
14+
import java.security.ProtectionDomain;
15+
16+
public class AtmTransformer implements ClassFileTransformer {
17+
18+
private static Logger LOGGER = LoggerFactory.getLogger(AtmTransformer.class);
19+
20+
private static final String WITHDRAW_MONEY_METHOD = "withdrawMoney";
21+
22+
/** The internal form class name of the class to transform */
23+
private String targetClassName;
24+
/** The class loader of the class we want to transform */
25+
private ClassLoader targetClassLoader;
26+
27+
public AtmTransformer(String targetClassName, ClassLoader targetClassLoader) {
28+
this.targetClassName = targetClassName;
29+
this.targetClassLoader = targetClassLoader;
30+
}
31+
32+
@Override
33+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
34+
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
35+
byte[] byteCode = classfileBuffer;
36+
37+
String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/"); //replace . with /
38+
if (!className.equals(finalTargetClassName)) {
39+
return byteCode;
40+
}
41+
42+
if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
43+
LOGGER.info("[Agent] Transforming class MyAtm");
44+
try {
45+
ClassPool cp = ClassPool.getDefault();
46+
CtClass cc = cp.get(targetClassName);
47+
CtMethod m = cc.getDeclaredMethod(WITHDRAW_MONEY_METHOD);
48+
m.addLocalVariable("startTime", CtClass.longType);
49+
m.insertBefore("startTime = System.currentTimeMillis();");
50+
51+
StringBuilder endBlock = new StringBuilder();
52+
53+
m.addLocalVariable("endTime", CtClass.longType);
54+
m.addLocalVariable("opTime", CtClass.longType);
55+
endBlock.append("endTime = System.currentTimeMillis();");
56+
endBlock.append("opTime = (endTime-startTime)/1000;");
57+
58+
endBlock.append("LOGGER.info(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");
59+
60+
m.insertAfter(endBlock.toString());
61+
62+
byteCode = cc.toBytecode();
63+
cc.detach();
64+
} catch (NotFoundException | CannotCompileException | IOException e) {
65+
LOGGER.error("Exception", e);
66+
}
67+
}
68+
return byteCode;
69+
}
70+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.baeldung.instrumentation.agent;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.lang.instrument.Instrumentation;
7+
8+
public class MyInstrumentationAgent {
9+
private static Logger LOGGER = LoggerFactory.getLogger(MyInstrumentationAgent.class);
10+
11+
public static void premain(String agentArgs, Instrumentation inst) {
12+
LOGGER.info("[Agent] In premain method");
13+
14+
String className = "com.baeldung.instrumentation.application.MyAtm";
15+
transformClass(className,inst);
16+
}
17+
18+
public static void agentmain(String agentArgs, Instrumentation inst) {
19+
LOGGER.info("[Agent] In agentmain method");
20+
21+
String className = "com.baeldung.instrumentation.application.MyAtm";
22+
transformClass(className,inst);
23+
}
24+
25+
private static void transformClass(String className, Instrumentation instrumentation) {
26+
Class<?> targetCls = null;
27+
ClassLoader targetClassLoader = null;
28+
// see if we can get the class using forName
29+
try {
30+
targetCls = Class.forName(className);
31+
targetClassLoader = targetCls.getClassLoader();
32+
transform(targetCls, targetClassLoader, instrumentation);
33+
return;
34+
} catch (Exception ex) {
35+
LOGGER.error("Class [{}] not found with Class.forName");
36+
}
37+
// otherwise iterate all loaded classes and find what we want
38+
for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
39+
if(clazz.getName().equals(className)) {
40+
targetCls = clazz;
41+
targetClassLoader = targetCls.getClassLoader();
42+
transform(targetCls, targetClassLoader, instrumentation);
43+
return;
44+
}
45+
}
46+
throw new RuntimeException("Failed to find class [" + className + "]");
47+
}
48+
49+
private static void transform(Class<?> clazz, ClassLoader classLoader, Instrumentation instrumentation) {
50+
AtmTransformer dt = new AtmTransformer(clazz.getName(), classLoader);
51+
instrumentation.addTransformer(dt, true);
52+
try {
53+
instrumentation.retransformClasses(clazz);
54+
} catch (Exception ex) {
55+
throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
56+
}
57+
}
58+
59+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.baeldung.instrumentation.application;
2+
3+
import com.sun.tools.attach.VirtualMachine;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.io.File;
8+
import java.util.Optional;
9+
10+
/**
11+
* Created by adi on 6/10/18.
12+
*/
13+
public class AgentLoader {
14+
private static Logger LOGGER = LoggerFactory.getLogger(AgentLoader.class);
15+
16+
public static void run(String[] args) {
17+
String agentFilePath = "/home/adi/Desktop/agent-1.0.0-jar-with-dependencies.jar";
18+
String applicationName = "MyAtmApplication";
19+
20+
//iterate all jvms and get the first one that matches our application name
21+
Optional<String> jvmProcessOpt = Optional.ofNullable(VirtualMachine.list()
22+
.stream()
23+
.filter(jvm -> {
24+
LOGGER.info("jvm:{}", jvm.displayName());
25+
return jvm.displayName().contains(applicationName);
26+
})
27+
.findFirst().get().id());
28+
29+
if(!jvmProcessOpt.isPresent()) {
30+
LOGGER.error("Target Application not found");
31+
return;
32+
}
33+
File agentFile = new File(agentFilePath);
34+
try {
35+
String jvmPid = jvmProcessOpt.get();
36+
LOGGER.info("Attaching to target JVM with PID: " + jvmPid);
37+
VirtualMachine jvm = VirtualMachine.attach(jvmPid);
38+
jvm.loadAgent(agentFile.getAbsolutePath());
39+
jvm.detach();
40+
LOGGER.info("Attached to target JVM and loaded Java agent successfully");
41+
} catch (Exception e) {
42+
throw new RuntimeException(e);
43+
}
44+
}
45+
46+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.baeldung.instrumentation.application;
2+
3+
/**
4+
* Created by adi on 6/14/18.
5+
*/
6+
public class Launcher {
7+
public static void main(String[] args) throws Exception {
8+
if(args[0].equals("StartMyAtmApplication")) {
9+
new MyAtmApplication().run(args);
10+
} else if(args[0].equals("LoadAgent")) {
11+
new AgentLoader().run(args);
12+
}
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.baeldung.instrumentation.application;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
/**
7+
* Created by adi on 6/11/18.
8+
*/
9+
public class MyAtm {
10+
private static Logger LOGGER = LoggerFactory.getLogger(MyAtm.class);
11+
12+
private static final int account = 10;
13+
14+
public static void withdrawMoney(int amount) throws InterruptedException {
15+
Thread.sleep(2000l); //processing going on here
16+
LOGGER.info("[Application] Successful Withdrawal of [{}] units!", amount);
17+
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.baeldung.instrumentation.application;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
public class MyAtmApplication {
7+
8+
private static Logger LOGGER = LoggerFactory.getLogger(MyAtmApplication.class);
9+
10+
public static void run(String[] args) throws Exception {
11+
LOGGER.info("[Application] Starting ATM application");
12+
MyAtm.withdrawMoney(Integer.parseInt(args[2]));
13+
14+
Thread.sleep(Long.valueOf(args[1]));
15+
16+
MyAtm.withdrawMoney(Integer.parseInt(args[3]));
17+
}
18+
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Agent-Class: com.baeldung.instrumentation.agent.MyInstrumentationAgent
2+
Can-Redefine-Classes: true
3+
Can-Retransform-Classes: true
4+
Premain-Class: com.baeldung.instrumentation.agent.MyInstrumentationAgent
5+
Main-Class: com.baeldung.instrumentation.application.Launcher
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Configuration status="WARN">
3+
<Appenders>
4+
<Console name="Console" target="SYSTEM_OUT">
5+
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level - %msg%n"/>
6+
</Console>
7+
</Appenders>
8+
<Loggers>
9+
<Root level="debug">
10+
<AppenderRef ref="Console"/>
11+
</Root>
12+
</Loggers>
13+
</Configuration>

0 commit comments

Comments
 (0)