Profiling Real-life Applications Using the NetBeans Profiler

Gregg Sporar, Sang Shin


 

NetBeans IDE 6.0 includes a powerful profiling tool that can provide important information about the runtime behavior of your application. The NetBeans profiling tools easily enable you to monitor thread states, CPU performance, and memory usage of your application from within the IDE, and imposes relatively low overhead.

In this hands-on lab, you are going to analyze performance data collected by using NetBeans profiler on RollerWeb application as the first exercise. You are going to profile iText application in the second exercise.


Expected duration: 75 minutes (excluding homework)




Software Needed

Before you begin, you need to install the following software on your computer. 


Operating Systems/platforms you can use

Change Log


Things to do (by Authors)


Lab Exercises


Exercise 0: Installation and Configuration


You will install MySQL as a database to RollerWeb application you are going to build, run and profile in Exercise 1.

(1.1) Start MySQL database server and create a database

1. Install MySQL database server (if you have not done so yet.)
2. Start the MySQL database server.  (The steps for this vary depending on your operating system. For example, on Windows Vista, you can use Start > Control Panel > Administrative Tools > Services. For additional information, refer to the documentation on determing MySQL server version..)


Figure-0.11: Start MySQL database server

2. Run the script to create RollerDB database.


Figure-0.12: Run the SQL script

drop database rollerDB;
create database rollerDB;
grant all on rollerDB.* to roller@'%' identified by 'roller';
grant all on rollerDB.* to roller@localhost identified by 'roller';

3. Populate database tables.

The MySQL server stores database tables in files. Locate the data subdirectory of your MySQL installation.   There should be a new subdirectory named data/rollerDB or data/rollerdb.


Figure-0.14: Shutdown MySQL database server


Figure-0.15: rollerdb directory


Figure-0.16: Copy database files

5. Start MySQL database server again.


Figure-0.17: Start MySQL database server

6.  Make a database connection.


Figure-0.18: New Connection


Figure-0.19: New Database Connection


Figure-0.20: Close diaglog box

7. Display tables from rollerdb database.


Figure-0.21: Tables of the rollerdb database

8. Start GlassFish app. server.


Figure-0.22: Start GlassFish app. server

9. Add MySQL client side driver to the GlassFish.


Figure-0.23: Properties of GlassFish V2


Figure-0.24: lib directory of the GlassFish V2


Figure-0.25: MySQL


Figure-0.26: Location where MySQL client driver is located


Figure-0.27: Copy MySQL client driver to the lib directory of GlassFish v2

10. Create Connection Tool..

asadmin create-jdbc-connection-pool --user admin --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource --steadypoolsize 8 --maxpoolsize 32 --property port=3306:databaseName=rollerdb:serverName=localhost:password=roller:user=roller --restype javax.sql.DataSource rollerpool  
Code-0.28: Create Connection Pool


Figure-0.29: Create Connection Pool

11. Create JDBC resource.
asadmin create-jdbc-resource --user admin --connectionpoolid rollerpool jdbc/rollerdb
Code-0.30: Create JDBC resource


Figure-0.31: CreateJDBC resource

12. Restart GlassFish V2 app server.


Figure-0.32:Restart GlassFish app server


                                                                                                                       return to top of the exercise


Exercise 1:  Profiling "Roller" application

This exercise is based on an actual usage of the NetBeans profiler to find a performance problem in the Roller open source blog engine. It was originally presented by Jaroslav (Jarda) Bachor�k during a technical session at JavaOne 2007.

While experimenting with Roller, Jarda noticed that the process of saving a new blog entry seemed to take longer than it really should. Using the NetBeans profiler's ability to select a root method for performance profiling, Jarda was able to quickly and easily locate some inefficient code in the Roller source.

It should be noted that this exercise uses the source code for Roller v2.3, which was current at the time that Jarda discovered the performance problem. The current version of Roller no longer has the performance problem that is described here.

<Learning points>

<Tasks to be performed>

  1. Profile "RollerWeb" sample application
  2. Analyize profiling result
  3. Run automated test with JMeter
  4. Analyze calling patterns - identify poorly designed codes
  5. Correct the performance problem

(1.1) Profile "RollerWeb" sample application


0. Make sure MySQL is running as described in Exercise 0 above.
1. Open the project.

Figure-1.11: Open RollerWeb project

2. Start profiling.

Figure-1.12: Start Profiling

Figure-1.13: Select JDK

Figure-1.14: Enable profiling
3. Create custom profiling configuration.

Figure-1.15: Create custom profiling configuration

Figure-1.16: Give a name

4. Define a root method for profiling.
<Learning point: CPU profiling> CPU profiling is done with instrumentation, which means the profiler inserts bytecode on the fly into your application. This adds overhead, so the more instrumentation is done, the more overhead is imposed. By choosing to profile just part of the application you reduce the overhead imposed by the profiler.  The portions of your application that have instrumentation will run more slowly, but the rest of your application will run at full speed.


Figure-1.18: Define root method

<Learning point: Root method> When you are profiling only part of your application, you must select at least one root method. A root method is the method where you want the profiler to start doing instrumentation. The profiler will examine the call graph to figure out which methods are called by the root method and will instrument them as well. This process is done recursively until all methods that are reachable from the root method have been instrumented.

Only the methods that are reachable from the root method are instrumented, and the instrumentation is only used when those methods are invoked from a root method. This decreases the instrumentation overhead and saves you the trouble of trying to create combinations of filters in order to limit instrumentation to only the necessary methods.


Figure-1.19: Specify root methods
Note: Jarda examined the source for Roller enough to figure out that WeblogEntry.jsp was the entry point to the "save a new blog entry" logic.


Figure-1.20: Select root method

Figure-1.21: Close dialog box

5. Specify Filter mode and Run.
<Learning point: Filter> The Filter setting allows us to optionally specify one or more additional filters in order to limit the amount of instrumentation overhead. By profiling only the classes that are defined in our project, we will reduce the overhead dramatically.


Figure-1.22: Start the profiling

Trouble-shooting:  If you see the following error condition, it is likely due to the fact that MySQL database server is not running or it is not properly configured as described above.  Start the MySQL database server if it has not been started or follow the instruction again for configuring MySQL database with the GlassFish V2.


Figure-1.23: Possible error when MySQL database server is not running

6. Do the login.

Figure-1.24: Click Login

Figure-1.25: Enter Username and Password

Figure-1.26: User operations

                                                                                                                       return to top of the exercise


(1.2) Analyse profiling result


1. Take the snapshot of the profiling.
<Learning point: Call Tree> The Call Tree tab displays the CPU results organized by threads and then by methods called on that thread, with the root method at the top. Since a JSP was selected as the root method, the profiler lists the org.apache.jsp.weblog.WeblogEdit_jsp._jspService() method as the root method.


Figure-1.27: Take Snapshot

2. Display Hot Spots view of the snapshot.
<Learning point: Hot Spots> The Hot Spots tab displays the CPU results organized by Self Time. Self time is the amount of time spent executing a method's own instructions - any time that is spent in instrumented methods that it calls is not part of that method's Self Time. The methods with the highest values for Self Time are taking up the most CPU time and are therefore referred to as "hot spots."

Note that time spent in called methods that are not instrumented is reported as part of the Self time for the calling method. This has an important implication for this profiling session; since only the application's classes are being instrumented, a large Self Time value does not necessarily indicate that a method's code is taking up all that time - it could be that the method calls long-running methods that are not part of the source of the application.


Figure-1.28: Hot Spots view of the snapshot

3. Display the Combined view of the snapshot.
<Learning point: Combined view> The Combined tab displays both the Call Tree and the Hot Spots in one window. Click with the mouse on a method in the Hot Spots list and the Call Tree will be expanded to show you where in the calling sequence that method was invoked. This makes it easy to see the connection between the root method(s) you selected and the methods that are consuming the most CPU time.

<Learning point: Need for excluding warmup time (called rampup time sometimes) for accurate profiling> In the example screen snapshot in Figure-1.29 below, the _jspService() method took up a large percentage of the time: 68.9%. (What explanation might there be for that?)  In all likelihood, the majority of that time was probably in methods that it called, but it is reported as Self Time because those methods are outside the application and are not instrumented.

The time used in _jspService() is mostly a one-time cost for initially loading included JSPs, tag libraries, etc. This leads to an important observation about CPU profiling: to be accurate, you must allow for warmup.  In this case, the web server needs to warmup a bit before it will begin serving up this JSP efficiently. An even more commmon example is that the Java Virtual Machine (JVM) does its own internal profiling of your application as it runs and will compile the most frequently executed bytecode into machine code so that it will run faster. So all Java applications should be run for a while before the collected profiling results can be considered valid.


Figure-1.29: Combined view of the snapshot - Sang show the selection of _jspService un the Hot Spots view

4. Reset Collected Result.

Figure-1.30: Reset Collected Results.

                                                                                                                       return to top of the exercise

(1.3) Run automated test with JMeter


1. Start the test.

Figure-1.31:  Run Load Generator Script

Figure-3.12: Progress of the test

2. Observe the result.
<Learning point: JMeter> JMeter is an automated testing tool that emulates users accessing a web site. A JMeter script was used here to create two new entries to the weblog.


Figure-3.13: Two entries are added

3. Take the profiling snapshot.

Figure-3.14: Combined view

4. Analyze the data.

Figure-3.15: Detect the most time-consuming methods

5. Analyze the code.

<Learning point: Interpreting high Self-time> There is not much to the code - it mostly just calls other methods. Its Self Time is high because those other methods are not part of the source code for this application - they are instead in the Hibernate framework. So the time they use gets reported in the Self Time of this method. As it turns out, these particular Hibernate methods do disk accesses, which is why they run relatively slowly. So the moral of the story is that the high self-time is not necessarily due to the poor performing code in your application.

Now is this the end of the line? The slowest method in the application has been identified, and it is slow because it causes disk access. Is there anything else in the application itself that can be optimized in order to improve performance?  You will now analyze the calling patterns in fact identifying poorly designed coding practice in the next step.


Figure-3.16: getWeblogCategoryParentAssoc()

(1.4) Analyze the calling pattern

The most expensive method, getWeblogCategoryParentAssoc() is called by a method named getParentAssoc, which was called by getParent().

Notice something interesting, though: The method getParentAssoc() was invoked twice for each time that getParent() was invoked. Since each invocation of getParentAssoc() leads to an invocation of getWeblogCategoryParentAssoc(), it would be worth investigating whether all those additional calls can be removed. And the pattern is repeated on the calls to getParent() - it is being called twice for each call to getPath.


Figure-3.18: getWeblogCategoryParentAssoc()

                                                                                                                       return to top of the exercise

(1.5) Correct the problem


1. Right-click the entry for getParent() in the Call Tree window and select Go To Source. (Figure-1.41 below)


Figure-1.41: Go To Source on getParent() method.

2. Observe the inefficiently written code in getParent() method..
    public WeblogCategoryData getParent() throws RollerException {
        if (mNewParent != null) {
            // Category has new parent, so return that
            return (WeblogCategoryData) mNewParent;
        } else if (getParentAssoc() != null) {
            // Return parent found in database
            return ((WeblogCategoryAssoc) getParentAssoc()).getAncestorCategory();
        } else {
            return null;
        }
    }
Code-1.42: Inefficiently wriiten code


Figure-1.43: Inefficiently wriiten code

3. Modify the getParent() method.
  /*
 
  public WeblogCategoryData getParent() throws RollerException {
    if (mNewParent != null) {
      // Category has new parent, so return that
      return (WeblogCategoryData)mNewParent;
    } else if (getParentAssoc() != null) {
      // Return parent found in database
      return ((WeblogCategoryAssoc)getParentAssoc()).getAncestorCategory();
    } else {
      return null;
    }
  }
 
  */
 
 
  // *** NEW ***
    public WeblogCategoryData getParent() throws RollerException {
        if (mNewParent != null) {
           // Category has new parent, so return that
            return (WeblogCategoryData)mNewParent;
       } else {
            Assoc parentAssoc = getParentAssoc();
            if (parentAssoc != null) {
                // Return parent found in database
                mNewParent = ((WeblogCategoryAssoc)parentAssoc).getAncestorCategory();
                return (WeblogCategoryData)mNewParent;
            } else {
                return null;
            }
        }
    }
  //   *** NEW ***
Code-1.44: Modified code

Figure-1.45: Modified code

4. Right click getPath() in the Call Tree window and select Go To Source.


Figure-1.46: Go to source of getPath()

5. Observe the inefficiently written code in getPath() method..
    public String getPath(WeblogCategoryData category) throws RollerException {
            if (null == category.getParent()) {
              return "/";
            } else {
              String parentPath = getPath(category.getParent());
              parentPath = "/".equals(parentPath) ? "" : parentPath;
              return parentPath + "/" + category.getName();
            }
    }
Code-1.47: Inefficiently wriiten code

6. Modify the getPath() method.
    public String getPath(WeblogCategoryData category) throws RollerException {   
        WeblogCategoryData parent = category.getParent();
        if (null == parent) {
            return "/";
        } else {
            String parentPath = getPath(parent);
            parentPath = "/".equals(parentPath) ? "" : parentPath;
            return parentPath + "/" + category.getName();
        }
    }
Code-1.48: Modified getPath() method


Figure-1.49: Modified getPath() method

7. Profile the RollerWeb application again.

                                                                                                                                 return to top of the exercise

Summary

In this exercise you learned how to do CPU performance profiling with a root method using real-life example of "Roller". You learned that initial results can be misleading and it is therefore important to allow for warmup. You used a JMeter script to put load on a web application in order to spot the slowest method in that application. Using the profiling results, you identified some inefficient code. After modifying the code you used the profiler to verify the change did have an impact.

                                                                                                                                 return to the top

Exercise 2:  Profiling "iText" application


This exercise is based "Advanced Profiling: Theory in Practice with NetBeans" article written by Osvaldo Pinali Doederlein with his permission.

<to be provided>


                                                                                                                       return to top of the exercise


Summary

In this exercise you learned how to have the Profiler do performance analysis on a method.

                                                                                                                                 return to the top
  




Homework Exercise (for people who are taking Sang Shin's "Advanced Java SE Programming online course")


<to be provided>