This section describes how to write a custom Data Producer. Before beginning, you should understand the following terminology:
Monitored function | Method of the monitored application that is marked as function interesting for performance or exception analyze |
Data Producer | Method that get all actual monitored function arguments and collects event data that will be sent to SE-Viewer |
Prefix producer | Data Producer that is called at the very beginning of the method |
Postfix producer | Data Producer that is called at the very end of the method execution |
OnException producer | Data Producer that is called at the very end of the method in case the method has thrown an exception |
The Data Producer assembly is a .NET assembly that contains Data Producers.
Data Producer assembly requirements:
1. Assembly has to be added to the GAC
2. The architecture of your assembly has to be compatible with the architecture of monitored application. You can’t use MSIL assembly to monitor applications written for Framework 1.1.
3. Data Producer assembly has to have APTC attribute:
Add following line to the AssemblyInfo.cs:
[assembly:AllowPartiallyTrustedCallers]
You have to mark your function as monitored function. The list of monitored function types is follows:
You can specify prefix producer for:
You can specify postfix producer and onException producer for any type of monitored function.
You can specify Data Producers for your methods:
Node ss:configuration\ss:applicationSettings\ss:instrumentationAssemblies\ss:instrumentationAssembly contains information about Data Producer assembly
<ss:instrumentationAssemblies> <ss:instrumentationAssembly name="AssemblyName" keyToken="97bd5f1ee20e650e" version="1.0.0.0" locale="Neutral" /> </ss:instrumentationAssemblies> |
Node ss:configuration\ss:applicationSettings\ss:functions\ss:function contains information about Data Producers assigned to the function
<ss:functions> <ss:function name="MethodName" module="MathodAssembly.dll">
<ss:instrumentation value="prefix" name="Namespace.Class.Producer" assembly="AssemblyName" />
<ss:instrumentation value="postfix" name="Namespace.Class.Producer" assembly="AssemblyName" />
<ss:instrumentation value="onException" name="Namespace.Class.Producer" assembly="AssemblyName" />
</ss:function> </ss:functions> |
Producer is a method that is satisfied for the following parameters:
1. Class, containing producer has to be a public
2. Producer is a static method
3. Prefix and postfix producer has to have following signature:
public static object ProducerName( Int32 functionID, Int64 namePtr, Int32 argsCount, object[] parameters, object threadContext ) |
6. OnException producer has to have following signature:
public static object ProducerName( object producerData, object exc, Int32 functionID, Int64 functionName, Int32 argsCount, object[] arguments, object threadContext) |
Name | Value | Comment |
functionID | Unique identifier of the monitored function | You has to use it when accessing Instrumentation API |
namePtr | Pointer to the unmanaged string that is containing full name of the method | This value is null for some methods. |
argsCount | Number of arguments that the monitored function has. | Note, that the non-static method has this argument as a zero parameter. |
parameters | Arguments of the monitored function | Note, that the non-static method has this argument as a zero parameter. |
threadContext | Current thread context Current thread context | |
producerData | Data, collected by postfix producer | |
exc | Exception, thrown by monitored function |
Actually producer can return any object. You have to define what value producer will return based on the goal of producer. Producer can be written for:
If you write producer, interesting for the regular Agent monitoring you have to return string value, containing XML fragment. Format of that XML fragment is not documented and is a value to change. Please, use Instrumentation API.
The scenario when you write producer, interesting for Actions only is not supported and you AVIcode do not give any guarantees that everything will working OK.
Instrumentation API has a number of functions to get the information about current process, current domain and Agent status. Also this API has a class that can be used to specify what information you collect from producer.
Using ObjectContainer class:
Constructor has no parameters.
You can add object that is needed to collect. All members of this object will be collected with the using default settings for deep and number of methods to collect. Also you can specify the collecting template.
public void AddObjectChildren(string name, object objectToCollect)
public void AddObjectChildren(string name, object objectToCollect, string collectionDescription) |
You can add simple values. In this case you have to specify type and a value, represented in string.
public void AddValueChildren(string name, string value, Type type)
public void AddValueChildren(string name, string value, string type) |
You can add complex values – arrays or objects. In this case you get the ObjectContainer in which you can add other values.
public ObjectContainer AddArrayChildren(string name, Type type)
public ObjectContainer AddArrayChildren(string name, string type)
public ObjectContainer AddClassChildren(string name, Type type)
public ObjectContainer AddClassChildren(string name, string type) |
public object GetDataProducerResult() |
Function returns the Data Producer’s result. The XML fragment of event. This Fragment need to be returned from Data Producer function.
if the exception occurred in the producer and you can’t process it by yourself you have to return
ObjectContainer.GetOnExceptionEmptyResult(Exception exc) |
if you want your EntryPoint not to be collected you have to return
ObjectContainer.GetConditionNotCollectResult() |
OnException producer gets additional parameter – producerData. if you want to collect this data, collected by postfix producer you can add it using method:
ObjectContainer.AddProducerResultChildren(object producerResultChildren) |
The following is a simple producer that collects two parameters: Name and NewExecutionStatus.
public static Object System_Workflow_ComponentModel_Activity_SetStatus_Postfix( Int32 functionID, Int64 namePtr, Int32 argsCount, Object[] parameters, Object threadContext) { ObjectContainer virtualSer = new ObjectContainer();
Activity thisObj = (Activity)parameters[0];
virtualSer.AddObjectChildren( "NewExecutionStatus", ((ActivityExecutionStatus)parameters[1]).ToString());
virtualSer.AddObjectChildren("Name", thisObj.QualifiedName);
return virtualSer.GetDataProducerResult(); }
private static object GetPrivateField(Type t, string field, Object instance) { try { return t.InvokeMember( field, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null, CultureInfo.InvariantCulture); } catch { //Need process exception //- Extended logic to determine - report exception or not } return null; }
private static void FillActivityInformation( ObjectContainer ser, Activity Activity) { if (Activity == null) { return; }
//Name ser.AddObjectChildren("Name", Activity.QualifiedName);
//Description if ( (null != Activity) && (0 != Activity.Description.Length) ) { ser.AddObjectChildren("Activity Description", Activity.Description); }
//Dependant properties IDictionary<DependencyProperty, object> dependantProperties = null; try { Type t = typeof(System.Workflow.ComponentModel.DependencyObject);
dependantProperties= (IDictionary<DependencyProperty,object>) GetPrivateField(t, "dependencyPropertyValues", Activity); } catch { }
if (null != dependantProperties) { ObjectContainer properties = ser.AddArrayChildren( "DependantProperties", "IDictionary<DependencyProperty, object>");
foreach (KeyValuePair<DependencyProperty, object> property in dependantProperties) { properties.AddObjectChildren(property.Key.Name, property.Value); } } }
public static Object System_Workflow_Runtime_WorkflowExecutor_RunScheduler_Postfix(Int32 functionID, Int64 namePtr, Int32 argsCount, Object[] parameters, Object threadContext) {
ObjectContainer virtualSer = new ObjectContainer();
Activity currentAtomicActivity = null; Activity rootActivity = null;
Object thisObj = parameters[0];
if (null != thisObj) { Type t = thisObj.GetType();
try { //this.currentAtomicActivity - current transaction currentAtomicActivity = (Activity)t.InvokeMember( "currentAtomicActivity", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, parameters[0], null, CultureInfo.InvariantCulture); } catch { }
try { //this.rootActivity - root Activity rootActivity = (Activity)t.InvokeMember( "rootActivity", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, parameters[0], null, CultureInfo.InvariantCulture); } catch { } }
ArrayList par = new ArrayList();
if (null != currentAtomicActivity) { ObjectContainer currentActivitySer = virtualSer.AddClassChildren("currentAtomicActivity", currentAtomicActivity.GetType());
FillActivityInformation(currentActivitySer, currentAtomicActivity); }
if (null != rootActivity) { ObjectContainer rootActivitySer = virtualSer.AddClassChildren("currentAtomicActivity", rootActivity.GetType());
FillActivityInformation(rootActivitySer, rootActivity); }
return virtualSer.GetDataProducerResult(); } |
Last update: Thursday, December 09, 2010 02:07:57 PM