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+).
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
You should be able to build jdiagnose by going into the site folder and calling:
maven multiproject:install
To do a do a full distribution just run "maven" in the site folder (not really recommended, though).
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.
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. |
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 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.
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
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.
There are several prebuilt diagnostics e.g.
Diagnostic | Description |
---|---|
Basic JDBC Diagnostic | Tests a database through a JDBC connection |
Check ClassPath Diagnostic | Makes sure that all items listed on the ClassPath exist |
HTTP(S) URL Diagnostic | Tests a HTTP(S) URL |
Java Version Diagnostic | Ensure that you're running with the correct Java Version |
JDK Diagnostic | Tries to make sure that you're running with the JDK |
Pingable Diagnostic | Pings objects that implement the Pingable interface |
Tcp Diagnostic | Tests a Socket |
Succeeding Diagnostic | Test diagnostic that always succeeds |
Failing Diagnostic | Test 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.
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.
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.
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.
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.
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).
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.
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.
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. |
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>
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>
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.
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.
JDiagnose comes with DDL scripts for a few database (and you're welcome to submit more). The table has the following columns:
Column Name | Column Type | Description |
---|---|---|
id | VARCHAR(32) | The primary key, should be an IDENTITY column if supported |
guid | VARCHAR(128) | The results GUID |
host | VARCHAR(256) | The host that producted the diagnostic result |
agent | VARCHAR(256) | The name of the agent running the diagnostic |
name | VARCHAR(256) | The name of the diagnostic |
resultState | VARCHAR(32) | Whether the diagnostic was successful or not |
startDate | DATE | When the diagnostic started |
finishDate | DATE | When the diagnostic finished |
duration | INT | How long the diagnostic took (in milliseconds) |
summary | VARCHAR(512) | The message summary, if there was one |
body | VARCHAR(1024) | The summary body, if there was one |
Check out the JDiagnose-Library src/sql directory for the scripts to create the tables.