This section will walk you through several simple examples of how Instruments can be put to use within an component.
Instruments are themselves completely thread safe. However the code in which they are embedded is usually not. Where necessary, it is the responsibility of the component developer to take synchronization issues into account. Use of the Instrument API should not require any extra work in this area.
One obvious example is to track method calls. This can be done by creating a CounterInstrument and then calling its increment method each time the method is called.
public void add( Object obj ) { m_list.add( obj ); m_instrumentAdds.increment(); } public Object remove( Object obj ) { Object removed m_list.remove( obj ); if ( removed != null ) { m_instrumentRemoves.increment(); } return removed; }
Note that in both methods, the Instrument is incremented after the action in the method has taken place. This is done to make sure that the counter is not incremented should an Exception be thrown while performing the action.
While the following method is slightly simpler than the above example, it would increment the counter even if the object did not exist in the list, or an exception had been thrown.
public Object remove( Object obj ) { m_instrumentRemoves.increment(); return m_list.remove( obj ); }
How this is implemented really depends on what is being done in the method and what the actual meaning of the counter is meant to be.
There are a number of ways to count the number of cyles that a loop goes through in an application. How exactly the cycles are counted depends on the frequency of the loop and what information is to be provided via the counter instrument.
If the loop cycles a couple times per second over several seconds or even minutes. Then it may be useful to a user to see how the loop is progressing over time. This can be accomplished by adding instrumentation to the loop as follows, so that each individual loop cycle is recorded.
while( test ) { // Do something that takes a little while. m_instrumentLoops.increment(); }
The increment method of an instrument is designed to be very light weight especially when nothing is actually monitoring the data. When the user has registered to collect sample data from the counter, there will be a small amount of additional overhead added to each call. In the above example, this is trivial.
However if the loop being instrumented is very tight, but loops for a short period of time, then it makes no sense to call increment every time through the loop. In such cases, it is better to maintain a seperate counter and then call increment a single time when the loop has completed.
int count = 0; while( test ) { // Do something that takes a little while. count++; } if ( count > 0 ) { m_instrumentLoops.increment( count ); }
Notice that the count passed to the increment method must be greater than 0.
If there is a chance that an exception could be thrown partway through the loop, it may be necessary to track how many cycles were successful. This can be acomplished using a finally block as follows:
int count = 0; try { while( test ) { // Do something that takes a little while. count++; } } finally { if ( count > 0 ) { m_instrumentLoops.increment( count ); } }
Quantitative values like collection or pool sizes can be tracked using a ValueInstrument. Value Instruments allow integer values to be set as the component runs. The InstrumentManager then makes use of the individual values to present values like the maximum, minimum, or mean value over a given period of time.
To track the size of a collection, it is necessary to notify the instrument each time the size of the collection is changed. Notice that the instrument is always notified after the size of the collection has changed. This is done to ensure that the value is always correct even if an exception is thrown while modifying the collection.
This example makes use of synchronization to ensure that the component is thread safe when modifying the collection. Synchronization would be necessary even without Instrumentation.
public void add( Object obj ) { int size; synchronized( m_list ) { m_list.add( obj ); size = m_list.size(); } m_instrumentSize.setValue( size ); } public Object remove( Object obj ) { Object removed; int size; synchronized( m_list ) { removed = m_list.remove( obj ); if ( removed == null ) { return null; } size = m_list.size(); } m_instrumentSize.setValue( size ); return removed; }
There are a couple things to point out about the above example. The first is how synchronization and the Instruments are handled. It is perfectly valid to set values to the instruments within the synchronization blocks. However, depending on what listeners have been registered with the instrument, this could result in poor performance. By taking the extra step of accessing the instruments after the synchronization block has completed, other threads will be able to access the method while the instrument value is being set.
It is often useful to combine the use of CounterInstruments in the above methods. It is then easy for users to track the number of adds and removes as well as the overall collection size.
Value Instruments can also be used to record the amount of time that an operation takes to complete. This can be quite useful to debug things like database query or servlet response times as load on an application increases.
public void process() { long start = System.currentTimeMillis(); // Do something. m_instrumentTime.setValue( (int)( System.currentTimeMillis() - start ) ); }
Some quantitative values are better counter than set to a value instrument. Example would be the number of bytes returned by a servlet, or the number of records returned from a database query. Values such as these are cumulative rather than absolute.
public void query() { // Execute query if ( records > 0 ) { m_instrumentRecords.increment( records ); } }