Building JDiagnose

JDiagnose hosted at SourceForge and is built using Apache's Maven. So, to build it, you'll need a cvs client and a copy of Maven installed (tested with 1.0+).

Where to get the source

There are 4 projects you'll need to check out build jdiagnose: core, library, site and web. The following cvs commands should do it:

cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jdiagnose login
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jdiagnose co -P core
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jdiagnose co -P library
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jdiagnose co -P site
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/jdiagnose co -P web

Building it

You should be able to build jdiagnose by going into the site folder and calling:

    maven multiproject:install
You need to call install instead of artifact so that the jdiagnose-web project can pick up the appropriate jar files.

To do a do a full distribution just run "maven" in the site folder (not really recommended, though).

Core JDiagnose

This section introduces some of the core idea's behind JDiagnose. JDiagnose is not a general purpose remote monitoring and administration infrastructure like JMX or SNMP but is intended to supplement them.

Results

The Result objects (ResultInfo and RemoteResult) make up the core of the JDiagnose system. In essence, they represent the information available as a result of running a Diagnostic, specifically: what it was called, when it started, how long it took, whether it succeeded and if it failed, what the exception was that caused the failure. Remote results additionally contain information about which host and agent the diagnostic was running on. Below is a table of the elements that make up a RemoteResult and the ResultInfo it contains:

Guid A globally unique identifier for this result. Useful for tracking it accross in a distributed environment
Agent The agent is intended to be the name of the application that has generated this remote result. An application could be configured to contain multiple agents.
Host The name of the host that the agent is running on. Default to InetAddress.getLocalHost().getCanonicalHostName()
SequenceNumber A sequence number that gives ordering particular Agent, Host, Diagnostic combination. Similar to the JMX Notification sequence number.
ResultInfo.StartTime The time that the diagnostic started in milliseconds since the 1st of January 1970.
ResultInfo.Duration How long it took in milliseconds
ResultInfo.FinishTime The time that the diagnostic finished in milliseconds since the 1st of January 1970.
ResultInfo.Name The name of the diagnostic.
ResultInfo.State Whether the result SUCCEEDED or FAILED.
ResultInfo.Message The message that accompanied the result. Usually the exception message and stacktrace.

DefaultAgent

Using a org.jdiagnose.remote.DefaultAgent takes care of most of the heavy lifting in adding a Guid, Host, Agent and SequenceNumber to local results.

The RemoteSystem: Agents, Hosts and Diagnostics

The org.jdiagnose.remote.system.* classes present a view of current and historical results.

RemoteSystem
    |-------- RemoteHosts
    |              |--------RemoteAgentAtHosts
    |                                |----------RemoteDiagnostics
    |                                                   |----------RemoteResults
    |---------RemoteAgents
                   |--------RemoteAgentAtHosts
                                     |----------RemoteDiagnostics
                                                        |----------RemoteResults

This is used by the JDiagnose Web Application to present a summary of the current system that is being monitored.

Senders and Listeners

The usual path of the JavaVersionDiagnostic result from the getting started guide goes something like this:

org.jdiagnose.library.JavaVersionDiagnostic
org.jdiagnose.runtime.DefaultDiagnosticRunner
org.jdiagnose.remote.SendingRemoteResultSender
org.jdiagnose.remote.comms.HttpSender
org.jdiagnose.library.spring.web.HttpListenerController
org.jdiagnose.remote.StoringRemoteResultListener
org.jdiagnose.remote.RemoteResultStore
org.jdiagnose.remote.system.InMemoryRemoteSystem

Incorporating JDiagnose into your client application

While the JDiagnose Web Application can be configured to remotely monitor applications (e.g. using the HttpDiagnostic) it is often necessary to embed diagnostics within your application. There are several prebuilt diagnostics that can be used to within an application. Often however it may be necessary to write your own. Methods can also be decorated to produce diagnostic results. The following sections cover these options in more detail.

Using the prebuilt diagnostics

There are several prebuilt diagnostics e.g.

DiagnosticDescription
Basic JDBC DiagnosticTests a database through a JDBC connection
Check ClassPath DiagnosticMakes sure that all items listed on the ClassPath exist
HTTP(S) URL DiagnosticTests a HTTP(S) URL
Java Version DiagnosticEnsure that you're running with the correct Java Version
JDK DiagnosticTries to make sure that you're running with the JDK
Pingable DiagnosticPings objects that implement the Pingable interface
Tcp DiagnosticTests a Socket
Succeeding DiagnosticTest diagnostic that always succeeds
Failing DiagnosticTest Diagnostic that always fails

These can be configured to diagnose the various parts of your application. Where these aren't appropriate, you can write your own.

Writing your own diagnostic

If one of the prebuilt diagnostics doesn't appeal, it's easy enough to roll your own. JDiagnose allows diagnostics to be built in a manner reminiscent of JUnit TestCases. Create a class that extends DiagnosticUnit and create void methods that start with "diagnose" e.g.:

public class SomeRandomDiagnostic extends DiagnosticUnit {
    private Random random = new Random();
    public void diagnoseSomethingRandom() throws Exception {
        // fail 10% of the time
        assertTrue("Randomly failed", random.nextInt(100) > 10);
    }
}

While there is a TestCaseDiagnostic (that runs a JUnit TestCase), diagnostics differ from TestCases in that they're expected to be configured usually in a Dependency Injection friendly way.

Making your services Pingable

Instead of writing a Diagnostic, you could have your services implement the ping method on org.jdiagnose.Pingable e.g.:

public class SomeRandomService implements Pingable {
    private Random random = new Random();
    public void ping() throws Exception {
        // fail 10% of the time
        if(random.nextInt(100) > 10) {
            throw new Exception("Randomly failed");
        }
    }
}

You can then set your Pingable service into an org.jdiagnose.library.PingableDiagnostic.

Using the Diagnostic Template

Instead of running independent diagnostics, you may just want to decorate an existing method so that you can get diagnostic results whenever it is called. Support for this is provided by the org.jdiagnose.remote.template.* classes. The DiagnosticServiceSupport class provides a convenient superclass for service classes that want to decorate certain methods e.g.:

public class DiagnosticMyServiceWrapper extends DiagnosticServiceSupport implements MyService {
    MyService delegate = new MyServiceImpl();
    public List getCustomers(final String param) {
        return getDiagnosticTemplate().execute("getCustomers",
            new DiagnosticCallback() {
                public Object invoke() throws Throwable {
                    return delegate.doSomethingInteresting(param);
                }
            });
    }
}

Now, while that takes care of some of the complexity of providing diagnostic results for a service, it should be even easier if you use either a proxy or interceptor, as discussed in the next section.

Using the Diagnostic Proxy or Interceptor

Adding Diagnostics to a service is the kind of orthogonal activity that Dynamic Proxies and AOP were built for. Both the DiagnosticProxy in jdiagnose-core and the DiagnosticInterceptor in jdiangose-library are built on top of the DiagnosticTemplate mentioned in the previous section. This is how you could use the DiagnosticProxy:

public void main(String args[]) {
    MyService service = new DiagnosticProxyFactory().createProxy(new DefaultAgent("jdiagnose"), new
        MyServiceImpl(), 
            new HttpSender("http://localhost:8080/jdiagnose/httplistener.htm"));
    List customers = service.getCustomers();
}

To use the DiagnosticInterceptor, you'll need an AOP framework that has support for the aopalliance classes e.g. the SpringFramwork AOP Framework e.g. (assuming you have "agent" and "httpSender" beans defined elsewhere):

 
<bean id="autoProxyCreator"
    class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>

<bean id="diagnosticsAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <bean class="org.jdiagnose.library.aop.DiagnosticInterceptor">
            <constructor-arg><ref bean="agent"/></constructor-arg>
            <constructor-arg><ref bean="httpSender"/></constructor-arg>
        </bean>
    </property>
    <property name="patterns">
        <list>
            <value>.*MyService.getCustomer.*</value>
        </list>
    </property>
</bean>

Both the Proxy and Interceptor have the option to reduce the number of results that they send. If you use the constructor with "filterSuccesses" set to true, they will only send failures and successes immediately subsequent to failures. All other successes will be suppressed.

Sending Results

Once Diagnostic results are being generated, they'll need to go somewhere, usually to a JDiagnostic Server. The simplest way to do this is to use the org.jdiagnose.remote.comms.HttpSender, which sends remote results over HTTP as POST data. The JDiagnose Server has a controller configured to respond to the following url:

http://host:port/jdiagnose/httplistener.htm

There are also MulticastSender and MulticastListener classes for supporting a more distributed architecture. There are built on top of UDP and have no additional support for reliability.

You can create your own your own RemoteResultSender implementations to support additional protocols (e.g. SNMP or JMX's NotificationBroadcaster).

Sending Results Asynchronously

An AsyncRemoteResultSender can be used in order to reduce the performance overhead of sending results remotely e.g.:

RemoteResultSender httpSender = 
    new AsyncRemoteResultSender(
        new HttpSender("http://localhost:8080/jdiagnose/httplistener.htm"));

Note, the default settings for the AsyncRemoteResultSender uses a BoundedLinkedQueue with a default capacity of 1024 after which it will start blocking.

Logging

The jdiagnose-core project has been designed not to have any dependencies, including logging. In order to use Log4j to log out diagnostic results and exceptions, use the logging listeners defined in the jdiagnose-library org.jdiagnose.library.logging package.

Mock

In order to test JDiagnose with a large number of hosts, agents and results, you can use the org.jdiagnose.library.mock.MockRemoteSystem class.

Configuring and Running the JDiagnose Server

Once your client applications have been instrumented to produce diagnostics, you can use the JDiagnose Server to provide an overview of the system. The JDiagnose Server is a standard Servlet 2.3 Web Application and you should be able to deploy it by just dropping the WAR file in your favourite Servlet 2.3 compatible container.

Spring Configuration

The JDiagnose Server uses Spring for its service configuration and MVC. The configuration can be found in the following XML files:

jdiagnose/WEB-INF/classes/diagnostics.xml Where all the diagnostics, diagnostic runners, SMTP senders and other services are configured.
jdiagnose/WEB-INF/classes/db.xml Where all the Database configuration is done.
jdiagnose/WEB-INF/jdiagnose-servlet.xml Contains all the Web MVC configuration.

Configuring Diagnostics

Diagnostics and the Diagnostic runners are configured in the diagnostics.xml file e.g.:

 
<bean id="installDiagnosticRunner" class="org.jdiagnose.runtime.DefaultDiagnosticRunner">
    <constructor-arg>
        <bean class="org.jdiagnose.Diagnostics">
            <property name="diagnosticContainers">
                <list>
                    <bean class="org.jdiagnose.library.DiagnoseDiagnostic"/>
                    <bean class="org.jdiagnose.library.JavaVersionDiagnostic">
                        <property name="javaVersion"><value>1.3+</value></property>
                    </bean>
                    <bean class="org.jdiagnose.library.CheckClassPathDiagnostic"/>
                </list>
            </property>
        </bean>
    </constructor-arg>
</bean>

In order for them to appear on the menu on the right hand side, you'll need to add your new runner into the "runners" bean in the jdiagnose-servlet.xml file:

 
<bean name="runners" class="org.jdiagnose.library.spring.web.Runners">
    <constructor-arg>
        <map>
            <entry key="runtime"><ref bean="runtimeDiagnosticRunner"/></entry>
            <entry key="install"><ref bean="installDiagnosticRunner"/></entry>
        </map>
    </constructor-arg>
</bean>

Configuring Automatic Runs

JDiagnose Server uses Quartz to run the DiagnosticRunner's automatically e.g.:

 
<bean id="runInvoker" 
    class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject"><ref bean="runtimeDiagnosticRunner"/></property>
    <property name="targetMethod"><value>runAsynchronously</value></property>
</bean>

<bean id="scheduler" 
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers"><list><ref bean="cronTrigger"/></list></property> 
    <property name="quartzProperties">
        <props>
            <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
            <prop key="org.quartz.threadPool.threadCount">1</prop>
        </props>
    </property>
</bean>

<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail"><ref bean="runInvoker"/></property>
    <property name="cronExpression"><value>0 0/1 7-23 * * ?</value></property> 
</bean>

Configuring SMTP

JDiagnose Server can be configured to send emails in the event of a diagnostic failure. The configuration looks as follows:

 
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host"><value>${host}</value></property>
</bean>

<bean id="smtpListener" class="org.jdiagnose.library.smtp.SmtpListener">
    <property name="from"><value>${fromaddress}</value></property>
    <property name="to">
        <list>
            <value>${toaddress}</value>
        </list>
    </property>
    <property name="subject"><value>Diagnostic Failure</value></property>
    <property name="mailSender"><ref bean="mailSender"/></property>
    <property name="messageProvider"><ref bean="velocityMessageProvider"/></property>
</bean>

Replace the host, fromaddress and toaddress variables to get it to send failures to you.

Developing with Resin

The jdiagnose-web/src/conf/ folder contains resin .conf files that should be useful while developing as they will allow resin to run against development configuration, web and classes directories. You will have to change the application directory setting from "/dev/jdiagnose/web/src/web" to wherever you have jdiagnose checked out to.

The JDiagnose Database

JDiagnose comes with DDL scripts for a few database (and you're welcome to submit more). The table has the following columns:

Column NameColumn TypeDescription
idVARCHAR(32)The primary key, should be an IDENTITY column if supported
guidVARCHAR(128)The results GUID
hostVARCHAR(256)The host that producted the diagnostic result
agentVARCHAR(256)The name of the agent running the diagnostic
nameVARCHAR(256)The name of the diagnostic
resultStateVARCHAR(32)Whether the diagnostic was successful or not
startDateDATEWhen the diagnostic started
finishDateDATEWhen the diagnostic finished
durationINTHow long the diagnostic took (in milliseconds)
summaryVARCHAR(512)The message summary, if there was one
bodyVARCHAR(1024)The summary body, if there was one

Check out the JDiagnose-Library src/sql directory for the scripts to create the tables.