Últimamente hemos estado viendo en los posts como realizar tareas de monitorización. Hoy veremos los Java Agents, que son y como funcionan. Los cuales también pueden ayudarnos a realizar tareas de monitorización, entre otras cosas. Algo que intentaremos explicar en este post, primero con un poco de teoría y luego con la ayuda de ejemplos.
¿Que son los Java Agents?, son básicamente librerías Java en las cuales se incluyen clases que implementan la API de Instrumentación de Java. La cual esta disponible desde la JDK 1.5. Esta es una API muy sencilla pero a la vez muy potente. Y establece las bases de la instrumentación de aplicaciones.
La funcionalidad principal de esta instrumentación, es que se nos permitirá la adición de código a determinados métodos. Y con esta adición, podremos recompilar datos para ser utilizados con fines principalmente de monitorización y análisis o registro de eventos. Permitiéndonos obtener información vital sobre la aplicación que nos puede ayudar a resolver problemas u obtener datos que no serían posibles de otra forma.
La principal cualidad de esta API de instrumentación es la alteración del byte-code de las clases que están siendo ejecutadas por la maquina virtual. A través de esta alteración podremos realizar las acciones anteriormente indicadas.
Pero centrémonos, ¿Que necesitamos para crear un Java Agent?, seguir estos tres pasos:
- Desarrollar un método concreto.
- Crear y configurar correctamente el fichero MANIFEST.MF
- Indicar a la máquina virtual que tenga en cuenta a nuestra librería con el Java Agent.
El método a desarrollar depende de cuando queramos que sea asociado el agente a la ejecución de aplicaciones en la JVM. La API de instrumentación pondrá a disposición de nosotros dos métodos diferentes:
- premain: Permitirá la ejecución del Java Agent y su método asociado antes de la ejecución de cualquier otra aplicación en la JVM. Para lo cual el Java Agent debe indicarse en el arranque de la JVM.
- agentmain: Permitirá la ejecución del Java Agent una vez que la JVM ya se encuentre ejecutando una aplicación. Esta asociación debe realizarse de forma programática.
- Premain-Class: Para indicar la clase que implemente el método premain.
- Agent-Class: Para indicar la clase que implemente el método agentmain.
- Can-Redefine-Classes: Permite indicar si el Java Agent puede o no redefinir las clases que interfiera.
- Can-Retransform-Classes: Permite indicar si el Java Agent puede o no transformar la clase que interfiera.
- maven-shade-plugin: nos permite crear la librería incluyendo las dependencias en el caso de que las tenga.
- maven-jar-plugin: nos permite asociar el fichero MANIFEST a la librería a crear.
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.2.0</version> <configuration> <archive> <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> </plugin> </plugins>
java -javaagent:/full/path/to/javaAgent.jar -jar app.jar
File agentFile = Paths.get("agent.jar").toFile(); VirtualMachine jvm = VirtualMachine.attach(VirtualMachine.list().get(0).id()); jvm.loadAgent(agentFile.getAbsolutePath()); // jvm.detach(); //to finish association
@Log4j2 public class BasicAgentExample { public static void premain(final String agentArgs, final Instrumentation inst) { log.info("Start from premain. agentArgs: " + agentArgs); inst.addTransformer((loader, className, classBeingRedefined, protectionDomain, classfileBuffer) -> { log.info(String.format("Classname: %s", className)); return null; }); } }
[INFO ] 2021-03-04 [main] BasicAgentExample - Start from premain. agentArgs: null [INFO ] 2021-03-04 [main] BasicAgentExample - Classname: sun/misc/PostVMInitHook
....
@Log4j2 public class CounterAgentExample { public static void premain(final String agentArgs, final Instrumentation inst) { log.info("Start from premain. agentArgs: " + agentArgs); Advice advice = Advice.to(CounterMeasureAdvice.class); new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(ElementMatchers.isAbstract())) .transform((builder, type, classLoader, module) -> builder.visit(advice.on(ElementMatchers.isMethod()))) .installOn(inst); } }
public class CounterMeasureAdvice { public final static Logger log = LogManager.getRootLogger(); public static Map<String, Long> map = new HashMap<>(); @Advice.OnMethodEnter public static void enter(@Advice.Origin final String origin) { log.info("CounterMeasureAdvice: Enter into: " + origin); if (map.containsKey(origin)) { map.put(origin, map.get(origin) + 1); } else { map.put(origin, 1L); } log.info(String.format("Origin %s invoked %d times", origin, map.get(origin))); } }
public class ExampleThree extends AbstractClass { public static int fibonacci(final int n) { if (n > 1) { return fibonacci(n - 1) + fibonacci(n - 2); // función recursiva } else { // caso base return n; } } // java -javaagent:/path/to/agent.jar -cp *.jar the.main.ClassName public static void main(final String[] args) { System.out.println("Fibonacci de 5: " + fibonacci(5)); } }
[INFO ] 2021-03-04 [main] - CounterMeasureAdvice: Enter into: public static int ExampleThree.fibonacci(int) [INFO ] 2021-03-04 [main] - Origin public static int ExampleThree.fibonacci(int) invoked 14 times [INFO ] 2021-03-04 [main] - CounterMeasureAdvice: Enter into: public static int ExampleThree.fibonacci(int) [INFO ] 2021-03-04 [main] - Origin public static int ExampleThree.fibonacci(int) invoked 15 times
No hay comentarios:
Publicar un comentario