Let's start writing a simple analyzer that scans through the data structures that represent all the thread pools in a WAS process, and prints key information about each pool:
public class WASThreadPoolsSample1 extends WASAnalyzerBase implements IReport { public IAnalysisReport produceReport() {
IAnalysisReport out = allocateReport(null);
ObjectWrapperCollection pools = ObjectWrapperCollection.getObjectInstances(getContext(),
"com/ibm/ws/util/ThreadPool");
for (int index = 0; index < pools.size(); index++) {
ObjectWrapper pool = (ObjectWrapper) pools.get(index);
pool.printValueAtPath(out, "Pool name", "name");
pool.printValueAtPath(out, "Min size", "minimumPoolSize_");
pool.printValueAtPath(out, "Max size", "maximumPoolSize_");
pool.printValueAtPath(out, "Current size", "poolSize_");
pool.printValueAtPath(out, "List of Threads", "threads_");
out.printLiteral("");
}
return out;
}
}
Here is a fragment of the output from this analyzer, run on a small WAS process dump:
... Pool name: "WLMMonitorSleeper"
Min size: 0
Max size: 10
Current size: 1
List of Threads: java/util/HashMap@0x01167330 Pool name: "SoapConnectorThreadPool"
Min size: 3
Max size: 5
Current size: 2
List of Threads: java/util/HashMap@0x01F910C0 Pool name: "WebContainer"
Min size: 10
Max size: 50
Current size: 0
List of Threads: java/util/HashMap@0x02149D50 ...
Let's examine the key aspects of this analyzer:
The analyzer is a simple Java class, defined as in the previous example of the
Hello World Analyzer. It's main purpose is to generate a report, so it contains a
produceReport() method that will be invoked by the tool framework when that analyzer is requested, and that returns a report containing the desired information.
Unlike the
Hello World Analyzer, this analyzer needs to do some work inside its
produceReport() method to actually find the information that it wants to produce.
We happen to know that, in WAS, each thread pool is represented by a data structure of class
com.ibm.ws.util.ThreadPool. So we need some code to look for all instances of that class present inside the dump:
ObjectWrapperCollection pools = ObjectWrapperCollection.getObjectInstances(getContext(), "com/ibm/ws/util/ThreadPool");
ObjectWrapperCollection is a special analyzer, part of the library of standard analyzers shipped with the Dump Analyzer tool, whose purpose is to find collections of objects: all the instances of a given class, or all the subclasses of a given class, etc. Here, we use the static factory method
ObjectWrapperCollection.getObjectInstances(), which returns an instance of the
ObjectWrapperCollection analyzer that represents the desired list of instances. The parameters are as follows:
- getContext() is needed internally to give the analyzer some context information, e.g. a reference to the dump being analyzed. The details of this are not important for this tutorial, and can be found in the full Javadoc for the Dump Analyzer tool. Here, suffice it to say that we invoke the getContext() method implemented by the WASAnalyzerBase class for our analyzer, and pass that same context value to initialize the new ObjectWrapperCollection analyzer that we want to create. We will find this same pattern everywhere: whenever we invoke a particular analyzer as part of the operation of some other analyzer, we must pass it a context, which is typically the same context that is associated with the analyzer that is making the invocation.
- com/ibm/ws/util/ThreadPool is the name of the class for which we want to find all the instances. Note that we use the internal Java representation of class names, with a "/" as the separator character rather than a ".".
ObjectWrapperCollection is an analyzer, that happens to implement the
java.util.Collection from the standard Java Collections framework. So we can use that interface to enumerate and extract the items from the collection, i.e. each of the instances of
com/ibm/ws/util/ThreadPool that are found in the dump.
Each item in the collection is represented by another standard analyzer from the library, of type
ObjectWrapper. Each instance of the
ObjectWrapper analyzer represents one particular object instance from the dump being analyzed.
So we iterate over these instances simply as follows: (we could also have used an Iterator over the collection)
for (int index = 0; index < pools.size(); index++) {
ObjectWrapper pool = (ObjectWrapper) pools.get(index);
// ... do whatever we need to with each "pool" object
}
So now, we have a local variable
pool that references an
ObjectWrapper analyzer that represents one particular instance of a
com/ibm/ws/util/ThreadPool object from the dump being analyzed. We want to print the contents of that object, i.e. print the value of its various fields:
pool.printValueAtPath(out, "Pool name", "name");
pool.printValueAtPath(out, "Min size", "minimumPoolSize_");
pool.printValueAtPath(out, "Max size", "maximumPoolSize_");
pool.printValueAtPath(out, "Current size", "poolSize_");
pool.printValueAtPath(out, "List of Threads", "threads_");
This code is pretty self-explanatory. The
ObjectWrapper analyzer exports a method
printValueAtPath(), that looks for a particular field in the target object being represented by this
ObjectWrapper and prints the value of that field. The arguments are as follows:
- out: a reference to the report object in which to write the value of the field
- a label to describe this field in the report
- a string that must match exactly the name of the field in the target object.
In this case, we are printing five fields named "name", "minimumPoolSize_", "maximumPoolSize_", "poolSize_" and "threads_"). We picked those fields by looking at the definition of
com/ibm/ws/util/ThreadPool, either from the source code or by examining it with another analyzer or command in the Dump Analyzer tool itself, and we decided that these five fields were the ones that we were particularly interested in for this new analyzer.
If the value of a given field is a String or one of the primitive Java types (int, boolean, etc.) or Integer, Boolean, etc., that value is printed normally.
If the value of a given field is an object reference, the
printValueAtPath() method prints a standard representation for all object references, like
java/util/HashMap@0x01167330 in the example above. This means that this particular field contains a reference to a
HashMap object that happens to be at the address
0x01167330 in the dump being analyzed. In the following sections of this tutorial, we will see how we can follow that reference to see the fields inside that referenced object itself, and other that it in turn references, etc.
If the name of the field specified in
printValueAtPath() does not correspond to a valid field defined in the target object, then this method prints an error indication like
[<analyzererror>] in lieu of the expected value of the field.
Notes:
- There are several other methods and variations similar to printValueAtPath(). We will encounter several of them in other sections of this tutorials. For the complete list, refer to the Javadoc for the ObjectWrapper analyzer.
- What if I want to print the address of the target object itself (e.g. com/ibm/ws/util/ThreadPool@0x022DE1F8) instead of the value of one of its fields? I can do that by invoking printValueAtPath() with a null string ("") for the field name.
The code in this analyzer iterates over all the thread pool objects, and prints some fields for each object. We need a way to mark in the report when the information for one thread pool object is ended and we start the information for the next object.
In this simplest example, we use the construct
to insert a blank line in the report. In the next section of this tutorial, we will learn some more powerful ways to control the format the report.
Writing and Formatting Reports