Difference between revisions of "AUG 5 Writing Java Probes"

From OC Systems Wiki!
Jump to: navigation, search
m (Lists)
m (Example 5-11. Example6a.xmj)
Line 416: Line 416:
 
==== Example 5-11. Example6a.xmj ====
 
==== Example 5-11. Example6a.xmj ====
  
As you can see, the <code>&lt;parameter&gt;</code> tag has two attributes, "name" and "value"; both are required. In [#MARKER-9-958 Example6ABean.java] below, we'll see how the ProbeBean can reference and use the parameter we've defined.
+
As you can see, the <code>&lt;parameter&gt;</code> tag has two attributes, "name" and "value"; both are required. In [[#Example6ABean.java|Example6ABean.java]] below, we'll see how the ProbeBean can reference and use the parameter we've defined.
  
 
  import com.ocsystems.aprobe.*;
 
  import com.ocsystems.aprobe.*;

Revision as of 23:41, 20 March 2018

Next Previous Top Contents Index

Aprobe User Guide

Writing Java Probes


The previous chapters described applying Aprobe to native programs: those which consist of linked object files created by compiling C, C or Ada source code.

Aprobe also supports applying probes on entry and exit to Java methods. This is in fact just an elaborate probe on the Java Virtual Machine (JVM), which is a compiled program. This probe replaces the Java byte code as it is loaded with a patched version, which may cause additional, user-provided Java "probes" to be executed.

Your Java probes are picked up from your classpath, and their mapping to particular methods is specified in an XMJ file, which is an XML file with the suffix .xmj.

Requirements

Aprobe for Java only works for Java version 1.2 or newer. This should not be an issue in most cases. However, users of AIX version 4.3 or older probably cannot run Java version 1.2 without a number of patches. Check with OC Systems for more information.

If you're not sure what version you have, simply run

java -version

from the command-line.

To compile your own probes, you will need a Java Development Kit (JDK), which provides the "javac" (Java compiler) command. In the examples that follow we will assume that the Java Runtime Environment (JRE) and JDK are under the same directory, identified by the JAVA_HOME environment variable.

Applying Java Probes

Java classes are executed either directly using the java command, or by compiled programs which contain an embedded JVM, such as a web browser. Both may be probed, though somewhat differently.

In the first case, one uses the apjava command, which processes the java command line and then invokes aprobe on the java program itself.

When running an application with an embedded JVM such as Netscape, you could theoretically use aprobe and pre-load the Java library, something like aprobe -dll jvm_library ... application where jvm_library is the path the library that will be loaded by the application. However, in practice, you can't generally run aprobe on such processes directly from the command line, but must intercept and apply Aprobe on a child process. This is supported only by OC Systems' RootCause product.

In the remainder of this discussion we will assume a pure Java application run with apjava.

Tracing Java Programs

OC Systems RootCause product provides extensive support for tracing and analyzing Java applications. Some of that support is included in Aprobe in the trace.ual predefined probe. To use this on your Java application, you would:

  1. Copy $APROBE/probes/trace.cfg to java.trace.cfg in your current directory.
  2. Edit java.trace.cfg and replace the default "Trace *" with TRACE clauses that specify which classes or methods in the special "$java$" module are to be traced. For example:
TRACE "Pi::*" in "$java$" PARAMETERS TRUE
  1. Run your application using [AUG_Tools_Reference#apjava|apjava]] and trace, for example:
apjava -u trace -java Pi
  1. (See "A Simple Example" below concerning the Pi example.)
  2. Format the data. The configuration file and the generated APD files are based on the program name "java", so you would format the output with:
apformat java.apd

You can use the -d option to specify different APD filenames and the -c UAL option to name a different configuration file, for example:

mv java.trace.cfg Pi.trace.cfg
apjava -d Pi.apd -u trace -p "-c Pi.trace.cfg" -java Pi
apformat Pi.apd

See trace.ual for more information.

A Simple Example

This chapter describes how one writes these files though a graduated series of a examples. The source text for these examples is found on-line in the directory:

$APROBE/examples/java

Let's start with a simple example of how to do this. Under the above directory are two subdirectories, "Pi" and "JNI". To run the Pi example:

java -classpath $APROBE/examples/java/Pi Pi

Write the Probe In Java

Create a file called MyFirstProbe.java as shown below.

This probe will print messages whenever a method to which it is applied gets called.

import com.ocsystems.aprobe.*;

public class MyFirstProbe extends ProbeMethod  {
  public boolean onEntry( Object[] parameters )
  {
    System.out.println( "Hello world (from my probe)!" );

    for( int i=parameters.length-1; i>=0; i-- )
  {
    System.out.println(
        "Parameter # "   i   " is "   parameters[i] );
  }
  return true;
}

public Object onExit( Object returnValue )
{
  System.out.println( "Goodbye world (from my probe)!" );
  System.out.println( "Return value was: "   returnValue );
  return returnValue;
}

public void onExit() // no return value {
  System.out.println( "Goodbye world (from my probe)!" );
}

public void onExceptionExit( Throwable e )
{
  System.out.println(
    "Goodbye world (from my probe), due to exception:" );
  System.out.println( e );
  }
}

Example 5-1. MyFirstProbe.java

Note that although all this probe does is make some calls to print via System.out.println(), probes may contain arbitrary Java code, to do whatever you want.

You can create any of your own probes to be applied to methods simply by extending the class com.ocsystems.aprobe.ProbeMethod, and overriding the onEntry(), onExit() and onExceptionExit() methods, as shown above. As you can see, your probe has access to the parameters and return value of the method it is probing.

Compile the Java Probe

Then compile this MyFirstProbe.java. You must specify to the java compiler where it can find the classes in the package com.ocsystems.aprobe. Do this with the command

 javac -classpath $APROBE/lib/aprobe.jar MyFirstProbe.java

This will create MyFirstProbe.class.

Write an XMJ File

Now that we have written a probe, how do we specify where this probe is to apply? This is done with an XMJ file, or deployment descriptor file, as follows.

  1. In your current directory, create another file, called MyFirstProbe.xmj, with the following contents:
<probe_deployment>
  <probe class="MyFirstProbe">
    <target value="Pi::main"/>
  </probe>
</probe_deployment>

Example 5-2. MyFirstProbe.xmj

This indicates that the probe contained in the class MyFirstProbe should be applied to the method main() in target class Pi. You could pick any public method in any class that interested you.

Run With apjava

Run the Pi program again, this time using AUG_Tools_Reference#apjava apjava to apply the probes described in the XMJ file:

apjava -u MyFirstProbe.xmj -java -classpath $APROBE/examples/java/Pi Pi

Note that this command assumes that JAVA_HOME is defined, from which apjava determines which Java Virtual Machine to run. If JAVA_HOME is not defined, then you must explicitly specify the root of the Java installation on the -java option:

apjava ... -java=/opt/j2sdk1.4.1 -classpath ...

See apjava for more information.

Applying One Probe to Many Methods

You may wish to apply a probe to many different methods. There are several ways to do this with the .xmj deployment descriptor file, without changing the Java at all: wildcards, target lists, and using multiple .xmj files.

Wildcards

You can use a wildcard string as the target value in your .xmj file to accomplish this, as shown below:

<probe_deployment>
  <probe class="MyFirstProbe">
    <target value="*::*"/>
  </probe>
</probe_deployment>

Example 5-3. WildcardExample.xmj

Using WildcardExample.xmj as the deployment descriptor file will cause every method to have MyFirstProbe applied to it.

The class, the method, or both can be a wildcard. All of the following are valid:

  • Probe all methods in all classes, as shown above:
<target value="*::*"/>
  • Probe all methods in a given class:
<target value="Classname::*"/>
  • Probe all methods named "foo" in any class:
<target value="*::foo"/>

Lists

You can define a list of targets, and reference that list as the target of a probe, as shown in ListExample.xmj below. This deployment descriptor file shows how to construct a list. This example would apply MyFirstProbe to two methods, main() and calc_pi().

<probe_deployment>
  <target_list_definition name="ListExampleList">
    <list_target value="Pi::calc_pi"/>
    <list_target value="Pi::main"/>
  </target_list_definition >

  <probe class="MyFirstProbe">
    <target_list name="ListExampleList"/>
  </probe>
</probe_deployment>

Example 5-4. ListExample.xmj

Multiple XMJ Files

You can specify multiple XMJ files on the command line, or specify a directory from which all appropriate files (.ual, .xmj) are used. Often it is useful to set aside a single directory with all the Java probes and related XMJ files, add this to your CLASSPATH, and also specify this single directory on the apjava command line, for example:

cp MyFirstProbe.class *.xmj ~/JavaProbes/
CLASSPATH=$CLASSPATH:~/JavaProbes
apjava -u JavaProbesDir -java Pi

Note that if you use this directory approach, then any .xmj files you do not wish to use must be removed from the named directory.

Using Method IDs

If you ran the introductory example, and applied the single probe to a number of different target methods, you may have noticed that the output can be a bit hard to figure out, because all of the methods being probed are producing the same output. However, there is built-in support to identify different target methods. Each one has a unique method ID, and your ProbeMethod knows the ID of the target method upon which it is acting.

In addition, the class com.ocsystems.aprobe.SymbolTable contains a variety of utility functions for manipulating method IDs. The most immediately useful such is getPrintableMethodName(). It converts a method ID to a String containing the name of the method. The method ID itself is retrieved by the invoking the probe's own getMethodId() method, which is inherited from the class com.ocsystems.aprobe.ProbeMethod.

This next probe, Example2.java, uses com.ocsystems.aprobe.SymbolTable.getPrintableMethodName() and a probe's method ID to make very clear the program flow in the target:

import com.ocsystems.aprobe.*;

public class Example2 extends ProbeMethod {
  private String methodName = "(unknown)";

  private void printEvent( String event )
  {
    System.out.println(
      event   "  (in method "   methodName   ")" );
  }

  public boolean onEntry( Object[] parameters )
  {
    methodName =
      SymbolTable.getPrintableMethodName( getMethodId() );
    printEvent( "Method Entry" );
    return true;
  }

  public Object onExit( Object returnValue )
  {
    printEvent( "Method Exit" );
    return returnValue;
  }

  public void onExit() // no return value  {
    printEvent( "Method Exit" );
  }

  public void onExceptionExit( Throwable e )
  {
    printEvent( "Method Exit via exception" );
  }
}

Example 5-5. Example2.java

This example also shows that your probe class is just another class. You can define any of your own fields and methods in it, and use them as you would in any other class, just as we did here with methodName and printEvent().

Logging Data from Java

So far, we have sent all output from our probes to System.out. This mixes up the probe's output with the application's output, and makes post-runtime analysis more difficult. As you've seen in earlier chapters, Aprobe normally logs data at runtime to be examined later. You can use the same mechanism in your custom probes. This is used most easily through the class
com.ocsystems.aprobe.Logger.

The following probe uses the method log() in that class to record information which will then be displayed when the APD file is formatted and examined.

import com.ocsystems.aprobe.*;

public class Example3 extends ProbeMethod {
  public boolean onEntry( Object[] parameters )
  {
    Logger.log( "Hello world (from my probe)!" );
    return true;
  }

  public Object onExit( Object returnValue )
  {
    Logger.log( "Goodbye world (from my probe)!" );
    return returnValue;
  }

  public void onExit() // no return value  {
    Logger.log( "Goodbye world (from my probe)!" );
  }

  public void onExceptionExit( Throwable e )
  {
    Logger.log(
      "Goodbye world via exception (from my probe)!" );
  }
}

Example 5-6. Example3.java

The onLine() Method

The probes that we have seen so far perform actions only on entry to, and exit from, the target methods. Once can also probe individual source lines of a method if it has been compiled with debugging information (usually using "javac -g"). This is done using the method onLine(), as shown below.

import com.ocsystems.aprobe.*;
public class Example4 extends ProbeMethod {
  private String methodName = "(unknown)";

  private void printEvent( String event ) {
    System.out.println(
      event   "  (in method "   methodName   ")" );
  }

  public void onLine( int lineNumber ) {
    printEvent( "Line # "   lineNumber );
  }

  public boolean onEntry( Object[] parameters ){
    methodName =
      SymbolTable.getPrintableMethodName( getMethodId() );
    printEvent( "Method Entry" );
    return true;
  }

  public Object onExit( Object returnValue ) {
    printEvent( "Method Exit" );
    return returnValue;
  }

  public void onExit() {
    printEvent( "Method Exit" );
  }

  public void onExceptionExit( Throwable e ) {
    printEvent( "Method Exit via exception" );
  }
}

Example 5-7. Example4.java

Enabling Line Probes

If not applied selectively, line probes can seriously impact performance. For this reason, line probing is turned off by default.

Thus, in order for the onLine() method to be called, you must specify that your probe needs to access lines. This is done in the deployment descriptor (.xmj) file, using the attribute "lines" in the <probe> tag, as shown in Example4.xmj below. Valid values for the "lines" attribute are "TRUE" or "FALSE". If you do not specify a value, "FALSE" is assumed.

<probe_deployment>
  <probe class="Example4" lines="TRUE">
    <target value="Pi::main"/>
  </probe>
</probe_deployment>

Example 5-8. Example4.xmj

Advanced Java Probes

So far, all the examples have been, in certain ways, pretty simple and straightforward. Each probe is independent. Each applies to certain methods, and a new probe instance is created every time one of those methods is invoked. You've simply specified the name of the probe, and its targets, in the deployment descriptor, and Aprobe did the rest automatically.

Under the covers, however, what's going on is quite intricate. For each method being probed, there exists what we call a "trigger", which, essentially, works as a "factory" for probes. When the method is invoked, this trigger gets, well, triggered, and then, by default, creates an instance of the appropriate probe.

This structure allows enormously powerful customizations. Triggers can create probes only conditionally. Triggers can be enabled or disabled. Triggers can be removed entirely from association with a method, and new triggers can be created dynamically.

ProbeBeans

How does one use this power? With a "ProbeBean". What is a "ProbeBean"? It is a class, derived from com.ocsystems.aprobe.ProbeBean, that groups together related code, data, triggers, and probes. ProbeBeans are created and initialized when the Java application first starts, and they in turn create the triggers that create the probes. Every probe is created by a trigger, and every trigger belongs to a particular ProbeBean.

There are a couple of places where you would want to use a "ProbeBean" instead of the simpler method described earlier:

  • you can collect data on a per-thread basis; and
  • sophisticated probes can be structured more easily, e.g., the number of class files can be reduced for the probes and data can be more easily shared among probes.

In the examples we've seen so far, an automatically-created ProbeBean created triggers to invoke the probes you specified in your deployment descriptor file. The files MyFirstProbeBean.java and MyFirstProbeBean.xmj below show how you would manually create and deploy a ProbeBean for MyFirstProbe.java, shown at the start of this chapter.

<probe_deployment>
  <bean class="MyFirstProbeBean">
    <instrument_target>
      <target value="Pi::main"/>
    </instrument_target>
  </bean>
</probe_deployment>

Example 5-9. MyFirstProbeBean.xmj

In MyFirstProbeBean.xmj we use the tag <bean>, and the attribute "class" to specify which class is the ProbeBean to load. The <target> tag looks familiar, but what is <instrument_target>? Since the deployment descriptor no longer directly causes the creation of probes, we don't know which classes we need to add "hooks" to. The <instrument_target> tag indicates that Aprobe must add its hooks to the targets (or target lists) specified.

Note that <instrument_target> does not create any triggers or probes, the way a <probe> tag does. The creation of triggers and probes is left up to the ProbeBean itself, as shown in MyFirstProbeBean.java below.

The tag <instrument_target> also accepts the attribute "lines" just as "probe" does, so that line probes may be added.

import com.ocsystems.aprobe.*;
public class MyFirstProbeBean extends ProbeBean {
  public void onEntry() {
    int targetID = getTarget();

    new ProbeTargetTrigger( targetID )
    {
      public Probe createProbe()
      {
        return new MyFirstProbe();
      };
    };
  }
}

Example 5-10. MyFirstProbeBean.java

Let's look more closely at MyFirstProbeBean, which is as simple as it gets. When the deployment descriptor is read, Aprobe will create an instance of any ProbeBean specified with the <bean> tag, and invoke its onEntry() method. The ProbeBean needs to know what methods to apply itself to, so it can create the appropriate triggers. This is done with the method getTarget(), which returns an ID corresponding the group of methods specified within an <instrument_target> tag in the .xmj file. The ProbeBean then creates a new trigger, derived from ProbeTargetTrigger and applied to that target, which in turn creates probes of the class MyFirstProbe. The fact that our new trigger is a ProbeTargetTrigger causes it to automatically apply itself to all the methods represented by the target ID, and its createProbe() method gets invoked whenever any of those methods does.

That's probably more than you wanted to know, but this allows you to explore the power of ProbeBeans further in the next example.

Parameterizing Probes

Once you've compiled your ProbeBean and Probe Java code, you can still greatly vary their behavior through the deployment descriptor. The obvious way we've already seen is by changing the target methods to which your probes apply. You can also, however, pass arbitrary other parameters to your Probe or ProbeBean via the deployment descriptor, using the <parameter> tag. This pair of examples will show how to do that, for a ProbeBean or for a stand-alone Probe.

<probe_deployment>
  <bean class="Example6ABean">
    <parameter name="ReallyHaveProbes" value="true"/>
    <instrument_target>
      <target value="Pi::main"/>
    </instrument_target>
  </bean>
</probe_deployment>

Example 5-11. Example6a.xmj

As you can see, the <parameter> tag has two attributes, "name" and "value"; both are required. In Example6ABean.java below, we'll see how the ProbeBean can reference and use the parameter we've defined.

import com.ocsystems.aprobe.*;
 public class Example6ABean extends ProbeBean {
  public void onEntry()
  {
    String reallyHaveProbesString =
      getParameter( "ReallyHaveProbes" );
    boolean reallyHaveProbes =
      ( Boolean.valueOf( reallyHaveProbesString )
        ).booleanValue();

    if( reallyHaveProbes )
    {
      int target = getTarget();

      new ProbeTargetTrigger( target )
      {
        public Probe createProbe()
        {
          return new Example4();
        };
      };
    }
    else
    {
      System.out.println(
      "Probes disabled by parameter in deployment!" );
    }
  }
}

Example 5-12. Example6ABean.java

The ProbeBean retrieves the value of a parameter, as a String, by name, also a String. This name passed to getParameter() must exactly match the attribute "name" in the <parameter> tag.

This method of parameterization via the deployment file may also be used without ProbeBeans, as shown on the following pages:

<probe_deployment>
  <probe class="Example6BProbe">
  <parameter name="BeVerbose" value="true"/>
  <target value="Pi::main"/>
  </probe>
</probe_deployment>

Example 5-13. Example6b.xmj

The parameter is specified the same way here, but nested within the "probe" tag rather than the <bean> tag. The mechanism by which a Probe retrieves the parameter is shown in [#MARKER-9-959 Example6BProbe.java].

Every probe has a field which references the trigger that created it, and every trigger belongs to a bean. Here, even though we did not create a custom bean explicitly, a default bean was created for us by Aprobe. It is through this bean that a probe accesses its parameters. Here, the probe uses the value of the parameter to decide how much information to display.

Note that although both of these examples used the parameter as a boolean value, you are not restricted to that. Parameters could represent names, numerical values, etc. You may have multiple parameters for a single bean or probe, as long as each parameter has a unique name.

import com.ocsystems.aprobe.*;

public class Example6BProbe extends ProbeMethod {
  boolean beVerbose;

  public boolean onEntry( Object[] parameters )
  {
    System.out.println( "Entering a method." );

    String beVerboseString =
      trigger.bean.getParameter( "BeVerbose" );
    beVerbose =
      ( Boolean.valueOf( beVerboseString )
      ).booleanValue();

    if( beVerbose )
    {
      for( int i=parameters.length-1; i>=0; i-- )
      {
        System.out.println(
          "Parameter # "   i   " is "   parameters[i] );
      }
    }

    return true;
  }

  public Object onExit( Object returnValue )
  {
    System.out.println( "Exiting a method." );

    if( beVerbose )
    {
      System.out.println(
        "Method's return value is:  "   returnValue );
    }

    return returnValue;
  }

  // exercise for reader:   
  // provide other onExit, onExceptionExit methods
}

Example 5-14. Example6BProbe.java

Working with Threads

In addition to methods, Aprobe can also track threads. In fact, unless you specify otherwise, all your method probes are actually created within the context of a default thread probe! But you can also create your own thread probes to get more information about your multi-threaded applications. Thread probes are created by a thread trigger every time the JVM creates a new thread. The thread trigger is typically created via a ProbeBean.

You can use thread probes in order to keep track of data on a per-thread basis. Like method probes, thread probes have an onEntry() method. All thread probes derive from the class com.ocsystems.aprobe.ProbeThread.

A thread probe is created by a ProbeThreadTrigger. Our ProbeBean creates a new such ProbeThreadTrigger that will create instances of our ProbeThread class.

Example7Bean.java, on the next page, tracks the beginning of all threads, and counts calls to methods within each of those threads. The Example7.xmj deployment description file applies this thread tracking to all methods in the Pi class.

<probe_deployment>
  <bean class="Example7Bean">
    <instrument_target>
      <target value="Pi::*"/>
    </instrument_target>
  </bean>
</probe_deployment>

Example 5-15. Example7.xmj

import com.ocsystems.aprobe.*;

public class Example7Bean extends ProbeBean {
  int target;
    static int numberOfThreads = 0;

  class Example7ThreadProbe extends ProbeThread   {
    int threadNumber;
    int callsInThisThread;


    class Example7MethodProbe extends ProbeMethod     {
      public boolean onEntry( Object[] p )
      {
        callsInThisThread  ;
        System.out.println(
          "Call # "   callsInThisThread
          " in thread # "   threadNumber );
        return true;
      }
    }


    public void onEntry() // thread entry    {
      threadNumber =   numberOfThreads;
      System.out.println(
        "Hello from start of thread # "   threadNumber );

      new ProbeTargetTrigger( target, this )
      {
        public Probe createProbe()
        {
          return new Example7MethodProbe();
        }
      };
    }
  } // end ProbeThread 

  public void onEntry() // bean entry   {
    target = getTarget();

    new ProbeThreadTrigger()
    {
      public Probe createProbe()
      {
        return new Example7ThreadProbe();
      }
    };
  }
}

Example 5-16. Example7Bean.java

Dynamic Probe Deactivation

Triggers include the ability to be activated and deactivated dynamically. [#MARKER-9-965 Example8.java] on the next page shows a probe that will only be called once per thread, because the first time it is invoked, it disables the trigger that created it.

Of course, you don't have to restrict your logic to turning the trigger off immediately. You could set whatever conditions you like, such as after 100 iterations, or when a buffer fills up.

import com.ocsystems.aprobe.*;

class MyMethodProbe extends ProbeMethod {
  public boolean onEntry( Object[] p )
  {
    System.out.println(
      "You should only see this once per thread!" );
    getTrigger().disableProbe();
    return true;
  }
}

public class Example8 extends ProbeBean {
  public void onEntry() // bean entry  {
    int target = getTarget();
    new ProbeTargetTrigger( target )
    {
      public Probe createProbe()
      {
        return new MyMethodProbe();
      };
    };
  }
}

Example 5-17. Example8.java


Next Previous Top Contents Index

Copyright 2006-2017 OC Systems, Inc.