Appendix - Custom Producer API

Introduction

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

Data Producer Assembly

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]

Configuration settings

Monitored function types

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.

Writing configuration files

You can specify Data Producers for your methods:

Specify Data Producer assembly

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>

Specify producers for the monitored function

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>

Designing a Producer

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)

Producer Arguments

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  

Producer return value

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 for writing producer

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.

ObjectContainer class

Using ObjectContainer class:

Constructor

Constructor has no parameters.

Adding values

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)

Get return value

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.

Error processing

if the exception occurred in the producer and you can’t process it by yourself you have to return

ObjectContainer.GetOnExceptionEmptyResult(Exception exc)

Specific of the Prefix producer

if you want your EntryPoint not to be collected you have to return

ObjectContainer.GetConditionNotCollectResult()

Specific of the OnException producer

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)

Data Producer Example

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