Express (Distributed operating systems), v8.0 > Troubleshoot and support > Analyzing application server Java system dumps with the IBM Monitoring and Diagnostic Tools for Java - Dump Analyzer > Write Dump Analyzer modules for WAS diagnostics - Tutorial


Tutorial: Writing Dump Analyzer modules for WAS diagnostics


Extract Values from Data Structures; Exception Handling

In all the examples so far, we've only been printing the values of various fields of various data structures, but we have not attempted to perform any computation directly with these values. Let us extend our sample analyzer to compute a sum of the maximum sizes and current sizes of all the thread pools that we encounter:

public IAnalysisReport produceReport() {
/*NEW*/    int numberOfAllocatedThreads = 0;
/*NEW*/    int numberOfPossibleThreads = 0;

IAnalysisReport out = allocateReport(null); ObjectWrapperCollection pools = ObjectWrapperCollection.getObjectInstances(getContext(), "com/ibm/ws/util/ThreadPool"); out.printField("Number of thread pools", pools.size()); for (int index = 0; index < pools.size(); index++) { ObjectWrapper pool = (ObjectWrapper) pools.get(index); /*NEW*/ try { /*NEW*/ int curSize; /*NEW*/ int maxSize; IAnalysisReport out2 = pool.startPrintGroup(out, "Thread Pool", pool.getParent(), ObjectWrapper.MODE_NONE, IAnalysisReport.TAG_STANDOUT); { pool.printValueAtPath(out2, "Pool name", "name"); out2.startFormatSection(IAnalysisReport.FORMAT_COLUMNS, "1 1"); { pool.printValueAtPath(out2, "Min size", "minimumPoolSize_"); /*NEW*/ maxSize = pool.printValueAtPath(out2, "Max size", "maximumPoolSize_").getIntegerValue(); } out2.endSection(); /*NEW*/ curSize = pool.printValueAtPath(out2, "Current size", "poolSize_").getIntegerValue(); pool.printCollectionValueReportsAtPath(out2, "List of Threads", "Thread", "threads_", HashMapWrapper.class.getName(), ThreadPoolWorkerWrapper.class.getName()); } pool.endPrintGroup(); /*NEW*/ numberOfAllocatedThreads += curSize; /*NEW*/ numberOfPossibleThreads += maxSize; /*NEW*/ } catch (Exception e) { /*NEW*/ logExceptionMessage("Error extracting information for pool", e, out); } }

/*NEW*/ out.printField("Current number of pool threads", numberOfAllocatedThreads); /*NEW*/ out.printField("Maximum possible number of pool threads", numberOfPossibleThreads);

return out; }

Here are the key new elements in this code:


New variables to accumulate the sums

We define two new variables numberOfAllocatedThreads and numberOfPossibleThreads, to hold the sum of all the current sizes of all the pools and all the maximum sizes of all the pools respectively. As we scan through each pool in the for loop while generating the report, we add-up the values coming from each pool into these variables.

At the end of the scan (and the end of the report), we print the final values. We use the printField() method on the report object, that was already mentioned earlier in this tutorial. This method prints an arbitrary value in the report, with a label, independent of how that value might have been extracted from some data structure, or computed, or obtained in any other way within the analyzer.



Return value from printValueAtPath()

As before, we use ObjectWrapper.printValueAtPath() and its variants to print the values of the various fields extracted from the ThreadPool data structures. But in addition to printing the values, printValueAtPath() also returns the value of the field. We've been ignoring that return value so far, but now we will use it to obtain the information that we need to compute the total pool sizes.

The return value from printValueAtPath() is a new ObjectWrapper instance, that represents the value stored in the field being read in the original dump. That ObjectWrapper could represent a value of any type: maybe it is a reference to some other object from the original dump, or maybe it is a primitive value (e.g. an integer, a short, a boolean, etc.).

In this example, since we are extracting the value of the maximumPoolSize_ and poolSize_ fields, we know that the type of the value in question is an integer. We use the method getIntegerValue() on the ObjectWrapper instance that contains the value in question, to convert it to its original integer value.

ObjectWrapper.getIntegerValue() is one of a set of utility methods exported by the ObjectWrapper analyzer. Since an ObjectWrapper could represent a value of many different types, there is one such method to convert the value of that ObjectWrapper to each of these original types (e.g. getIntegerValue(), getShortValue(), getBooleanValue(), getStringValue(), getJavaObjectValue(), etc.). For any specific instance of an ObjectWrapper, only one of these methods will return a valid value, corresponding to the type represented by this particular ObjectWrapper instance. All other methods will throw an DTFJException, signifying that this particular ObjectWrapper instance is not compatible with this particular type.


Handle exceptions

In this code, we also must deal explicitly with errors that might possibly occur while extracting the values.

In previous examples, where we used printValueAtPath() only to print the value in the report, this was not necessary, because printValueAtPath() handled all possible errors internally: if a given value could not be found (for example because the named field does not exist, or because some object reference is null), printValueAtPath() simply inserts an error annotation in the report at the spot where it would otherwise have printed the desired value.

But now that we are manipulating the values explicitly in our analyzer code, we must account for the fact that some value in question might not be there. There are actually two potential errors that might occur while executing this code:

We handle both of these potential errors with a single try/catch block that catches any type of exception. This is a common pattern in many analyzers. Generally, we'd like our analyzers to do the best job that they can, and produce a report that is as complete as possible, even if some portions of that report cannot be generated for various reasons. Therefore, it is a good idea to catch generic exceptions at fine granularity throughout the code of the analyzer, report the problem, and move on to the rest of the function of that analyzer.

If instead we had used a single try/catch block around the entire body of the produceReport() method, any exception that occurs while scanning any one of the potentially many ThreadPool data structures, would have caused the entire analysis to abort right on the spot.


Reporting the error / exception

In the exception handler, we use the construct

logExceptionMessage("Error extracting information for pool", e, out);

This is the typical construct for reporting any kind of error that occurs during the analysis. It takes as parameters an error message, an exception (all errors during analysis are normally detected through some sort of exception), and an optional reference to a report.

When logExceptionMessage() is invoked, it performs the following functions:


Alternative function: getValueAtPath()

Note that using the return value from ObjectWrapper.printValueAtPath(), as we've shown above, is actually a short-cut to keep the analyzer code terse in the common case when we want to both print a value in a report and use it as input for some computation in the analyzer.

If we only wish to obtain the value from a given field of a given data structure (represented by an ObjectWrapper), without printing it in a report, we should use the the method ObjectWrapper.getValueAtPath(). This method returns the desired value as a new ObjectWrapper instance, just like the printValueAtPath() method. But unlike printValueAtPath() which returns null if an error occurred (e.g. if the field name is not found), getValueAtPath() throws a DTFJException in case of error. This is actually more accurate than returning null, because:


Next Topic


Invoke Another Analyzer