Registering a Custom Data Collection Mechanism with TPTP's Launch Configuration Framework
Extending TPTP's Launch Configuration

The following document provides an overview of how TPTP's framework can be extended to register a primitive data collection mechanism that launches an agent with limited functionality.

Table of Contents:
1.0 Introduction
2.0 Design Overview of Data Collectors & Analysis Types
2.1 Creating a Data Collector and an Analysis Type
2.1.1 Associating a Configuration to a Data Collector and an Analysis Type
2.2 Implementing a Launch Delegate
2.3 Implementing a Custom Control Provider

1.0 Introduction

This document will walk through the steps required to create a custom data collector that will be associated with the "Java Application" launch configuration type. It also describes the steps required in associating an analysis type, custom configuration, launch delegate, and a control provider to the data collector registered. Readers that wish not to follow with the article can use the link at the very bottom to download a copy of the sample plug-in.

The reader is assumed to have an intermediate knowledge of TPTP and PDE. The features that will be leveraged in this document are present in TPTP-4.2.0-200603101411 or older. Ensure that a correct driver is downloaded and setup. See TPTP's installation guide for more details.


2.0 Design Overview of Data Collectors & Analysis Types

In TPTP's launch configuration framework, there are extension points that make it possible for contributors to associate a data collector with a specific launch type. A data collector causes the launch of one or more agents. The agents are expected to collect and report data that is understandable by TPTP's loaders. An agent can for example report logging events (a.k.a Common Base Events), profile events, monitoring events, and etc...
As an example, consider the data collectors that are associated with Java applications: Drop down the menu of the profile toolbar item > Select Profile... > Double click Java Application to create a new launch configuration > Switch to the Profile/Monitor tab.
The profile dialog should look similar to figure 2.1. Notice that there are two data collectors registered with Java application:

  1. Java Profiling - This data collector will allow the launched Java application to be profiled
  2. Logging Agent Discoverer - This data collector performs periodic queries to determine if there are any logging agents registered with the process launched. Once a logging agent is discovered, it is automatically attached and monitored.

Figure 2.1 - Associated data collectors and analysis types of Java applications

Notice that in figure 2.1, the "Java Profiling" node has four associated analysis types. The purpose of an analysis type is to scope the data that needs to be collected. As a rule of thumb, a data collector defines how data is collected and its associated analysis types indicate what data should be collected.

Notice that there is a button on the right side of the dialog called "Edit Option". If a data collector of an analysis type has an associated configuration, then this button will be enabled. A configuration is a multi-page wizard that allows users to set custom input on editable options. As an example, select the " Java Profiling" data collector, and click on "Edit Options". Notice that the displayed wizard contains two pages: the first page is used to set the profiling filters, and the second page allows users to set options that limits profiling data.

Figure 2.2 provides an entity diagram that describes the overall relationship between the entities involved in a launch configuration. A control provider will be further discussed in section 2.3.
Figure 2.2 - Entity relationship


2.1 Creating a Data Collector and an Analysis Type
Follow the steps below to associate a custom data collector with the Java application launch type:
  1. Create a plug-in with the default options. Name the plug-in "org.eclipse.tptp.examples.data.collector".
  2. Open the manifest file of the plug-in and declare a dependency on org.eclipse.debug.core, org.eclipse.jdt.launching, org.eclipse.tptp.platform.models, and org.eclipse.hyades.trace.ui.
  3. Switch to the extensions page of the manifest file and add the extension point "org.eclipse.hyades.trace.ui.dataCollectionMechanism". This extension will allow the new data collector to be registered.
  4. Once the extension has been added, switch to the plugin.xml page and modify the extension's fragment to specify its required elements and attributes (see below):

    <!-- Declare the data collector -->
    <extension point="org.eclipse.hyades.trace.ui.dataCollectionMechanism">
        <!-- Declare the custom data collector -->
        <collector
          id = "org.eclipse.tptp.examples.data.collector.myCustomDataCollector"
          name = "My Custom Data Collector"
          description = "Here is a new data collector"
          icon = "icons/full/etool16/data_collector.gif">

       <!-- Associate this data collector with an anlysis type that will be defined later -->
       <analysis>
          <applicableType id = "org.eclipse.tptp.examples.data.collector.customAnalysisType"/>
       </analysis>
       </collector>
    </extension>
    The above extension will declare a data collector and associate it with an analysis type with id "org.eclipse.tptp.examples.data.collector.executionAnalysisType". This analysis type will be defined in the next step. Use the following icon that the data collector makes use of: (the icon should be copied in the plug-in and its path specified as the value of the 'icon' attribute)

  5. Use the "org.eclipse.hyades.trace.ui.analysisTypes" extension point to define a custom analysis type (see below):

    <!-- Declare the analysis type -->
    <extension point="org.eclipse.hyades.trace.ui.analysisTypes">
       <!-- The custom analysis type -->
       <analysisType
          name="Custom Analysis Type"
          icon="icons/full/obj16/exectimeantype_obj.gif"
          description= "Here is a new analysis type"
          id="org.eclipse.tptp.examples.data.collector.customAnalysisType"
          configurationId = "org.eclipse.tptp.examples.data.collector.customAnalysisTypeConfiguration">
       </analysisType>
    </extension>
    The analysis type defined above is associated with a configuration that will be declared in section 2.1.1. Use the following icon for the analysis type: . The next step is to associate the custom data collector with a launch configuration type:

  6. Associate the custom data collector with the Java application launch configuration type using the extension defined below:

    <!-- Make the associations between the Java launch configuration and the custom data collector -->
    <extension point="org.eclipse.hyades.trace.ui.launchConfigMechanismAssociator">

       <!-- Local Java Application -->
       <association
          launchConfigID = "org.eclipse.jdt.launching.localJavaApplication">
          <mechanism mechanismID = "org.eclipse.tptp.examples.data.collector.myCustomDataCollector">
             <configuration
                launchDelegate = "org.eclipse.tptp.examples.data.collector.launcher.CustomDelegate"
                configurationId = "org.eclipse.tptp.examples.data.collector.myCustomDataCollectorConfiguration"
                associatedAgent = "org.eclipse.tptp.examples.data.collector.myCustomDataCollectorAgent">
             </configuration>
          </mechanism>
       </association>
    </extension>
    Notice that the configuration element takes three attributes. The launchDelegate attribute defines a class which will handle the launching of the profile/monitor session, the configurationId attribute is the ID of the configuration extension for the data collector, and the associatedAgent attribute is the ID of the agent declaration extension that will be affiliated with the custom data collector. These attributes will further be explored in later sections. For now, create the class "CustomDelegate" under the package "org.eclipse.tptp.examples.data.collector.launcher". Have the class extend "org.eclipse.tptp.trace.ui.provisional.launcher.AbstractProcessLauncher".
Run a self-hosted workbench to see the results of the added contribution. Open the profile launch configuration and notice the new data collector and analysis type that appears when a launch configuration of type Java application is created. The dialog should appear something very similar to figure 2.3.

Figure 2.3 - The results of the contribution

2.1.1 Associating a Configuration to a Data Collector and an Analysis Type
In this section, a dummy configuration will be associated with both the data collector and analysis type defined in the previous section. Follow the steps below to declare the configurations:
  1. Use the "org.eclipse.hyades.trace.ui.configuration" extension point to define the configurations:

    <!-- Define the configuration wizard that will be associated with the custom data collector and analysis type -->
    <extension point = "org.eclipse.hyades.trace.ui.configuration">

       <!-- Data collector configuration -->
       <configuration
          id = "org.eclipse.tptp.examples.data.collector.myCustomDataCollectorConfiguration"
          class = "org.eclipse.tptp.examples.data.collector.configuration.CustomDataCollectorConfiguration"
          dialogTitle = "Custom Data Collector Configuration">
       </configuration>

       <!-- Analysis type configuration -->
       <configuration
          id = "org.eclipse.tptp.examples.data.collector.customAnalysisTypeConfiguration"
          class = "org.eclipse.tptp.examples.data.collector.configuration.CustomAnalysisTypeConfiguration"
          dialogTitle = "Custom Analysis Type Configuration">
       </configuration>
    </extension>
  2. Define the classes AbstractConfiguration, CustomDataCollectorConfiguration, and CustomAnalysisTypeConfiguration under the package name "org.eclipse.tptp.examples.data.collector.configuration".
  3. Open AbstractConfiguration and replace its content with:

    					
    package org.eclipse.tptp.examples.data.collector.configuration;
    
    import org.eclipse.debug.core.ILaunchConfiguration;
    import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.graphics.Font;
    import org.eclipse.swt.graphics.FontData;
    import org.eclipse.swt.layout.GridData;
    import org.eclipse.swt.layout.GridLayout;
    import org.eclipse.swt.widgets.Composite;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.Label;
    import org.eclipse.tptp.trace.ui.provisional.launcher.DataCollectionEngineAttribute;
    import org.eclipse.tptp.trace.ui.provisional.launcher.IConfiguration;
    import org.eclipse.tptp.trace.ui.provisional.launcher.IConfigurationPage;
    import org.eclipse.tptp.trace.ui.provisional.launcher.IStatusListener;
    
    /**
     * An abstract configuration that will only display a single page with a label.
     */
    public abstract class AbstractConfiguration implements IConfiguration
    {
    	private IConfigurationPage configurationPage;
    
    	/**
    	 * Here is our chance to initialize the configuration pages
    	 */
    	public void initialize()
    	{
    		/* Create the configuration page if it has not yet been created */
    		if (configurationPage == null)
    		{
    			configurationPage = new IConfigurationPage()
    			{
    				public void reset(ILaunchConfiguration launchConfiguration)
    				{
    				}
    
    				public void createControl(Composite parent)
    				{
    					Composite result = new Composite (parent, SWT.NONE);
    					result.setLayout(new GridLayout());
    					result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    					
    					Label titleLabel = new Label(result, SWT.NULL);
    					titleLabel.setText(getLabelText());
    					titleLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    
    					Font font = titleLabel.getFont();
    					if (font != null) {
    						FontData[] fonts = font.getFontData();
    						if (fonts != null && fonts.length >= 1) {
    							titleLabel.setFont(new Font(Display.getDefault(), fonts[0].getName(), 
    							fonts[0].getHeight() + 3, SWT.BOLD));
    						}
    					}
    				}
    
    				public String getPageName()
    				{
    					return this.getClass().getName();
    				}
    
    				public String getTitle()
    				{
    					return "Title of the Configuration Wizard";
    				}
    
    				public ImageDescriptor getWizardBanner()
    				{					
    					return null;
    				}
    
    				public String getDescription()
    				{
    					return "Here is the description of the configuration wizard";
    				}
    
    				public void addErrorListener(IStatusListener statusListener)
    				{				
    				}
    			};
    		}
    	}
    
    	/**
    	 * Return the configuration pages of this configuration.
    	 */
    	public IConfigurationPage[] getConfigurationPages()
    	{
    		return new IConfigurationPage[]{configurationPage};
    	}
    
    	/**
    	 * Normally client would use this to write the selected options of the configuration wizard to
    	 * the launch configuration. 
    	 */
    	public boolean finishConfiguration(ILaunchConfigurationWorkingCopy workingCopy)
    	{
    		return true;
    	}
    
    	/**
    	 * Normally this is used to return the attributes that is understandable by the back end engine
    	 * that starts the profile/monitor session.
    	 */
    	public DataCollectionEngineAttribute[] getAttributes()
    	{
    		return new DataCollectionEngineAttribute[]{};
    	}
    
    	/**
    	 * Return the label text that will be displayed in the single page 
    	 * configuration wizard.
    	 */
    	public abstract String getLabelText();
    }
    							
  4. Open CustomDataCollectorConfiguration and replace its content with:

    					
    package org.eclipse.tptp.examples.data.collector.configuration;
    
    
    /**
     * This data collector configuration
     */
    public class CustomDataCollectorConfiguration extends AbstractConfiguration
    {
    	public String getLabelText()
    	{
    		return "Data Collector Configuration";
    	}
    
    }
    							
  5. Open CustomAnalysisTypeConfiguration and replace its content with:

    					
    package org.eclipse.tptp.examples.data.collector.configuration;
    
    /**
     * The analysis type configuration
     */
    public class CustomAnalysisTypeConfiguration extends AbstractConfiguration
    {
    	public String getLabelText()
    	{
    		return "Analysis Type Configuration";
    	}
    
    }
    							
To see the added configurations: Self-host a workbench > Open the profile launch configuration > Select the custom data collector or the custom analysis type > and click on "Edit Options". A single-page wizard with the appropriate label should open - see figure 2.4.

Figure 2.4 - The configuration of the custom data collector


2.2 Implementing a Launch Delegate

Recall that in section 2.1 a launch delegate, "org.eclipse.tptp.examples.data.collector.launcher.CustomDelegate" was specified for the custom data collector. Within TPTP's framework, a launch configuration type (e.g. Java application) is associated with a primary launch delegate called org.eclipse.tptp.trace.ui.provisional.launcher.PrimaryLaunchDelegate. The primary launch delegate looks up the data collectors selected and invokes their respective launch delegate. There are three different kinds of launchers that can be associated with a data collector (see table 2.1):

Launch Delegate Type Description
A self manageable launcher This type of launcher benefits the least from the abstract launch classes available. A self manageable launcher will manage its own creation of data model entities. The general launcher in place will do nothing more than invoking the launch method of a self manageable launcher.
A process launcher A type of launcher that will launch an actual process on the target machine where a profile/monitor session will begin.
An agent launcher This type of launcher doesnít need an actual process to be launched. It only creates and feeds an agent. The process that the agent is associated with will either be a dummy process OR one thatís created by a process launcher.
Table 2.1 - The launch delegate types

Figure 2.5 is the decision tree that the primary launch delegate will use to determine the sequence of method invocations. Figure 2.6 describes the relationship between the classes and interfaces involved. The delgate class that will be defined for the custom data collector will extend AbstractProcessLauncher. The class will launch a Java process with an associated dummy agent that will not collect any type of data.


Figure 2.5 - The interaction between the launch delegate classes and interfaces. Methods marked by '+' will only be invoked once by the first delegate that is detected (e.g. if two data collectors are selected, then only one of the delegates will have its '+' methods invoked by the primary delegate).



Figure 2.6 - The relationship between the launch delegate classes and interfaces

Open CustomDelegate and replace its content with the following code:

					
package org.eclipse.tptp.examples.data.collector.launcher;

import java.io.File;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.hyades.internal.execution.local.common.CommandElement;
import org.eclipse.hyades.internal.execution.local.common.Constants;
import org.eclipse.hyades.internal.execution.local.common.ControlMessage;
import org.eclipse.hyades.internal.execution.local.common.QueryProcessListCommand;
import org.eclipse.hyades.internal.execution.local.common.RegisteredProcessListCommand;
import org.eclipse.hyades.internal.execution.local.control.Agent;
import org.eclipse.hyades.internal.execution.local.control.AgentConfiguration;
import org.eclipse.hyades.internal.execution.local.control.AgentConfigurationEntry;
import org.eclipse.hyades.internal.execution.local.control.CommandHandler;
import org.eclipse.hyades.internal.execution.local.control.Node;
import org.eclipse.hyades.internal.execution.local.control.Process;
import org.eclipse.hyades.internal.execution.local.control.ProcessListener;
import org.eclipse.hyades.models.hierarchy.HierarchyFactory;
import org.eclipse.hyades.models.hierarchy.TRCAgent;
import org.eclipse.hyades.models.hierarchy.TRCAgentProxy;
import org.eclipse.hyades.models.hierarchy.TRCConfiguration;
import org.eclipse.hyades.models.hierarchy.TRCOption;
import org.eclipse.hyades.models.hierarchy.TRCProcessProxy;
import org.eclipse.hyades.models.hierarchy.util.SaveUtil;
import org.eclipse.hyades.trace.ui.ProfileEvent;
import org.eclipse.hyades.trace.ui.UIPlugin;
import org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate;
import org.eclipse.swt.widgets.Display;
import org.eclipse.tptp.trace.ui.internal.launcher.core.LauncherConstants;
import org.eclipse.tptp.trace.ui.provisional.launcher.AbstractProcessLauncher;

/**
 * The launcher class for the custom data collector
 */
public class CustomDelegate extends AbstractProcessLauncher 
{
	/** The agent name */
	private static final String AGENT_NAME = "org.eclipse.tptp.examples.data.collector.agent";
	
	/** The agent type */
	private static final String AGENT_TYPE = "Custom Agent Type";
	
	/** This is an extended version of the JDT launch configuration delegate that will be used as a helper to 
	 * extract some required attributes from the configuration. */
	protected JavaConfigurationExtended javaLaunchConfigurationDelegate;

	public CustomDelegate()
	{
		super(AGENT_NAME, AGENT_TYPE);
		javaLaunchConfigurationDelegate = new JavaConfigurationExtended();
	}
	
	/**
	 * Retrieve the working directory from the configuration
	 */
	protected String getWorkingDirectory(ILaunchConfiguration conf)
	{
		try
		{
			File workingDirectory = javaLaunchConfigurationDelegate.getWorkingDirectory(conf);
			if (workingDirectory != null)
				return workingDirectory.getAbsolutePath();
			return null;
		} 
		catch (CoreException e)
		{
			return null;
		}
	}

	/**
	 * Retrieve the program arguments from the configuration
	 */
	protected String getProgramArguments(ILaunchConfiguration conf)
	{
		try
		{
			return javaLaunchConfigurationDelegate.getProgramArguments(conf);
		} catch (CoreException e)
		{
			return null;
		}
	}

	/**
	 * Retrieve the main type from the configuration
	 */
	protected String getMainTypeName(ILaunchConfiguration conf)
	{
		try
		{
			return javaLaunchConfigurationDelegate.getMainTypeName(conf);
		} catch (CoreException e)
		{
			return null;
		}
	}

	/**
	 * Retrieve the VM arguments from the configuration
	 */
	protected String getVMArguments(ILaunchConfiguration conf)
	{
		try
		{
			return javaLaunchConfigurationDelegate.getVMArguments(conf);
		} catch (CoreException e)
		{
			return null;
		}
	}

	/**
	 * Retrieve the classpath from the configuration
	 */
	protected String[] getClasspath(ILaunchConfiguration conf)
	{
		try
		{
			return javaLaunchConfigurationDelegate.getClasspath(conf);
		} catch (CoreException e)
		{
			return null;
		}
	}
	

	public Agent createAgent(Process process, final TRCProcessProxy trcProcessProxy) throws CoreException
	{
		final Agent agent = super.createAgent(process, trcProcessProxy);
		
		process.addProcessListener(new ProcessListener()
		{
			/** The model entity represeting an agent */
			private TRCAgentProxy trcAgentProxy;
			
			public void processLaunched(final Process process) 
			{
				long processId = 0;
				try
				{
					processId = Long.parseLong(process.getProcessId());
				}
				catch (Exception e)
				{				
					/* Ignore silently */
				}
							
				new Thread(new ProcessStateNotifier(new IProcessStateChangeListener(){

					public void processStateChanged(boolean alive) 
					{
						updateAgentStatus(alive);
						
					}}, process, processId)).start();
				
				
		        String pPath = trcProcessProxy.eResource().getURI().toString();
		        IPath path = new Path(pPath);

		        if (path.segmentCount() > 1) {
		            pPath = path.removeLastSegments(1).toString();
		        }		
		        
		        String fileName = "MyAgentFileName.trcaxmi";
		        IPath filePath = new Path(pPath).append(fileName);

		        URI uri = SaveUtil.createURI(filePath.toString()+"#").trimFragment();

		        Resource agDoc = Resource.Factory.Registry.INSTANCE.getFactory(uri).createResource(uri);
		        EList agExt = agDoc.getContents();

		        UIPlugin.getDefault().getResourceSet().getResources().add(agDoc); // prevents reloading later
		        HierarchyFactory factory = UIPlugin.getDefault().getPerftraceFactory();

		        trcAgentProxy = factory.createTRCAgentProxy();
		        trcAgentProxy.setName(AGENT_NAME);		   
		        trcAgentProxy.setType(AGENT_TYPE);		     
		        trcAgentProxy.setProcessProxy(trcProcessProxy);

		        /* We need to close the configuration of the execution agent and include it as part of the agent proxy */
		        AgentConfiguration agentConfiguration = agent.getConfiguration();
		        int agentConfigurationCount = agentConfiguration == null ? 0 : agentConfiguration.size();
				
		        /* For each configuration */
		        TRCConfiguration agentProxyConfiguration = null;
		        for (int i = 0; i < agentConfigurationCount; i++)
		        {
		        	AgentConfigurationEntry agentConfigurationEntry = agentConfiguration.getEntryAt(i);
		        	String configuraitonEntryName = agentConfigurationEntry.getName();
		        	
		        	TRCOption agentProxyConfigurationEntry = factory.createTRCOption();
		        	agentProxyConfigurationEntry.setKey(configuraitonEntryName);
		        	agentProxyConfigurationEntry.setValue(agentConfigurationEntry.getValue());
		        	
		        	/* Create the agent proxy configuration */
		        	if (agentProxyConfiguration == null)
		        	{
	    				agentProxyConfiguration = factory.createTRCConfiguration();

	    				agentProxyConfiguration.setName(configuraitonEntryName);   
	    				agentProxyConfiguration.setActive(true);
	    				agentProxyConfiguration.setAgentProxy(trcAgentProxy);
	    				trcAgentProxy.getConfigurations().add(agentProxyConfiguration);
	    			}
	    			agentProxyConfigurationEntry.setConfiguration(agentProxyConfiguration);              
	    		
	    		}
				
				
		        TRCAgent iAgent = factory.createTRCAgent();
		        iAgent.setAgentProxy(trcAgentProxy);
		        agExt.add(iAgent);
		        iAgent.setName(trcAgentProxy.getName());
		        iAgent.setRuntimeId(trcAgentProxy.getRuntimeId());
		        iAgent.setType(trcAgentProxy.getType());
		        iAgent.setCollectionMode(trcAgentProxy.getCollectionMode());
		        
		        updateAgentStatus(true);
			}			
			
			public void processExited(Process process) 
			{
				updateAgentStatus(false);
			}
			
			
			/**
			 * Activate the model entity representing the agent		
			 */
			private void updateAgentStatus(boolean activate)
			{
				/* Set the attributes of the agent */
				trcAgentProxy.setAttached(activate);
				trcAgentProxy.setActive(activate);
				trcAgentProxy.setMonitored(activate);
				trcProcessProxy.setActive(activate);
				
				/* Update the UI to indicate a refresh */
				updateAgentStatus(ProfileEvent.START_MONITOR);
			}
			
			/**
			 * Update the status of the agent.
			 */
			private void updateAgentStatus(final int status)
			{
				Display d = Display.getDefault();

				d.asyncExec(new Runnable() {
		                public void run() {                    
		                    ProfileEvent event = UIPlugin.getDefault().getProfileEvent();

		                    event.setSource(trcAgentProxy);
		                    event.setType(status);
		                    UIPlugin.getDefault().notifyProfileEventListener(event);
		                }
				});
			}
	
		
		});
		
		return agent;
	} 
	
	
	
	/**
	 * Extend the JDT Java launch configuration delegate
	 */
	protected class JavaConfigurationExtended extends AbstractJavaLaunchConfigurationDelegate
	{
		public void setDefaultSourceLocator(ILaunch launch, ILaunchConfiguration configuration) throws CoreException {
			super.setDefaultSourceLocator(launch, configuration);
		}

		public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException
		{
			/* Doesn't need to be implemented */
		}
	}
	
	
	/**
	 * This runnable periodically checks to ensure that a given process is alive 
	 */
	private class ProcessStateNotifier implements Runnable 
	{
		/** The process whose state is periodically checked */
		private Process process;
		
		/** A flag that indicates when a message sent to the Agent Controller has been processed */
		private boolean isMessageProcessed;
		
		/** The list of active processes */
		private long[] activeProcesses;
		
		/** The process id of the process launched */
		private long processId;
		
		/** The state listener registered */
		private IProcessStateChangeListener stateListener;
		
		private ProcessStateNotifier (IProcessStateChangeListener stateListener, Process process, long processId)
		{
			this.stateListener = stateListener;
			this.process = process;
			this.processId = processId;
		}
		
		public void run()
		{			
			while (true)
			{					
				waitFor(500);							
				ControlMessage message = new ControlMessage();
				QueryProcessListCommand qplCommand = new QueryProcessListCommand();
				
				message.appendCommand(qplCommand);
				isMessageProcessed = false;
				try
				{
					process.getNode().getConnection().sendMessage(message, new CommandHandler(){

						public void incommingCommand(Node node, CommandElement command)
						{
							int tag = (int) command.getTag();
							switch (tag) 
							{			
								/* Queries for meta data of agents (just the names) */
								case (int) Constants.RA_PROCESS_LIST:
															
									RegisteredProcessListCommand rplCmd = (RegisteredProcessListCommand) command;
									activeProcesses = rplCmd.getProcessList();
									
									break;
							}
							isMessageProcessed = true;
						}});
				} 
				catch (Exception e)
				{
					isMessageProcessed = true;
				}
										
				/* Wait until we get a response */
				while (!isMessageProcessed)
				{
					waitFor(500);
				}
			

				boolean foundProcess = false;
				if (activeProcesses != null)
				{
					for (int i = 0; i < activeProcesses.length; i++) 
					{
						if (activeProcesses[i] == processId)
						{
							foundProcess = true;
							break;
						}
					}
				}
				if (!foundProcess)
				{
					stateListener.processStateChanged(false);
					break;
				}						
			}
		}
		
		
		private void waitFor(long waitPeriod)
		{
			synchronized(this)
			{
				try
				{
					this.wait(waitPeriod);
				}
				catch (Exception e)
				{
					return;
				}
			}
		}
	};
	
	private interface IProcessStateChangeListener
	{
		public void processStateChanged(boolean alive);
	}
}							

Self-host a workbench to test drive the new launch delegate: In the self-hosted workbench, create a Java project with a classic HelloWorld class > Switch to the profile and logging perspective > Open the profile launch dialog > Create a new launch configuration of type Java application > Specify the HelloWorld class as the main class > Select the custom data collector that appears under the profile/monitor tab > Click on Profile. The HelloWorld process should be launched and the model entities for the process and the agent should appear in the profiling monitor view - see figure 2.7.


Figure 2.7 - The profile monitor view


2.3 Implementing a Custom Control Provider

Notice that there are toolbar and context menu options associated with the custom agent that do not apply in our context. TPTP's framework will associate a default set of options to an agent that does not have a control provider. Using the org.eclipse.hyades.trace.ui.controllableEntity extension point, clients can associate control providers on 4 different controllable entities: the process, agent, analysis type, and the toolbar. The relationship of the classes and interfaces involved when defining a control provider is depicted in figure 2.8. This section will associate a custom control provider to the custom agent. The provider will only be used to associate actions to options that will appear in the context menu of the agent. The control provider class will extend org.eclipse.tptp.trace.ui.provisional.control.provider.AbstractAgentControlProvider and will only associate actions to the resume/pause/attach/detach options.


Figure 2.8 - Control provider class diagram

Follow the steps below to associate a custom control provider to the agent:

  1. Open the plugin.xml of the org.eclipse.tptp.examples.data.collector plug-in
  2. One of the first steps of associating a control provider is to declare the agent that the provider will be affiliated with. Use the org.eclipse.hyades.trace.ui.agentDeclaration extension point to declare an agent:
    <!-- Declare the custom agent -->
    <extension point = "org.eclipse.hyades.trace.ui.agentDeclaration">
       <agent
          id = "org.eclipse.tptp.examples.data.collector.myCustomDataCollectorAgent"
          name = "org.eclipse.tptp.examples.data.collector.agent"
          type = "Custom Agent Type">
       </agent>
    </extension>


  3. Define the control provider:
    <!-- Associate a custom control provider to the agent declared. -->
    <extension point = "org.eclipse.hyades.trace.ui.controllableEntity">
       <!-- The custom agent -->
       <control associatedAgentId = "org.eclipse.tptp.examples.data.collector.myCustomDataCollectorAgent">
          <entity type = "agent"
          class = "org.eclipse.tptp.examples.data.collector.controls.CustomAgentControlProvider"/>
       </control>
    </extension>


  4. Create a new class called CustomAgentControlProvider under the new package: org.eclipse.tptp.examples.data.collector.controls. The content of the file appears below:
    package org.eclipse.tptp.examples.data.collector.controls;
    
    import org.eclipse.core.runtime.CoreException;
    import org.eclipse.jface.dialogs.MessageDialog;
    import org.eclipse.jface.viewers.StructuredSelection;
    import org.eclipse.tptp.examples.data.collector.Activator;
    import org.eclipse.tptp.trace.ui.provisional.control.provider.AbstractAgentControlProvider;
    import org.eclipse.tptp.trace.ui.provisional.control.provider.IAgentStateModifier;
    
    /**
     * A custom control provider that will associate actions to the resume/pause/attach/detach
     * options that appear in the context menu of the custom agent.
     */
    public class CustomAgentControlProvider extends AbstractAgentControlProvider
    {
    	/* The state modifier for the custom agent */
    	private IAgentStateModifier stateModifier;
    	
    	public IAgentStateModifier getAgentStateModifier()
    	{
    		if (stateModifier != null)
    			return stateModifier;
    		
    		stateModifier = new IAgentStateModifier()
    		{
    
    			/**
    			 * Display a message that indicates that the detach option
    			 * has been invoked.
    			 */
    			public void detach() throws CoreException
    			{
    				showMessage("Detach option is invoked");
    			}
    
    			/**
    			 * Display a message that indicates that the attach option
    			 * has been invoked.
    			 */
    			public void attach() throws CoreException
    			{
    				showMessage("Attach option is invoked");
    			}
    
    			/**
    			 * Display a message that indicates that the resume option
    			 * has been invoked.
    			 */
    			public void startMonitoring() throws CoreException
    			{
    				showMessage("Resume option is invoked");
    			}
    
    			/**
    			 * Display a message that indicates that the pause option
    			 * has been invoked.
    			 */
    			public void pauseMonitoring() throws CoreException
    			{
    				showMessage("Pause option is invoked");
    			}
    
    			
    			/**
    			 * Returning true will always ensure that teh attach
    			 * option will always be enabled.
    			 */
    			public boolean canAttach()
    			{
    				return true;
    			}
    		
    			/**
    			 * Returning true will always ensure that teh attach
    			 * option will always be enabled.
    			 */
    			public boolean canDetach()
    			{
    				return true;
    			}
    			
    			/**
    			 * Returning true will always ensure that teh attach
    			 * option will always be enabled.
    			 */
    			public boolean canResume()
    			{
    				return true;
    			}
    			
    			/**
    			 * Returning true will always ensure that teh attach
    			 * option will always be enabled.
    			 */
    			public boolean canPause()
    			{
    				return true;
    			}
    
    			public void setInput(StructuredSelection input)
    			{		
    			}
    		
    			/**
    			 * Opens a message dialog with the message passed in.
    			 */
    			private void showMessage(String message)
    			{
    				MessageDialog.openInformation(
    						Activator.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell(), 
    						"", message);
    			}
    		};
    		
    		return stateModifier;
    	}
    
    }
    									
To test out the new control provider: Self-host a workbench > Profile the HelloWorld class created in the previous section > Right click the agent and select Attach to Agent. The message dialog in figure 2.9 should appear.


Figure 2.9 - The results of selecting the attach option

This conlcudes this article. The source code of the plug-in created can be downloaded here.