Using XSLT to generate Performance Counters code

April 11th, 2009

Whenever I’m faced with a project in which I have to create a lot of tedious and repeating code I turn to the power of XML and XSLT. Rather than copy/paste the same code over and over again, just to end up with a refactoring and maintenance nightmare, I create an XML definition file and an XSLT transformation. I am then free to add new elements to the XML definition or to change the way the final code is generated from the XSLT transformation. This can be fully integrated with Visual Studio so that the code generation happens at project build time and the environment shows the generated code as a dependency of the XML definition file.

A few examples of how I’m using this code generation via XSLT are:

Data Access Layer
I know this will raise quite a few eyebrows, but I always write my own data access layer from scratch and is generated via XSLT.
Performance Counters
I create all my performance counters objects via XSLT generation, automating the process of defining/installing them and the access to emit and consume the counter values.
Wire Frames
In any project that has networking communication I access the wire format from classes generated via XSLT that take care of serialization and validation.

For example I’ll show how to create a class library that can be added to your project to expose Performance Counters from your application.

MSXSL.EXE

To start you’ll need to download the Command Line Transformation Utility (msxsl.exe). This is a command line tool that takes an XML and an XSLT file as input and produces the result transformation. As a side note I actually use the same technique on non-Windows platforms, but there of course I use the xsltproc utility.

msxsl.exe has to be placed in a convenient location from where it is accessible by Visual Studio. Download the file and place it in Visual Studio’s Common\IDE folder because the build environment has a macro for it: $DevEnvDir.

The Project

Create a new project in Visual Studio. Choose a C# Class Library type project and save it as “PerformanceCounters”. After the project is created, add to it two new files, from menu Project/Add New Item or press Ctrl+Shift+A. For the first file choose an XML file type and name it Counters.xml. For the second one choose an XSLT file type and name it Counters.xslt.

Next add the step that actually performs XSLT transformation to the project build process. Go to the project properties, either from menu Project/PerformcanceCounters properties... or right-click the project in the Solutions Explorer and select Properties from the context menu. Choose the Build Events pane in the right side navigation tab to get to the pre-build and post-build project events. Click the Edit pre-build... button and add the following command:

"$(DevEnvDir)msxsl.exe" "$(ProjectDir)Counters.xml" "$(ProjectDir)Counters.xslt"
-o "$(ProjectDir)CountersGenerated.cs"

This command will run the command line transformation tool (msxsl.exe) and generate the CountersGenerated.cs file each time the project is built, prior to the project being actually built.

Save and close the project properties. The last thing we need now is to add the generated CountersGenerated.cs file to our project. However, we do not want this to be treated as an ordinary source file, we want the IDE to recognize that is a generated file dependent of the Counters.xml file. The way I prefer to do this is to actually manually add it directly into the project definition file. Open the PerformanceCounters.csproj file in your editor, locate the <ItemGroup> containing the Program.cs and insert this snippet:

	  <Compile Include="CountersGenerated.cs">
		  <AutoGen>True</AutoGen>
		  <DependentUpon>Counters.xml</DependentUpon>
	  </Compile>

Save the manually edited .csproj file and open the project normally from Visual Studio. The CountersGenerated.cs file now shows up in the Project Explorer as a generated file depending on Counters.xml:

The warning triangle is shown because the file does not exist on the disk, but don't worry about it as it will be generated shortly.

Defining the Performance Counters

We can go ahead and define the Performance Counters. The counters covering related functionality are grouped together and the groups can be single instance or multiple instance types. I also like to create a manager class that controls the management of the counters taking care of things like deployment during application setup and loading the counters when application starts. So my choice is for a XML like manager/group/counter. Some attributes are needed for the code generation XSLT transformation to know what class names and namespaces to use and some attributes are Performance Counters specific, like the counter types and bases for average counters. For start we'll create a simple counter and a group:

<?xml version="1.0" encoding="utf-8" ?>
<
manager xmlns="http://rusanu.com/Schemas/PerformanceCounters/v1.0"
clrname="PerformanceCountersManager" namespace="PerformanceCounters">
<
group name="My Counters" clrname="MyCounters" type="SingleInstance">
<
description>Demo Counters for XSLT code generation project</description>
<
counter name="My Count" type="NumberOfItems32"  clrname="MyCount" hasbase="No">Demo Counter</counter>
</
group>
</
manager>

This definition will create:

  • A manager class named PerformanceCounters.PerformanceCountersManager.
  • A performance counters category named My Group.
  • A C# class named MyGroup for the above performance counters category.
  • A performance counter named My Count.
  • A C# method named IncrementMyCount to increment the counter above.

Now we need to set in place the actual XSLT transformation that will generate code as we desire.

The XSTL transformation

We want our XSLT transformation to generate C# code, so we're going to start by a stylesheet that will generate the skeleton of a C# file: the using directives and some comment to warn the user that the file is a generated file and should not be manually modified. Also our XSLT transformation is directed to generate an text output as opposed to the default XML output:

<?xml version="1.0" encoding="UTF-8" ?>
<
xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:pc="http://rusanu.com/Schemas/PerformanceCounters/v1.0">
<
xsl:output method="text" encoding="utf-8"/>
<
xsl:template match="/">
/* This file was automatically generated during project build.
Do not manually modify this file as updates will be lost.*/
using System;
using System.Diagnostics;

<xsl:apply-templates/>
</
xsl:template>
</
xsl:stylesheet>

This skeleton XSLT can be used for any C# code generation, provided of course you modify the appropriate using directives.

Next we should add a template to generate our C# code. How one writes XSLT transformation templates is largely a matter of style and once you get the hang of it it quickly becomes a second nature. XSLT is a fully fledged programming language of its own and you can start thinking at templates in terms of procedures and functions. At least I know I do :). Our XSLT template should do the following:

  • Identify the manager defined in the XML file and generate a C# class for it.
  • Generate a class for each performance counter category.
  • Generate a method for each individual performance counter value to read and increment the counter.
  • Hook up the manager class to provide methods for deploying and deleting the performance counter categories.
  • Hook up the manager class with an instance of each performance counter category class.
  • Hook up the manager to provide loading of the default instance of the performance counters category classes.

Note that the above refer mostly to the SingleInstance performance counter category types. Multiple Instance category are a bitmore complex and I will cover them in my next post.

<xsl:template match="/pc:manager">
namespace <xsl:value-of select="@namespace"/>
{
public partial class 
<xsl:value-of select="@clrname"/>
{
<xsl:for-each select="./pc:group[@type='SingleInstance']">
private static <xsl:value-of select="@clrname"/> __<xsl:value-of select="@clrname"/> = 
new 
<xsl:value-of select="@clrname"/>();
public static 
<xsl:value-of select="@clrname"/> Default<xsl:value-of select="@clrname"/> {
get {return __
<xsl:value-of select="@clrname"/>; } 
}
</xsl:for-each>

public static void LoadSingleInstance(bool fReadOnly)
{
<xsl:for-each select="./pc:group[@type='SingleInstance']">
__<xsl:value-of select="@clrname"/>.Load(fReadOnly);
</xsl:for-each>
}

public static void DeleteSingleInstance()
{
<xsl:for-each select="./pc:group[@type='SingleInstance']">
<
xsl:value-of select="@clrname"/>.Delete();
</xsl:for-each>
}

public static void CreateSingleInstance()
{
<xsl:for-each select="./pc:group[@type='SingleInstance']">
<
xsl:value-of select="@clrname"/>.Create();
</xsl:for-each>
}
}

<xsl:for-each select="./pc:group">
public partial class <xsl:value-of select="@clrname"/>
{
private const string __description = 
@"
<xsl:value-of select="./pc:description/text()"/>";
private const string __category = 
@"
<xsl:value-of select="@name"/>";

<
xsl:for-each select="./pc:counter">
  private PerformanceCounter _<xsl:value-of select="@clrname"/>;
<xsl:if test="@hasbase='Yes'">
private PerformanceCounter _<xsl:value-of select="@clrname"/>Base;
</xsl:if>

public void Increment<xsl:value-of select="@clrname"/>(long value)
{
_
<xsl:value-of select="@clrname"/>.IncrementBy(value);
<xsl:if test="@hasbase='Yes'">
_<xsl:value-of select="@clrname"/>Base.IncrementBy(1);
</xsl:if>
}

public float <xsl:value-of select="@clrname"/> {
get { return _
<xsl:value-of select="@clrname"/>.NextValue(); } 
}

</
xsl:for-each>

<
xsl:if test="@type='MultipleInstance'">
   internal void Load(bool fReadOnly, string instanceName) {
    <xsl:for-each select="./pc:counter">
      _<xsl:value-of select="@clrname"/> = new PerformanceCounter(
      __category,
      @"
<xsl:value-of select="@name"/>",
      instanceName,
      fReadOnly);
      <xsl:if test="@hasbase='Yes'">
        _<xsl:value-of select="@clrname"/>Base = new PerformanceCounter(
        __category,
        @"
<xsl:value-of select="@name"/>Base",
        instanceName,
        fReadOnly);
      </xsl:if>
    </
xsl:for-each>
    }
</xsl:if>

<
xsl:if test="@type='SingleInstance'">
    internal void Load (bool fReadOnly)
    {
    <xsl:for-each select="./pc:counter">
      _<xsl:value-of select="@clrname"/> = new PerformanceCounter(
      __category,
      @"
<xsl:value-of select="@name"/>",
      fReadOnly);
      <xsl:if test="@hasbase='Yes'">
        _<xsl:value-of select="@clrname"/>Base = new PerformanceCounter(
        __category,
        @"
<xsl:value-of select="@name"/>Base",
        fReadOnly);
      </xsl:if>    </xsl:for-each>
    }
</xsl:if>

   internal static bool Exists
    {
get {return PerformanceCounterCategory.Exists(__category);}
    }

    internal static void Delete ()
    {
PerformanceCounterCategory.Delete(__category);
    }

    public static void Create ()
    {
CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
<xsl:for-each select="./pc:counter">
ccdc.Add(new CounterCreationData (
@"
<xsl:value-of select="@name"/>",
@"
<xsl:value-of select="."/>",
PerformanceCounterType.
<xsl:value-of select="@type"/>));
<xsl:if test="@hasbase='Yes'">
ccdc.Add(new CounterCreationData (
@"
<xsl:value-of select="@name"/>Base",
@"Base for 
<xsl:value-of select="."/>",
PerformanceCounterType.AverageBase));
</xsl:if>
</
xsl:for-each>
PerformanceCounterCategory.Create(
__category,
__description,
PerformanceCounterCategoryType.
<xsl:value-of select="@type"/>,
ccdc);
    }
}
 
</
xsl:for-each>
}
</xsl:template>

The generate code

If we go ahead and build the project. It will run our XSLT transformation over the definition XML file and the result will be saved in the CountersGenerated.cs file:

/* This file was automatically generated during project build.
Do not manually modify this file as updates will be lost.*/
using System;
using System.Diagnostics;


namespace PerformanceCounters
{
public partial class PerformanceCountersManager
{

private static MyCounters __MyCounters = 
new MyCounters();
public static MyCounters DefaultMyCounters {
get {return __MyCounters; } 
}


public static void LoadSingleInstance(bool fReadOnly)
{

__MyCounters.Load(fReadOnly);

}

public static void DeleteSingleInstance()
{
MyCounters.Delete();

}

public static void CreateSingleInstance()
{
MyCounters.Create();

}
}


public partial class MyCounters
{
private const string __description = 
@"Demo Counters for XSLT code generation project";
private const string __category = 
@"My Counters";


 
private PerformanceCounter _MyCount;


public void IncrementMyCount(long value)
{
_MyCount.IncrementBy(value);

}

public float MyCount {
get { return _MyCount.NextValue(); } 
}


    
internal void Load (bool fReadOnly)
    {
    
      _MyCount = 
new PerformanceCounter(
      __category,
      
@"My Count",
      fReadOnly);
      
    }


   
internal static bool Exists
    {
get {return PerformanceCounterCategory.Exists(__category);}
    }

    
internal static void Delete ()
    {
PerformanceCounterCategory.Delete(__category);
    }

    
public static void Create ()
    {
CounterCreationDataCollection ccdc = 
            
new CounterCreationDataCollection();

ccdc.Add(
new CounterCreationData (
@"My Count",
@"Demo Counter",
PerformanceCounterType.NumberOfItems32));

PerformanceCounterCategory.Create(
__category,
__description,
PerformanceCounterCategoryType.SingleInstance,
ccdc);
    }
}
 

}

So what did we achieve? We could had written this code manually in about 5 minutes. But the big advantage comes as we add more counters to our XML definition file. We could expand it to have 10 categories with 15 counters each and it would still generate all the needed counters. And refactoring the code is a breeze. Don't like how the counters are created? Just change the XSLT stylesheet and rebuild the project, all the performance counters creation code wil match your new preference.

In a future post I will cover how to use the performance counters in your projects, how to work with multiple instance counters and how to deal with the pesky AvgTimer32 counter type.

Comments are closed.