Sunday March 02, 2008
Getting Started Extending VisualVM (Part 6)
On its most abstract level, VisualVM is simply a tool for displaying data. By default, it displays data relating to applications, hosts, dumps, and snapshots. However, it could, theoretically, display any data at all (photos, for example), although the intention of the tool is for it to deal with monitoring, troubleshooting, and management data. The reason that it can work with any kind of data is the fact that its APIs are created with pluggability in mind. Pretty much everything is pluggable and that's the point—VisualVM is intended to cater to all your monitoring, troubleshooting, and management data needs.
Each source of data (which are primarily "application", "host", "heapdump", "threaddump", "coredump", and "snapshot") is represented by a node in the explorer view. What do you need to do if you want to add another source of data there? That's what I figured out today. Here you see the Memory Monitor sample from the JDK's "demo/management" folder opened from a new "System Monitors" node in the explorer view:
The "System Monitors" node is provided by my new data source. What's also interesting is to see the "Hello World" tab above. It comes from a different module. So, in the same way that your custom views are pluggable, your custom data sources are also pluggable. Good news, right?
First, let's look at how the new node for our data source was created. We begin by extending AbstractDataSource:
package org.visualvm.demodescriptorprovider;
import com.sun.tools.visualvm.core.datasource.AbstractDataSource;
public class DemoAbstractDataSource extends AbstractDataSource {
private static DemoAbstractDataSource sharedInstance;
public static synchronized DemoAbstractDataSource sharedInstance() {
if (sharedInstance == null) {
sharedInstance = new DemoAbstractDataSource();
}
return sharedInstance;
}
}
As usual, since the factory pattern is used over and over and over again in the VisualVM APIs, we need to create a factory class that will instantiate our data source:
package org.visualvm.demodescriptorprovider;
import com.sun.tools.visualvm.core.datasource.DataSource;
import com.sun.tools.visualvm.core.datasource.DataSourceRepository;
import com.sun.tools.visualvm.core.datasource.DefaultDataSourceProvider;
class DemoDefaultDataSourceProvider extends DefaultDataSourceProvider<DemoAbstractDataSource> {
static void register() {
DemoDefaultDataSourceProvider support = new DemoDefaultDataSourceProvider();
support.initContainer();
DataSourceRepository.sharedInstance().addDataSourceProvider(support);
}
private void initContainer() {
DemoAbstractDataSource container = DemoAbstractDataSource.sharedInstance();
DataSource.ROOT.getRepository().addDataSource(container);
registerDataSource(container);
}
}
The above will be called later from our ModuleInstall.restored method. Next, we need to describe our data source, which provides its icon, label, and position in the explorer view:
package org.visualvm.demodescriptorprovider;
import com.sun.tools.visualvm.core.model.dsdescr.DataSourceDescriptor;
import java.awt.Image;
import org.openide.util.Utilities;
public class DemoDataSourceDescriptor extends DataSourceDescriptor {
private static final Image NODE_ICON =
Utilities.loadImage(
"org/visualvm/demodescriptorprovider/icon.png",
true);
public DemoDataSourceDescriptor() {
super(DemoAbstractDataSource.sharedInstance(),
"System Monitors",
"Descriptor for System Monitors container",
NODE_ICON,
//set the position:
50,
EXPAND_NEVER);
}
}
Also, every data source and data source provider needs a model for synchronization purposes:
package org.visualvm.demodescriptorprovider;
import com.sun.tools.visualvm.core.datasource.DataSource;
import com.sun.tools.visualvm.core.model.AbstractModelProvider;
import com.sun.tools.visualvm.core.model.dsdescr.DataSourceDescriptor;
public class DemoAbstractModelProvider extends AbstractModelProvider<DataSourceDescriptor, DataSource> {
@Override
public DataSourceDescriptor createModelFor(DataSource ds) {
if (DemoAbstractDataSource.sharedInstance().equals(ds)) {
return new DemoDataSourceDescriptor();
}
return null;
}
}
Finally, we use the ModuleInstall.restored to register and initialize our entry points:
package org.visualvm.demodescriptorprovider;
import com.sun.tools.visualvm.core.model.dsdescr.DataSourceDescriptorFactory;
import org.openide.modules.ModuleInstall;
public class Installer extends ModuleInstall {
@Override
public void restored() {
DemoDefaultDataSourceProvider.register();
DataSourceDescriptorFactory.getDefault().registerFactory(new DemoAbstractModelProvider());
}
}
Now you have a new node in the explorer view. That means that you have a new data source. As explained in Getting Started Extending VisualVM (Part 1), you can create one or more views (tabs and subtabs) for a data source. In that case, we created a tab for the application data source, yesterday we created a tab for the host data source, and today we will create one for our own new data source. This time, instead of implementing DataSourceViewsProvider<Application> or DataSourceViewsProvider<Host>, you need to implement DataSourceViewsProvider<DataSource>.
Next, you need to specify that the views you're creating should only apply to your data source (otherwise the tabs provided by your DataSourceViewsProvider will apply to ALL data sources, in other words, they would appear in the views of all applications, hosts, thread dumps, head dumps, core dumps, and anything else that is a data source in VisualVM). Here's how to do that:
@Override
public boolean supportsViewFor(DataSource ds) {
if (DemoAbstractDataSource.sharedInstance().equals(ds)) {
return true;
}
return false;
}
However... since we have just created our own data source, we can now implement DataSourceViewsProvider<DemoAbstractDataSource>, instead of DataSourceViewsProvider<DataSource>. Then, instead of doing the check above, you can simply return "true".
Note: If a DataSourceViewsProvider returns true from the supportsViewFor() method, an "Open" menu item is automatically created for you. (I didn't know that, created my own Open menu item, and then found that I had two Open menu items, one from this plugin and one from another plugin that plugs into this data source, as explained later. Then I was told about this cool "gift" that the supportsViewFor menu item gives when returning true.)
Next, as explained in Getting Started Extending VisualVM (Part 1), use the DataSourceViewsProvider.getViews method to create your views. Here I followed the same steps as in that earlier blog entry, except that I copied the "Memory Monitor" sample from the JDK demo folder (which is just one class that creates a JPanel) and slightly reworked it so that it is now split across different detail subtabs.
At the end of all of this, you should have a plugin that has more or less this source structure:
And so, you have now created your first data source. As stated at the start, a data source is the primary actor in VisualVM. Everything that happens in VisualVM happens in relation to a data source. Now you can see how a simple example of a data source is created.
There's one more aspect to look at—the "Hello World" tab that you see in the first screenshot above. How did that get there? It is not in the sources shown in the screenshot of the sources that you see above. In the same way that your own views can be made to be pluggable, as explained in Getting Started Extending VisualVM (Part 4), so your data sources have a pluggable user interface too. What I did was this—I exposed the package that contains my data source class (DemoAbstractDataSource in the screenshot above). Then I attached it to the module suite that defines VisualVM, together with the plugin that provides the additional view. That meant I could implement DataSourceViewsProvider<DemoAbstractDataSource>. Alternatively, I could implement DataSourceViewsProvider<DataSource> and then check in my DataSourceViewsProvider.supportsViewFor for the DemoAbstractDataSource class, exactly as done in the first plugin.
So, the code for creating a new tab for my data source is the same as before, except that it is now done in a different plugin. And, when the data source is present, the "Hello World" tab defined in DataSourceViewsProvider.getViews is created. The "Open" menu item is shared between the two tabs, because both return "true" from DataSourceViewsProvider.supportsViewFor. Again you can see how versatile VisualVM is, because it is completely open to being extended, even to the extent that the data sources that you yourself provide can have user interfaces that can be extended by others.
Mar 02 2008, 09:51:23 AM PST Permalink


