So far (see
previous topic) we have managed to print a list of the threads that are contained in a thread pool:
List of Threads: java/util/HashMap@0x036F18B8 (4 entries)
Thread: com/ibm/ws/util/ThreadPool$Worker@0x012F3158 com/ibm/ws/util/ThreadPool$Worker@0x012F3158
Thread: com/ibm/ws/util/ThreadPool$Worker@0x011ACE18 com/ibm/ws/util/ThreadPool$Worker@0x011ACE18
Thread: com/ibm/ws/util/ThreadPool$Worker@0x012CE728 com/ibm/ws/util/ThreadPool$Worker@0x012CE728
Thread: com/ibm/ws/util/ThreadPool$Worker@0x011819F0 com/ibm/ws/util/ThreadPool$Worker@0x011819F0
But that is still not sufficient. We don't want to see a simple reference to the
com/ibm/ws/util/ThreadPool$Worker object associated with each thread. We want to see some information about that thread. The way to do this is to write a new analyzer module: a
Custom Wrapper specifically designed to extract and print information about an instance of
com/ibm/ws/util/ThreadPool$Worker.
Custom Wrappers are similar to the generic
ObjectWrapper and standard J2SE wrappers (
HashMapWrapper, etc.) that are part of the library of standard analyzers shipped with the Dump Analyzer tool. But Custom Wrappers correspond to types of objects that were not anticipated when the standard library was put together. We must write a new one ourselves.
Here is the code for the Custom Wrapper for
com/ibm/ws/util/ThreadPool$Worker:
public class ThreadPoolWorkerWrapper extends ObjectWrapper implements IReport, IWrapper { public void setParent(Object parent) {
super.setParent(parent);
if (! this.isInstanceOf("com/ibm/ws/util/ThreadPool$Worker")) {
throw new IllegalArgumentException("Attempting to initialize a ThreadPoolWorkerWrapper with an object of the wrong type " + IdentityStringHelper.buildIdentString(parent));
}
} public IAnalysisReport produceReport() {
IAnalysisReport out = allocateReport(null);
this.printValueAtPath(out, "Thread name", "name");
this.printValueAtPath(out, "Thread number", "threadNumber");
this.printValueAtPath(out, "Priority", "priority");
this.printValueAtPath(out, "Start time", "startTime");
return out;
}
}
and the code in the main analyzer that invokes this Custom Wrapper when listing the threads:
pool.printCollectionValueReportsAtPath(out2, "List of Threads", "Thread", "threads_", HashMapWrapper.class.getName(), ThreadPoolWorkerWrapper.class.getName());
The output looks as follows:
List of Threads: java/util/HashMap@0x036F18B8 (4 entries)
Thread: com/ibm/ws/util/ThreadPool$Worker@0x012F3158 com/ibm/ws/util/ThreadPool$Worker@0x012F3158
Thread name: "TCPChannel.DCS : 3"
Thread number: "00000016"
Priority: 5
Start time: 0
Thread: com/ibm/ws/util/ThreadPool$Worker@0x011ACE18 com/ibm/ws/util/ThreadPool$Worker@0x011ACE18
Thread name: "TCPChannel.DCS : 1"
Thread number: "00000012"
Priority: 5
Start time: 0
Thread: com/ibm/ws/util/ThreadPool$Worker@0x012CE728 com/ibm/ws/util/ThreadPool$Worker@0x012CE728
Thread name: "TCPChannel.DCS : 2"
Thread number: "00000015"
Priority: 5
Start time: 0
Thread: com/ibm/ws/util/ThreadPool$Worker@0x011819F0 com/ibm/ws/util/ThreadPool$Worker@0x011819F0
Thread name: "TCPChannel.DCS : 0"
Thread number: "00000011"
Priority: 5
Start time: 0
The key points are as follows:
The structure of
ThreadPoolWorkerWrapper is similar to that of other analyzers, such as the main
WASThreadPoolsSample analyzer that we've been developing in this tutorial. Except that instead of extending
WASAnalyzerBase (a generic common base class for WebSphere Application Server analyzers), it extends
ObjectWrapper.
ThreadPoolWorkerWrapper is effectively a specialized version of
ObjectWrapper, that offers all the generic functionality of
ObjectWrapper that is suitable for any type of object, plus some specific functionality tailored to interpreting and reporting on
com/ibm/ws/util/ThreadPool$Worker objects.
Unlike
WASThreadPoolsSample which is a global analyzer that operates in the scope of the entire dump,
ThreadPoolWorkerWrapper represents one particular instance of one particular object extracted from that dump. So it needs to be initialized to specify which particular object instance it represents. This is the purpose of the
IWrapper interface, which declares the method
setParent().
When the tool framework allocates a new instance of
ThreadPoolWorkerWrapper and detects that this analyzer implements the
IWrapper interface, the framework invokes the analyzer's
setParent() method, passing it a reference to the
parent object, i.e. the low-level DTFJ object representation that corresponds to the object in the original dump being analyzed.
Our
ThreadPoolWorkerWrapper implementation does not need to do much in its
setParent() method, because we rely on our superclass
ObjectWrapper to do all the hard work of keeping track of the
parent object and mediating access to it. So we forward the invocation to
ObjectWrapper's own
setParent() method.
In passing, though, it is wise to check that we are being initialized with a
parent of the correct type (
com/ibm/ws/util/ThreadPool$Worker). We do this with the help of the
isInstanceOf() method, which is itself implemented by our
ObjectWrapper superclass. Note that
ObjectWrapper itself implements
isInstanceOf(), but has no reason to use it to check anything, because it is generic and can deal with any type of parent object. It is only the specialized
ThreadPoolWorkerWrapper that needs to worry about being initialized with the correct type of parent object.
Notes:
- Note that the declaration of implementing IWrapper here is redundant, since we extend ObjectWrapper which itself implements IWrapper. But we left it in for clarity and consistency.
ThreadPoolWorkerWrapper overrides the
produceReport() method from its
ObjectWrapper superclass. We want to produce a customized report for
com/ibm/ws/util/ThreadPool$Worker objects, and not the generic report from
ObjectWrapper which prints all the fields from the object indiscriminately.
The implementation of
produceReport() can use all the facilities of the
ObjectWrapper superclass to access and print specific fields. In this example, we choose to simply print four key fields from a
com/ibm/ws/util/ThreadPool$Worker, without any fancy formatting or grouping.
Back in the
WASThreadPoolsSample analyzer, instead of using the
printCollectionEntriesAtPath() method to print all the entries in the HashMap that contains the list of threads, we now use the
printCollectionValueReportsAtPath() method. This method differs from the former in that it takes an additional parameter (the name of the wrapper analyzer to use to print individual entries -- in this case
ThreadPoolWorkerWrapper) and that instead of printing a simple object reference for each entry in the HashMap, it instantiates a
ThreadPoolWorkerWrapper for each entry and invokes its
produceReport() method.
As noted earlier in a previous topic, the output from this code still prints the object reference to each
com/ibm/ws/util/ThreadPool$Worker twice for each entry in the list. This breaks down as follows:
- We first print an object reference for the key in each entry in the HashMap, which is the com/ibm/ws/util/ThreadPool$Worker object
- Next we print the title of the custom report for the value of each entry in the HashMap, which is also the com/ibm/ws/util/ThreadPool$Worker object
- Next we print the body of the custom report associated with the com/ibm/ws/util/ThreadPool$Worker object, as implemented by the ThreadPoolWorkerWrapper
To avoid this double printing of the object reference, we have two alternatives:
- We could implement a getIdentityString() method in ThreadPoolWorkerWrapper. If that method is present, instead of printing a standard object reference as the title of the custom report in step (2) above, the tool would print whatever the getIdentityString() method returns for each ThreadPoolWorkerWrapper. That method could return some other information more useful than a standard object reference; for example the thread name.
- Or alternatively, we could use printCollectionMapEntryReportsAtPath() instead of printCollectionValueReportsAtPath(). This would require a modification to ThreadPoolWorkerWrapper to become a MapEntry wrapper instead of a simple object wrapper. A MapEntry wrapper is initialized with a parent that is a java.util.Map$Entry object that contains both the key and the value of the entry to be printed, instead of being initialized with just the value. This type of wrapper has full freedom (and full responsibility) to print all the information related to each entry (i.e. it is responsible for all three printing steps above, instead of letting the tool framework drive these steps).
We won't go into further detail of these alternatives here. Please refer to the API documentation for further information.
Extracting Values from Data Structures, Exception Handling