|
In This Chapter Accessing Built-in Performance Counters Implementing Timers Building a Customized Performance Counter Analyzing Performance with Sampling
It's agreed that a program must run correctly and produce accurate results, but in many systems this isn't enough. Enterprise-class applications are of such mass that they must also be scalable. Verifying the scalability of an application traditionally requires specialized tools and bolted-on functionality to support monitoring. Now there's help, using the performance counter capability of the System.Diagnostics namespace. Performance counters present an object framework for supporting application monitoring. The framework hooks into the operating system performance counter system to access available counters. Additionally, the performance counter framework can be extended for customized counters and data sampling. Such samples may be collected efficiently with timers, which, as their name suggests, enable periodic execution of logic via specified time intervals. By using either built-in or customized performance counters, a program can be monitored under various conditions to verify its performance and scalability. Accessing Built-in Performance CountersThe performance counter framework provides access to existing operating system counters. The help files associated with the operating system performance monitor application have more information on what counters are available. Using a performance counter involves declaring a PerformanceCounter object, initializing its properties as desired and requesting the counter value at various intervals to watch performance. The program in Listings 32.1 and 32.2 comprise a fictitious ordering system that demonstrates the use of system performance counters. The program is sufficiently equipped to degrade system performance, where the effects can be observed by watching the performance counter. Listing 32.1 System Performance Counter Demo: OrderClient.cs using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; using System.Diagnostics;
namespace OrderingClient { /// <summary> /// Performance Counter Demo. /// </summary> public class OrderClient : System.Windows.Forms.Form { private System.Windows.Forms.Label maxOrdLbl; private System.Windows.Forms.Label curOrdLbl; private System.Windows.Forms.TextBox maxOrdTxt; private System.Windows.Forms.Label curOrdResultLbl; private System.Windows.Forms.Button updateBtn; private int maxOrders; private int curOrders; private OrderProcessor orderProc; private System.Windows.Forms.Timer orderTimer; private System.Windows.Forms.Timer countTimer; private System.Windows.Forms.Label threadLbl; private System.Windows.Forms.Label threadResultLbl;
private System.Diagnostics.PerformanceCounter threadCounter
private System.ComponentModel.IContainer components; public OrderClient() { InitializeComponent(); maxOrders = 10; maxOrdTxt.Text = maxOrders.ToString(); orderProc = new OrderProcessor(); curOrders = orderProc.CurNoOrders; curOrdResultLbl.Text = curOrders.ToString(); } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.maxOrdTxt = new System.Windows.Forms.TextBox(); this.updateBtn = new System.Windows.Forms.Button(); this.maxOrdLbl = new System.Windows.Forms.Label(); this.curOrdLbl = new System.Windows.Forms.Label(); this.orderTimer = new System.Windows.Forms.Timer( this.components); this.curOrdResultLbl = new System.Windows.Forms.Label(); this.countTimer = new System.Windows.Forms.Timer( this.components); this.threadLbl = new System.Windows.Forms.Label(); this.threadResultLbl = new System.Windows.Forms.Label(); this.threadCounter = new System.Diagnostics.PerformanceCounter(); ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).BeginInit(); this.SuspendLayout();
this.maxOrdTxt.Location = new System.Drawing.Point(152, 24); this.maxOrdTxt.Name = "maxOrdTxt"; this.maxOrdTxt.TabIndex = 2; this.maxOrdTxt.Text = ""; this.maxOrdTxt.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.updateBtn.Location = new System.Drawing.Point(104, 152); this.updateBtn.Name = "updateBtn"; this.updateBtn.TabIndex = 4; this.updateBtn.Text = "Update"; this.updateBtn.Click += new System.EventHandler(this.updateBtn_Click);
this.maxOrdLbl.Location = new System.Drawing.Point(40, 24); this.maxOrdLbl.Name = "maxOrdLbl"; this.maxOrdLbl.TabIndex = 0; this.maxOrdLbl.Text = "Max Orders:"; this.maxOrdLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.curOrdLbl.Location = new System.Drawing.Point(40, 64); this.curOrdLbl.Name = "curOrdLbl"; this.curOrdLbl.TabIndex = 1; this.curOrdLbl.Text = "Current Orders:"; this.curOrdLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.orderTimer.Enabled = true; this.orderTimer.Interval = 2000; this.orderTimer.Tick += new System.EventHandler(this.orderTimer_Tick);
this.curOrdResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.curOrdResultLbl.Location = new System.Drawing.Point(152, 64); this.curOrdResultLbl.Name = "curOrdResultLbl"; this.curOrdResultLbl.Size = new System.Drawing.Size(100, 20); this.curOrdResultLbl.TabIndex = 3; this.curOrdResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.countTimer.Enabled = true; this.countTimer.Interval = 1000; this.countTimer.Tick += new System.EventHandler(this.countTimer_Tick);
this.threadLbl.Location = new System.Drawing.Point(40, 104); this.threadLbl.Name = "threadLbl"; this.threadLbl.TabIndex = 1; this.threadLbl.Text = "Thread Count:"; this.threadLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.threadResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.threadResultLbl.Location = new System.Drawing.Point(152, 104); this.threadResultLbl.Name = "threadResultLbl"; this.threadResultLbl.Size = new System.Drawing.Size(100, 20); this.threadResultLbl.TabIndex = 3; this.threadResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.threadCounter.CategoryName = ".NET CLR LocksAndThreads"; this.threadCounter.CounterName = "# of current physical Threads"; this.threadCounter.InstanceName = "OrderingClient";
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(288, 197); this.Controls.AddRange( new System.Windows.Forms.Control[] { this.threadLbl, this.threadResultLbl, this.updateBtn, this.curOrdResultLbl, this.maxOrdTxt, this.curOrdLbl, this.maxOrdLbl}); this.Name = "OrderClient"; this.Text = "Order Client"; ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).EndInit(); this.ResumeLayout(false); }
static void Main() { Application.Run(new OrderClient()); } private void updateBtn_Click(object sender, System.EventArgs e) { maxOrders = Convert.ToInt32(maxOrdTxt.Text); } private void orderTimer_Tick(object sender, System.EventArgs e) { orderTimer.Enabled = false; Thread th = new Thread(new ThreadStart(ProcessOrders)); th.Start(); orderTimer.Enabled = true; } private void countTimer_Tick(object sender, System.EventArgs e) { countTimer.Enabled = false; curOrdResultLbl.Text = orderProc.CurNoOrders.ToString(); threadResultLbl.Text = threadCounter.NextValue().ToString(); countTimer.Enabled = true; } private void ProcessOrders() { for (curOrders = orderProc.CurNoOrders; curOrders <= maxOrders; curOrders++) { curOrdResultLbl.Text = curOrders.ToString(); orderProc.ProcessOrder(); } } } } Listing 32.2 Server Component of System Performance Counter Demo: OrderProcessor.csusing System; using System.Threading;
namespace OrderingClient { /// <summary> /// Summary description for OrderProcessor. /// </summary> public class OrderProcessor { private static int curNoOrders = 0; private Random rand; public OrderProcessor() { rand = new Random(); } public int ProcessOrder() { Thread th = new Thread(new ThreadStart(doOrder)); th.Start();
curNoOrders++; return 0; } public int CurNoOrders { get { return curNoOrders; } set { curNoOrders = value; } } private void doOrder() { for (int delay = rand.Next(10000000); delay >= 0; delay—) ; curNoOrders—; } } } The performance counter framework belongs to the System.Diagnostics namespace. Performance counters are declared like any other class as follows:
private System.Diagnostics.PerformanceCounter threadCounter; This particular performance counter keeps track of the number of .NET Common Language Runtime (CLR) threads. There are three pertinent properties of a performance counter that are required: CategoryName, CounterName, and InstanceName as shown next. this.threadCounter.CategoryName = ".NET CLR LocksAndThreads"; this.threadCounter.CounterName = "# of current physical Threads"; this.threadCounter.InstanceName = "OrderingClient"; Performance counters are broken into categories that help organize each counter into a logical related group. The preceding example sets the category for the threadCounter object to ".NET CLR LocksAndThreads". An examination of this category in the .NET Framework Documentation shows that this category has counters for different types of threads and other counters associated with thread synchronization. This example assigns the "# of current physical Threads" counter to the CounterName property of the threadCounter object. The InstanceName property holds the name of the executable file whose count property will be monitored. To get the value of the counter, call the NextValue() method of the PerformanceCounter object. The following example shows how to do this: threadResultLbl.Text = threadCounter.NextValue().ToString(); The example converts the integer value returned from the NextValue() method into a string and places it into a Windows forms label control for presentation onscreen.  Fig 32.1
The above picture shows what the code from Listings 32.1 and 32.2 look like when compiled and executed. Increasing the number in the Max Orders text box stresses the system. This can be observed by watching the numbers change more sluggishly, indicating performance degradation. Here are the compilation instructions: csc /t:winexe /out:OrderingClient.exe OrderClient.cs OrderProcessor.cs Figure 32.1 A system performance counter. The program from Listings 32.1 and 32.2 use threads extensively. OrderClient uses threads to execute its loop efficiently. It finishes quickly so it doesn't hold up any other program activities, such as the ability to update Max Orders, update the count fields, and execute timers. The OrderProcessor class uses threads so it can accept orders efficiently without making the client block for each order. Otherwise, there would be no telling how long it could take to process an order, because the program is set to take a random amount of time for each order. This simulates the nature of many ordering systems, which typically have multiple types of orders and several options or variables that make the amount of time for each order practically unpredictable.
Tip As the world turns, Moore's law (which states that the speed of computers doubles every 18–24 months) has my faithful but inadequate computer dragging behind in performance. If you don't experience significant performance hits when incrementing Max Orders in this program, bump up the number of zeros in the rand.Next() method in the for loop initializer of the doOrder() method in Listing 32.2.
Implementing TimersIt would be easy to use existing C# constructs, such as sleeping threads or for and while loops, to control the periodic collection of performance counter data. The primary problems with these methods are their synchronous nature. Furthermore, loops like for and while deliver a significant performance hit. A better solution for performing logic via specified intervals is the timer. A timer can be set for a specified time interval, executing a callback routine whenever that interval elapses. The primary benefit of this approach is the asynchronous behavior of the timer, which delivers much better performance than the synchronous methods discussed earlier. Just set the timer interval, assign a callback routine to execute, and then move on and process the rest of the program logic. The following example shows how timers are declared: private System.Windows.Forms.Timer orderTimer; private System.Windows.Forms.Timer countTimer; These timers are members of the System.Windows.Forms namespace. The orderTimer will fire periodically to make sure the number of orders being processed go up to, but not over, the maximum number of orders. The countTimer fires periodically to update the number of orders being processed and to get and display the current value of the threadCounter performance counter. Here's an example of how the timers are set up: this.orderTimer.Enabled = true; this.orderTimer.Interval = 2000; this.orderTimer.Tick += new System.EventHandler(this.orderTimer_Tick); this.countTimer.Enabled = true; this.countTimer.Interval = 1000; this.countTimer.Tick += new System.EventHandler(this.countTimer_Tick); Both of these timers have their Enabled properties set to true, meaning that the timers are turned on. A timer can be turned off by setting the Enabled property to false; this is necessary when a program is in the middle of a callback and doesn't want the timer firing while a previous callback based on that timer is still executing. Setting the Interval property to 1000 makes the timer tick approximately every second. Thus, the orderTimer will tick in about two seconds, and the count timer will tick about once per second.
Warning Being based on the underlying operating system timer, don't bet on timers having a great degree of accuracy. This is because there are various operating system events that may preclude the tick event from firing on time; therefore, the safest assumption to make with timers is that they provide an approximate timing mechanism.
Callback routines are attached to the Tick event of a timer with the EventHandler delegate. The orderTimer timer calls the ordertimer_Tick() method, and the countTimer timer calls the countTimer_Tick() method when their respective Tick events fire. Listing 32.1 has the full code that shows what these routines do when their Tick event fires. Building a Customized Performance CounterOften the system performance counters are enough for monitoring a system's performance. However, sometimes you need a specialized counter that gives a unique picture of what's happening in a specific program. Making customized performance counters is possible, due to the extensible nature of the performance counter framework. Implementing a customized performance counter requires creating a new counter type and a new category to hold the new counter. The performance counter will be instantiated with the new counter and category definitions. Additional logic is also necessary to load the custom performance counter with program specific data. Listings 32.3 and 32.4 show how to implement custom performance counters.Listing 32.3 Client Using Data from Custom Performance Counter: CustomOrderClient.cs Listing 32.3 Client Using Data from Custom Performance Counter: CustomOrderClient.csusing System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; using System.Diagnostics; namespace OrderingClient { /// <summary> /// Summary description for Form1. /// </summary> public class CustomClient : System.Windows.Forms.Form { private System.Windows.Forms.Label maxOrdLbl; private System.Windows.Forms.Label curOrdLbl; private System.Windows.Forms.TextBox maxOrdTxt; private System.Windows.Forms. curOrders = orderProc.CurNoOrders; curOrdResultLbl.Text = curOrders.ToString(); } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.maxOrdTxt = new System.Windows.Forms.TextBox(); this.threadLbl = new System.Windows.Forms.Label(); this.orderTimer = new System.Windows.Forms.Timer(this.components); this.updateBtn = new System.Windows.Forms.Button(); this.threadResultLbl = new System.Windows.Forms.Label(); this.curOrdResultLbl = new System.Windows.Forms.Label(); this.threadCounter = new System.Diagnostics.PerformanceCounter(); this.curOrdLbl = new System.Windows.Forms.Label(); this.countTimer = new System.Windows.Forms.Timer(this.components); this.maxOrdLbl = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).BeginInit(); this.SuspendLayout();
this.maxOrdTxt.Location = new System.Drawing.Point(152, 24); this.maxOrdTxt.Name = "maxOrdTxt"; this.maxOrdTxt.TabIndex = 2; this.maxOrdTxt.Text = ""; this.maxOrdTxt.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.threadLbl.Location = new System.Drawing.Point(40, 104); this.threadLbl.Name = "threadLbl"; this.threadLbl.TabIndex = 1; this.threadLbl.Text = "Thread Count:"; this.threadLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.orderTimer.Enabled = true; this.orderTimer.Interval = 2000; this.orderTimer.Tick += new System.EventHandler(this.orderTimer_Tick);
this.updateBtn.Location = new System.Drawing.Point(104, 152); this.updateBtn.Name = "updateBtn"; this.updateBtn.TabIndex = 4; this.updateBtn.Text = "Update"; this.updateBtn.Click += new System.EventHandler(this.updateBtn_Click);
this.threadResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.threadResultLbl.Location = new System.Drawing.Point(152, 104); this.threadResultLbl.Name = "threadResultLbl"; this.threadResultLbl.Size = new System.Drawing.Size(100, 20); this.threadResultLbl.TabIndex = 3; this.threadResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.curOrdResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.curOrdResultLbl.Location = new System.Drawing.Point(152, 64); this.curOrdResultLbl.Name = "curOrdResultLbl"; this.curOrdResultLbl.Size = new System.Drawing.Size(100, 20); this.curOrdResultLbl.TabIndex = 3; this.curOrdResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.threadCounter.CategoryName = ".NET CLR LocksAndThreads"; this.threadCounter.CounterName = "# of current physical Threads"; this.threadCounter.InstanceName = "CustomClient";
this.curOrdLbl.Location = new System.Drawing.Point(40, 64); this.curOrdLbl.Name = "curOrdLbl"; this.curOrdLbl.TabIndex = 1; this.curOrdLbl.Text = "Current Orders:"; this.curOrdLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.countTimer.Enabled = true; this.countTimer.Interval = 1000; this.countTimer.Tick += new System.EventHandler(this.countTimer_Tick);
this.maxOrdLbl.Location = new System.Drawing.Point(40, 24); this.maxOrdLbl.Name = "maxOrdLbl"; this.maxOrdLbl.TabIndex = 0; this.maxOrdLbl.Text = "Max Orders:"; this.maxOrdLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight; this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(288, 197); this.Controls.AddRange( new System.Windows.Forms.Control[] { this.threadLbl, this.threadResultLbl, this.updateBtn, this.curOrdResultLbl, this.maxOrdTxt, this.curOrdLbl, this.maxOrdLbl}); this.Name = "CustomClient"; this.Text = "Custom Client"; this.Closing += new System.ComponentModel.CancelEventHandler( this.CustomClient_Closing); ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).EndInit(); this.ResumeLayout(false); } static void Main() { Application.Run(new CustomClient()); } private void updateBtn_Click( object sender, System.EventArgs e) { maxOrders = Convert.ToInt32(maxOrdTxt.Text); } private void orderTimer_Tick( object sender, System.EventArgs e) { orderTimer.Enabled = false;
Thread th = new Thread( new ThreadStart(ProcessOrders)); th.Start(); orderTimer.Enabled = true; } private void countTimer_Tick( object sender, System.EventArgs e) { countTimer.Enabled = false;
curOrdResultLbl.Text = orderProc.CurNoOrders.ToString(); threadResultLbl.Text = threadCounter.NextValue().ToString();
countTimer.Enabled = true; } private void ProcessOrders() { for (curOrders = orderProc.CurNoOrders; curOrders <= maxOrders; curOrders++) { curOrdResultLbl.Text = curOrders.ToString(); orderProc.ProcessOrder(); } } private void CustomClient_Closing(object sender, System.ComponentModel.CancelEventArgs e) { orderProc.Dispose(); } } } Listing 32.4 Server Implementing a Custom Performance Counter: CustomOrderProcessor.csusing System; using System.Threading; using System.Diagnostics; namespace OrderingClient { /// <summary> /// Summary description for CustomOrderProcessor. /// </summary> public class CustomOrderProcessor : IDisposable { private PerformanceCounter orderCounter; private Random rand; public CustomOrderProcessor() { rand = new Random();
CounterCreationDataCollection myCounters = new CounterCreationDataCollection(); CounterCreationData myCounterCreationData = new CounterCreationData();
myCounterCreationData.CounterName = "Order Count"; myCounterCreationData.CounterHelp = "Displays number of orders being processed."; myCounterCreationData.CounterType = PerformanceCounterType.NumberOfItems32; myCounters.Add(myCounterCreationData);
if (PerformanceCounterCategory.Exists( "Order Processor")) { PerformanceCounterCategory.Delete( "Order Processor"); } PerformanceCounterCategory.Create( "Order Processor", "OrderProcessor class counters", myCounters);
orderCounter = new PerformanceCounter( "Order Processor", "Order Count", false); orderCounter.RawValue = 0; } public int ProcessOrder() { Thread th = new Thread(new ThreadStart(doOrder)); th.Start(); CurNoOrders++; return 0; } public int CurNoOrders { get { return (int)orderCounter.NextValue(); } set { orderCounter.RawValue = value; } } private void doOrder() { for (int delay = rand.Next(1000000); delay >= 0; delay—) ; CurNoOrders—; } public void Dispose() { PerformanceCounterCategory.Delete( "Order Processor"); } } } The interesting bits of this program are in Listing 32.4. The custom counter is initialized in the constructor, and the updates are managed with the CurNoOrders property. The two primary classes supporting custom counters are the CounterCreationDataCollection and CounterCreationData, which are each instantiated with default constructors, as shown here: CounterCreationDataCollection myCounters = new CounterCreationDataCollection(); CounterCreationData myCounterCreationData = new CounterCreationData();
The CounterCreationData class holds counter definition properties that must be set to create a new counter. The CounterName property is a user-defined name of a counter. The CounterType property may be any member of the PerformanceCounterType enum, which are listed in Table 32.1. The following code sets the CounterCreationData properties, including the CounterHelp property, which is a description of the custom counter: myCounterCreationData.CounterName = "Order Count"; myCounterCreationData.CounterHelp = "Displays number of orders being processed."; myCounterCreationData.CounterType = PerformanceCounterType.NumberOfItems32; Table 32.1 Members of the PerformanceCounterType EnumCounter Name | Description | AverageBase | Denominator for AverageCount32 and AverageCount64 | AverageCount64 | 64-bit average count | AverageCount32 | 32-bit average count | AverageTimer32 | 32-bit average elapsed time | CounterDelta32 | 32-bit difference between counts | CounterDelta64 | 64-bit difference between counts | CounterMultiBase | Denominator for CounterMultiTimer, CounterMultiTimerInverse, CounterMultiTimer100Ns, and CounterMultiTimer 100NsInverse | CounterMultiTimer | Multiple time samplings—in use | CounterMultiTimer100Ns | Multiple time samplings in 100 nanosecond units | CounterMultiTimerInverse | Multiple time samplings—not in use | CounterTimer | Time sampling—in use | CounterTimerInverse | Time sampling—not in use | CountPerTimeInterval32 | 32-bit count per time interval | CountPerTimeInterval64 | 64-bit count per time interval | ElapsedTime | Difference between timer start and sample | NumberOfItems32 | 32-bit count | NumberOfItems64 | 64-bit count | NumberOfItemsHEX32 | 32-bit hexadecimal count | NumberOfItemsHEX64 | 64-bit hexadecimal count | RateOfCountsPerSecond32 | 32-bit number of counts per second | RateOfCountsPerSecond64 | 64-bit number of counts per second | RawBase | Denominator for RawFraction | RawFraction | Numerator of a fractional count | SampleBase | Denominator representing number of samplings | SampleCounter | Number of ones returned from 0 or 1 count | SampleFraction | Percentage of ones returned from 0 or 1 count | Timer100Ns | Time in 100 nanosecond units—in use | Timer100NsInverse | Time in 100 nanosecond units—not in use |
Once the new counter is defined, add the CounterCreationData object to the CounterCreationDataCollection object. This completes the definition of the counter, and now the counter must be added to a category. To create the CounterCategory, call the static Create() method of the PerformanceCounterCategory class with three parameters: category name, category description, and theunterCreationData Collection object just described. As you may suspect, multiple counters may be added to a category by just adding more CounterCreationData counters to the ounterCreationDataCollection object used as the third parameter to the Create() method of the PerformanceCounterCategory class. Here's the definition of the customized counter with a custom category: myCounters.Add(myCounterCreationData); if (PerformanceCounterCategory.Exists( "Order Processor")) { PerformanceCounterCategory.Delete( "Order Processor"); }
PerformanceCounterCategory.Create( "Order Processor", "OrderProcessor class counters", myCounters);
This example also contains a check for whether the new category exists. If this is true, the category is deleted before it is recreated. If a performance counter category already exists, then it can't be recreated, throwing a runtime exception. This program could just as well have used an exception handler around this code, which may be better form. However, to be instructive, this example shows how to use the Exists() and Delete() methods of the PerformanceCounterCategory class. There's also a Delete() method call in the Dispose() method so the program doesn't leave counters laying around unnecessarily. The performance counter object for this new custom performance counter is declared the same as any other performance counter. One important item to address is that a program must manage the custom counter itself,updating its value as appropriate. This performance counter value is initialized by setting its RawValue property to 0, as the following code shows: orderCounter = new PerformanceCounter( "Order Processor", "Order Count", false); orderCounter.RawValue = 0; Subsequent management of the custom performance counter resides in the CurNoOrders property. The get accessor obtains the NextValue(), a float result, and casts it to an int before returning the value. The set accessor directly sets the counter's RawValue property. Here's the CurNoOrders property: public int CurNoOrders { get { return (int)orderCounter.NextValue(); } set { orderCounter.RawValue = value; } } Custom performance counters present a unique view of special conditions within a program. They provide insight not available with the generalized view of system performance counters. Figure 32.2
Fig 32.2 shows the executed program from Listings 32.3 and 32.4. Here are the compilation instructions: csc /t:winexe /out:CustomClient.exe CustomClient.cs CustomOrderProcessor.cs Figure 32.2 A custom performance counter. Analyzing Performance with SamplingPrevious programs in this chapter provided interesting statistics to look at and even provided a general idea of what was happening with system performance. This is nice, but sometimes you really need to zero in on what's going on with a program and get a better picture of a more sophisticated scenario. Performance counter sampling does just that. Sampling is the capability to perform specialized calculations between successive performance counter results.This is especially relevant in tracking averages and discovering trends. Listings 32.5 and 32.6 show how to create a custom performance counter that performs sampling. Listing 32.5 Sampling Client: SampleClient.cs
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; using System.Diagnostics;
namespace OrderingClient { /// <summary> /// Summary description for Form1. /// </summary> public class SampleClient : System.Windows.Forms.Form { private System.Windows.Forms.Label maxOrdLbl; private System.Windows.Forms.TextBox maxOrdTxt; private System.Windows.Forms.Button updateBtn; private int maxOrders; private int curOrders; private CustomSamplingProcessor orderProc; private System.Windows.Forms.Timer orderTimer; private System.Windows.Forms.Timer countTimer; private System.Windows.Forms.Label threadLbl; private System.Windows.Forms.Label threadResultLbl; private System.Diagnostics.PerformanceCounter threadCounter; private System.Windows.Forms.Label ordRateResultLbl; private System.Windows.Forms.Label ordRateLbl; private System.ComponentModel.IContainer components; public SampleClient() { InitializeComponent(); maxOrders = 10; maxOrdTxt.Text = maxOrders.ToString(); orderProc = new CustomSamplingProcessor(); curOrders = orderProc.CurNoOrders; ordRateResultLbl.Text = curOrders.ToString(); } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.maxOrdTxt = new System.Windows.Forms.TextBox(); this.ordRateResultLbl = new System.Windows.Forms.Label(); this.threadLbl = new System.Windows.Forms.Label(); this.ordRateLbl = new System.Windows.Forms.Label(); this.orderTimer = new System.Windows.Forms.Timer( this.components); this.updateBtn = new System.Windows.Forms.Button(); this.threadResultLbl = new System.Windows.Forms.Label(); this.threadCounter = new System.Diagnostics.PerformanceCounter(); this.countTimer = new System.Windows.Forms.Timer( this.components); this.maxOrdLbl = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).BeginInit(); this.SuspendLayout();
this.maxOrdTxt.Location = new System.Drawing.Point(152, 24); this.maxOrdTxt.Name = "maxOrdTxt"; this.maxOrdTxt.TabIndex = 2; this.maxOrdTxt.Text = ""; this.maxOrdTxt.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.ordRateResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.ordRateResultLbl.Location = new System.Drawing.Point(152, 64); this.ordRateResultLbl.Name = "ordRateResultLbl"; this.ordRateResultLbl.Size = new System.Drawing.Size(100, 20); this.ordRateResultLbl.TabIndex = 3; this.ordRateResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.threadLbl.Location = new System.Drawing.Point(40, 104); this.threadLbl.Name = "threadLbl"; this.threadLbl.TabIndex = 1; this.threadLbl.Text = "Thread Count:"; this.threadLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.ordRateLbl.Location = new System.Drawing.Point(40, 64); this.ordRateLbl.Name = "ordRateLbl"; this.ordRateLbl.TabIndex = 1; this.ordRateLbl.Text = "Orders/Sec:"; this.ordRateLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.orderTimer.Enabled = true; this.orderTimer.Interval = 2000; this.orderTimer.Tick += new System.EventHandler( this.orderTimer_Tick);
this.updateBtn.Location = new System.Drawing.Point(104, 152); this.updateBtn.Name = "updateBtn"; this.updateBtn.TabIndex = 4; this.updateBtn.Text = "Update"; this.updateBtn.Click += new System.EventHandler( this.updateBtn_Click);
this.threadResultLbl.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.threadResultLbl.Location = new System.Drawing.Point(152, 104); this.threadResultLbl.Name = "threadResultLbl"; this.threadResultLbl.Size = new System.Drawing.Size(100, 20); this.threadResultLbl.TabIndex = 3; this.threadResultLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.threadCounter.CategoryName = ".NET CLR LocksAndThreads"; this.threadCounter.CounterName = "# of current physical Threads"; this.threadCounter.InstanceName = "SampleClient"; this.countTimer.Enabled = true; this.countTimer.Interval = 1000; this.countTimer.Tick += new System.EventHandler( this.countTimer_Tick);
this.maxOrdLbl.Location = new System.Drawing.Point(40, 24); this.maxOrdLbl.Name = "maxOrdLbl"; this.maxOrdLbl.TabIndex = 0; this.maxOrdLbl.Text = "Max Orders:"; this.maxOrdLbl.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(288, 197); this.Controls.AddRange( new System.Windows.Forms.Control[] { this.threadLbl, this.threadResultLbl, this.updateBtn, this.ordRateResultLbl, this.maxOrdTxt, this.ordRateLbl, this.maxOrdLbl}); this.Name = "SampleClient"; this.Text = "Sample Client"; this.Closing += new System.ComponentModel.CancelEventHandler( this.SampleClient_Closing); ((System.ComponentModel.ISupportInitialize) (this.threadCounter)).EndInit(); this.ResumeLayout(false); } static void Main() { Application.Run(new SampleClient()); } private void updateBtn_Click(object sender, System.EventArgs e) { maxOrders = Convert.ToInt32(maxOrdTxt.Text); } private void orderTimer_Tick(object sender, System.EventArgs e) { orderTimer.Enabled = false; Thread th = new Thread( new ThreadStart(ProcessOrders)); th.Start(); orderTimer.Enabled = true; } private void countTimer_Tick(object sender, System.EventArgs e) { countTimer.Enabled = false; ordRateResultLbl.Text = ((int)orderProc.OrderRate).ToString(); threadResultLbl.Text = threadCounter.NextValue().ToString(); countTimer.Enabled = true; } private void ProcessOrders() { for (curOrders = orderProc.CurNoOrders; curOrders <= maxOrders; curOrders++) { orderProc.ProcessOrder(); } } private void SampleClient_Closing(object sender, System.ComponentModel.CancelEventArgs e) { orderProc.Dispose(); } } }
Listing 32.6 Custom Performance Counter Sampling: CustomSamplingProcessor.cs using System; using System.Threading; using System.Diagnostics; namespace OrderingClient { /// <summary> /// Summary description for CustomSamplingProcessor. /// </summary> public class CustomSamplingProcessor : IDisposable { private PerformanceCounter orderCounter; private CounterSample orderSample; private static int curNoOrders = 0; private Random rand; public CustomSamplingProcessor() { rand = new Random(); CounterCreationDataCollection myCounters = new CounterCreationDataCollection(); CounterCreationData myCounterCreationData = new CounterCreationData(); myCounterCreationData.CounterName = "Order Count"; myCounterCreationData.CounterHelp = "Displays the of orders being processed."; myCounterCreationData.CounterType = PerformanceCounterType.RateOfCountsPerSecond32; myCounters.Add(myCounterCreationData); if (PerformanceCounterCategory.Exists( "Order Processor")) { PerformanceCounterCategory.Delete( "Order Processor"); } PerformanceCounterCategory.Create( "Order Processor", "OrderProcessor class counters", myCounters); orderCounter = new PerformanceCounter( "Order Processor", "Order Count", false); orderCounter.RawValue = 0; orderSample = new CounterSample(); orderSample = orderCounter.NextSample(); } public int ProcessOrder() { Thread th = new Thread(new ThreadStart(doOrder)); th.Start(); CurNoOrders++; return 0; } public int CurNoOrders { get { return curNoOrders; } set { curNoOrders = value; } } public float OrderRate { get { CounterSample tempSample = new CounterSample(); tempSample = orderCounter.NextSample(); float sample = CounterSample.Calculate( orderSample, tempSample); orderSample = tempSample; return sample; } } private void doOrder() { for (int delay = rand.Next(1000000); delay >= 0; delay—) ; CurNoOrders—; orderCounter.Increment(); } public void Dispose() { PerformanceCounterCategory.Delete( "Order Processor"); } } } The example program in Listings 32.5 and 32.6 is similar to the one in Listings 32.3 and 32.4, except in the way the data is collected. During creation of the CounterCreationData instance, the CounterType property is set to RateOfCountsPerSecond32. This enables the counter to support a count of the number of orders per second processed by the CustomSamplingProcessor object. Here is the property setting: myCounterCreationData.CounterType = PerformanceCounterType.RateOfCountsPerSecond32; Another difference in sampling is that the NextSample() method of the counter object is called instead of Next Value(). The NextSample() method returns a CounterSample object. Here's how to declare and collect a single counter sampling: orderSample = new CounterSample(); orderSample = orderCounter.NextSample(); Proper sampling of a RateOfCountsPerSecond32 type counter requires two samples. These samples are presented to the static Calculate() method of the CounterSample class. The result of the Calculate() method is a float type value representing the number of orders per second processed. The following example shows how the details of the Calculate() method are encapsulated in the read-only OrderRate property: public float OrderRate { get { CounterSample tempSample = new CounterSample(); tempSample = orderCounter.NextSample(); float sample = CounterSample.Calculate( orderSample, tempSample); orderSample = tempSample; return sample; } } This counter clearly provides valuable information about the performance of the program. An average on the way up shows potential for more capacity; when the average peaks, you have a good idea of what the system limits are; and a descending average indicates overload. The output from Listings 32.5 and 32.6 are shown in Fig 32.3  Figure 32.3
Here are the compilation instructions: csc /t:winexe /out:SampleClient.exe SampleClient.cs CustomSamplingProcessor.cs Figure 32.3 A custom sampling performance counter.
Tip Any of the performance counters used or created in this chapter may be monitored with the Windows Performance tool. In Windows 2000, the System Monitor can be found by selecting Settings, Control Panel from the Start menu. Then open the Administrative Tools folder and run the Performance Tool.
SummaryThe System.Diagnostics namespace includes a framework for supporting performance counters. Performance counters enable a program to be monitored for performance and scalability. At a basic level, predefined system performance counters can be used to examine a program's behavior. The performance counter framework supports customized performance counters for situations in which it's necessary to monitor specialized behavior. Custom performance counters identify conditions specific to an application and must be explicitly managed by the application. Sampling provides more sophisticated monitoring of program performance. This technique takes a number of samples and performs calculations on a regular basis. More so than other methods, the results of sampling can provide much more insight into a program's capability. This chapter examined how to monitor your system to see how it performs under various circumstances. The next chapter integrates C# with COM and shows you ways to enhance performance with enterprise services, such as COM+. |