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:
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.
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 that is 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.
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:
- if printValueAtPath() is unable to extract and return the desired value (e.g. named field does not exist, ...), it returns null instead of an ObjectWrapper instance representing the desired value. In this case, the subsequent call to getIntegerValue() on that null ObjectWrapper will trigger a NullPointerException
- if printValueAtPath() was able to extract a value from the specified field and return a valid ObjectWrapper, but that field (and that ObjectWrapper) does not contain an integer. In that case, the subsequent call to getIntegerValue() will trigger a DTFJException.
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.
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:
- it classifies the error according to the type of exception, which corresponds to various severities (e.g. an AnalyzerException is more serious than a DTFJ CorruptDataException exception, which may occur normally in small numbers even in the best formed dumps)
- it adds the error message to a summary or count of all errors encountered during the analysis, to be reported by the Dump Analyzer tool at the end of the entire analysis run
- if the report reference is not null, it inserts the error message in the report, at the current location in that report
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:
- the DTFJException contains specific information about the type of error encountered
- when returning null, we cannot tell if an error occured, or if the value of the field in question was actually null
Invoking Another Analyzer