This documentation is archived and is not being maintained.

Diagnostics

This chapter is excerpted from C# 3.0 in a Nutshell, Third Edition: A Desktop Quick Reference by Joseph Albahari, Ben Albahari, published by O'Reilly Media

When things go wrong, it's important that information is available to aid in diagnosing the problem. An IDE or debugger can assist greatly to this effect-but it is usually available only during development. Once an application ships, the application itself must gather and record diagnostic information. To meet this requirement, the .NET Framework provides a set of facilities to log diagnostic information, monitor application behavior, detect runtime errors, and integrate with debugging tools if available.

The types in this chapter are defined primarily in the System.Diagnostics namespace.

Conditional Compilation

You can conditionally compile any section of code in C# with preprocessor directives. Preprocessor directives are special instructions to the compiler that begin with the # symbol (and, unlike other C# constructs, must appear on a line of their own). The preprocessor directives for conditional compilation are #if, #else, #endif, and #elif.

The #if directive instructs the compiler to ignore a section of code unless a specified symbol has been defined. You can define a symbol with either the #define directive or a compilation switch. #define applies to a particular file; a compilation switch applies to a whole assembly:

#define TESTMODE            // #define directives must be at top of file
                            // Symbol names are uppercase by convention.
using System;

class Program
{
  static void Main(  )
  {
#if TESTMODE
    Console.WriteLine ("in test mode!");     // OUTPUT: in test mode!
#endif
  }
}

If we deleted the first line, the program would compile with the Console.WriteLine statement completely eliminated from the executable.

The #else statement is analogous to C#'s else statement, and #elif is equivalent to #else followed by #if. The ||, &, and ! operators can be used to perform or, and, and not operations:

#if TESTMODE & !PLAYMODE      // if TESTMODE and not PLAYMODE
  ...

Bear in mind, however, that you're not building an ordinary C# expression, and the symbols upon which you operate have absolutely no connection to variables-static or otherwise.

To define a symbol assembly-wide, specify the /define switch when compiling:

csc Program.cs/define:TESTMODE,PLAYMODE

Visual Studio provides an option to enter conditional compilation symbols under Project Properties.

If you've defined a symbol at the assembly level and then want to "undefine" it for a particular file, you can do so with the #undef directive.

Conditional Compilation Versus Static Variable Flags

The preceding example could instead be implemented with a simple static field:

static internal bool TestMode = true;

static void Main(  )
{
  if (TestMode) Console.WriteLine ("in test mode!");
}

This has the advantage of allowing runtime configuration. So, why choose conditional compilation? The reason is that conditional compilation can take you places variable flags cannot, such as:

  • Conditionally including an attribute

  • Changing the declared type of variable

  • Switching between different namespaces or type aliases in a using directive-for example:

    using TestType =
      #if V2
         MyCompany.Widgets.GadgetV2;
      #else
         MyCompany.Widgets.Gadget;
      #endif
    

You can even perform major refactoring under a conditional compilation directive, so you can instantly switch between old and new versions.

Another advantage of conditional compilation is that debugging code can refer to types in assemblies that are not included in deployment.

The Conditional Attribute

The Conditional attribute instructs the compiler to ignore any calls to a particular class or method, if the specified symbol has not been defined.

To see how this is useful, suppose you write a method for logging status information as follows:

static void LogStatus (string msg)
{
  string logFilePath = ...
  System.IO.File.AppendAllText (logFilePath, msg + "\r\n");
}

Now imagine you wanted this to execute only if the LOGGINGMODE symbol is defined. The first solution is to wrap all calls to LogStatus around an #if directive:

#if LOGGINGMODE
LogStatus ("Message Headers: " + GetMsgHeaders(  ));
#endif

This gives an ideal result, but it is tedious. The second solution is to put the #if directive inside the LogStatus method. This, however, is problematic should LogStatus be called as follows:

LogStatus ("Message Headers: " + GetComplexMessageHeaders(  ));

GetComplexMessageHeaders would always get called-which might incur a performance hit.

We can combine the functionality of the first solution with the convenience of the second by attaching the Conditional attribute (defined in System.Diagnostics) to the LogStatus method:

[Conditional ("LOGGINGMODE")]
static void LogStatus (string msg)
{
  ...
}

This instructs the compiler to implicitly wrap any calls to LogStatus in an #if LOGGINGMODE directive. If the symbol is not defined, any calls to LogStatus get eliminated entirely in compilation-including their argument evaluation expressions. This works even if LogStatus and the caller are in different assemblies.

Tip
The Conditional attribute is ignored at runtime-it's purely an instruction to the compiler.

Alternatives to the Conditional attribute

The Conditional attribute is useless if you need to dynamically enable or disable functionality at runtime: instead, you must use a variable-based approach. This leaves the question of how to elegantly circumvent the evaluation of arguments when calling conditional logging methods. A functional approach solves this:

using System;
using System.Linq;

class Program
{
  public static bool EnableLogging;

  static void LogStatus (Func<string> message)
  {
    string logFilePath = ...
    if (EnableLogging)
      System.IO.File.AppendAllText (logFilePath, message(  ) + "\r\n");
  }
}

A lambda expression lets you call this method without syntax bloat:

LogStatus ( () => "Message Headers: " + GetComplexMessageHeaders(  ) );

If EnableLogging is false, GetComplexMessageHeaders is never evaluated.

Debug and Trace are static classes that provide basic logging and assertion capabilities. The two classes are very similar; the main differentiator is their intended use. The Debug class is intended for debug builds; the Trace class is intended for both debug and release builds. To this effect:

All methods of the Debug class are defined with [Conditional("DEBUG")].
All methods of the Trace class are defined with [Conditional("TRACE")].

This means that all calls that you make to Debug or Trace are eliminated by the compiler unless you define DEBUG or TRACE symbols. By default, Visual Studio defines both DEBUG and TRACE symbols in a project's debug configuration-and just the TRACE symbol in the release configuration.

Both the Debug and Trace classes provide Write, WriteLine, and WriteIf methods. By default, these send messages to the debugger's output window:

Debug.Write     ("Data");
Debug.WriteLine (23 * 34);
int x = 5, y = 3;
Debug.WriteIf   (x > y, "x is greater than y");

Debug and Trace also provide Fail and Assert methods. By default, Fail displays the message in dialog as well as sending it to the debug output:

Debug.Fail ("File data.txt already exists");

Assert simply calls Fail if the bool argument is false, and it is useful for verifying code invariants (conditions that should always evaluate to true if your code is bug-free). Specifying a message is optional:

Debug.Assert (!File.Exists ("data.txt"), "File data.txt already exists");
var result = ...
Debug.Assert (result != null);

The Write, Fail, and Assert methods are also overloaded to accept a string category in addition to the message, which can be useful in processing the output.

In the Trace class, there are the additional methods TraceInformation, TraceWarning, and TraceError. The difference in behavior between these and the Write methods depends on the active TraceListeners.

TraceListener

The Debug and Trace classes each have a Listeners property, comprising a static collection of TraceListener instances. These are responsible for processing the content emitted by the Write, Fail, and Trace methods.

By default, the Listeners collection of each includes a single listener (DefaultTraceListener). The default listener has two key features:

  • When connected to a debugger such as Visual Studio, messages are written to the debug output window; otherwise, message content is ignored.

  • When the Fail method is called (or an assertion fails), a dialog appears asking the user whether to continue or abort-regardless of whether a debugger is attached.

You can change this behavior by removing the default listener, and then adding one or more of your own. You can write trace listeners from scratch (by subclassing TraceListener) or use one of the predefined types:

  • TextWriterTraceListener writes to a Stream or TextWriter or appends to a file.

  • EventLogTraceListener writes to the Windows event log.

  • EventProviderTraceListener writes to the Event Tracing for Windows (ETW) subsystem in Windows Vista.

  • WebPageTraceListener writes to an ASP.NET web page.

TextWriterTraceListener is further subclassed to ConsoleTraceListener, DelimitedListTraceListener, XmlWriterTraceListener, and EventSchemaTrace-Listener.

Tip
None of these listeners displays a dialog when Fail is called-only DefaultTraceListener has this behavior.

The following example clears Trace's default listener, then adds three listeners- one that appends to a file, one that writes to the console, and one that writes to the Windows event log:

// Clear the default listener:
Trace.Listeners.Clear(  );

// Add a writer that appends to the trace.txt file:
Trace.Listeners.Add (new TextWriterTraceListener ("trace.txt"));

// Obtain the Console's output stream, then add that as a listener:
System.IO.TextWriter tw = Console.Out;
Trace.Listeners.Add (new TextWriterTraceListener (tw));

// Set up a Windows Event log source and then create/add listener:
if (!EventLog.SourceExists ("DemoApp"))
  EventLog.CreateEventSource ("DemoApp", "Application");

Trace.Listeners.Add (new EventLogTraceListener ("DemoApp"));

In the case of the Windows event log, messages that you write with the Write, Fail, or Assert methods always display as "Information" messages in the Windows event viewer. Messages that you write via the TraceWarning and TraceError methods, however, show up as warnings or errors.

TraceListener also has a Filter of type TraceFilter that you can set to control whether a given message gets written to that listener. To do this, either instantiate one of the predefined subclasses (EventTypeFilter or SourceFilter) or subclass TraceFilter and override the ShouldTrace method. You could use this to filter by category, for instance.

TraceListener also defines IndentLevel and IndentSize properties for controlling indentation, and the TraceOutputOptions property for writing extra data:

TextWriterTraceListener tl = new TextWriterTraceListener (Console.Out);
tl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;

TraceOutputOptions are applied when using the Trace methods:

Trace.TraceWarning ("Orange alert");DiagTest.vshost.exe Warning: 0 : Orange alert
    DateTime=2007-03-08T05:57:13.6250000Z
    Callstack=   at System.Environment.GetStackTrace(Exception e, Boolean
needFileInfo)
    at System.Environment.get_StackTrace(  )
    at ...

Flushing and Closing Listeners

Some listeners, such as TextWriterTraceListener, ultimately write to a stream that is subject to caching. This has two implications:

  • A message may not appear in the output stream or file immediately.

  • You must close-or at least flush-the listener before your application ends; otherwise, you lose what's in the cache (4 KB if you're writing to a file).

The Trace and Debug classes provide static Close and Flush methods that call Close or Flush on all listeners (which in turn calls Close or Flush on any underlying writers and streams). Close implicitly calls Flush, closes file handles, and prevents further data from being written.

As a general rule, call Close before an application ends and call Flush anytime you want to ensure that current message data is written. This applies if you're using stream- or file-based listeners.

Trace and Debug also provide an AutoFlush property, which, if true, forces a Flush after every message.

Warning
It's a good policy to set AutoFlush to true on Debug and Trace if you're using any file- or stream-based listeners. Otherwise, if an unhandled exception or critical error occurs, the last 4 KB of diagnostic information may be lost.

Sometimes it is useful for an application to interact with a debugger if one is available. During development, the debugger is usually your IDE (e.g., Visual Studio); in deployment, the debugger is more likely to be:

  • DbgCLR

  • One of the lower-level debugging tools, such as WinDbg, Cordbg, or Mdgb

DbgCLR is Visual Studio stripped of everything but the debugger, and it is a free download with the .NET Framework SDK. It's the easiest debugging option when an IDE is not available, although it requires that you download the whole SDK.

Attaching and Breaking

The static Debugger class in System.Diagnostics provides basic functions for interacting with a debugger-namely Break, Launch, Log, and IsAttached.

A debugger must first attach to an application in order to debug it. If you start an application from within an IDE, this happens automatically, unless you request otherwise (by choosing "Start without debugging"). Sometimes, though, it's inconvenient or impossible to start an application in debug mode within the IDE. An example is a Windows Service application or (ironically) a Visual Studio designer. One solution is to start the application normally, and then choose Debug Process in your IDE. This doesn't allow you to set breakpoints early in the program's execution, however.

The workaround is to call Debugger.Break from within your application. This method launches a debugger, attaches to it, and suspends execution at that point. (Launch does the same, but without suspending execution.) Once attached, you can log messages directly to the debugger's output window with the Log method. You can tell whether you're attached to a debugger with the IsAttached property.

Debugger Attributes

The DebuggerStepThrough and DebuggerHidden attributes provide suggestions to the debugger on how to handle single-stepping for a particular method, constructor, or class.

DebuggerStepThrough requests that the debugger step through a function without any user interaction. This attribute is useful in automatically generated methods and in proxy methods that forward the real work to a method somewhere else. In the latter case, the debugger will still show the proxy method in the call stack if a breakpoint is set within the "real" method-unless you also add the DebuggerHidden attribute. These two attributes can be combined on proxies to help the user focus on debugging the application logic rather than the plumbing:

[DebuggerStepThrough, DebuggerHidden]
void DoWorkProxy(  )
{
  // setup...
  DoWork(  );
  // teardown...
}

void DoWork(  ) {...}   // Real method...

We described in the last section of Chapter 6, Framework Fundamentals how to launch a new process with Process.Start. The Process class also allows you to query and interact with other processes running on the same, or another, computer.

Examining Running Processes

The Process.GetProcessXXX methods retrieve a specific process by name or process ID, or all processes running on the current or nominated computer. This includes both managed and unmanaged processes. Each Process instance has a wealth of properties mapping statistics such as name, ID, priority, memory and processor utilization, window handles, and so on. The following sample enumerates all the running processes on the current computer:

foreach (Process p in Process.GetProcesses(  ))
{
  Console.WriteLine (p.ProcessName);
  Console.WriteLine ("   PID:      " + p.Id);
  Console.WriteLine ("   Started:  " + p.StartTime);
  Console.WriteLine ("   Memory:   " + p.WorkingSet64);
  Console.WriteLine ("   CPU time: " + p.TotalProcessorTime);
  Console.WriteLine ("   Threads:  " + p.Threads.Count);
}

Process.GetCurrentProcess returns the current process. If you've created additional application domains, all will share the same process.

You can terminate a process by calling its Kill method.

Examining Threads in a Process

You can also enumerate over the threads of other processes, with the Process.Threads property. The objects that you get, however, are not System.Threading.Thread objects, but rather ProcessThread objects, and are intended for administrative rather than synchronization tasks. A ProcessThread object provides diagnostic information about the underlying thread and allows you to control some aspects of it such as its priority and processor affinity:

public void EnumerateThreads (Process p)
{
  foreach (ProcessThread pt in p.Threads)
  {
    Console.WriteLine (pt.Id);
    Console.WriteLine ("   State:    " + pt.ThreadState);
    Console.WriteLine ("   Priority: " + pt.PriorityLevel);
    Console.WriteLine ("   Started:  " + pt.StartTime);
    Console.WriteLine ("   CPU time: " + pt.TotalProcessorTime);
  }
}

The StackTrace and StackFrame classes provide a read-only view of an execution call stack. You can obtain stack traces for the current thread, another thread in the same process, or an Exception object. Such information is useful mostly for diagnostic purposes, though it can also be used in programming (hacks). StackTrace represents a complete call stack; StackFrame represents a single method call within that stack.

If you instantiate a StackTrace object with no arguments-or with a bool argument-you get a snapshot of the current thread's call stack. The bool argument, if true, instructs StackTrace to read the assembly .pdb (project debug) files if they are present, giving you access to filename, line number, and column offset data.

Tip
Project debug files are generated when you compile with the /debug switch. Visual Studio compiles with this switch unless you request otherwise via Advanced Build Settings.

Once you've obtained a StackTrace, you can examine a particular frame by calling GetFrame-or obtain the whole lot with GetFrames:

static void Main() { A (  ); }
static void A()    { B (  ); }
static void B()    { C (  ); }
static void C(  )
{
  StackTrace s = new StackTrace (true);

  Console.WriteLine ("Total frames:   " + s.FrameCount);
  Console.WriteLine ("Current method: " + s.GetFrame(0).GetMethod(  ).Name);
  Console.WriteLine ("Calling method: " + s.GetFrame(1).GetMethod(  ).Name);
  Console.WriteLine ("Entry method:   " + s.GetFrame
                                       (s.FrameCount-1).GetMethod(  ).Name);
  Console.WriteLine ("Call Stack:");
  foreach (StackFrame f in s.GetFrames(  ))
    Console.WriteLine (
      "  File: "   + f.GetFileName(  ) +
      "  Line: "   + f.GetFileLineNumber(  ) +
      "  Col: "    + f.GetFileColumnNumber(  ) +
      "  Offset: " + f.GetILOffset(  ) +
      "  Method: " + f.GetMethod(  ).Name);
}

Here's the output:

Total frames:   4
Current method: C
Calling method: B
Entry method: Main
Call stack:
  File: C:\Test\Program.cs  Line: 15  Col: 4  Offset: 7  Method: C
  File: C:\Test\Program.cs  Line: 12  Col: 22  Offset: 6  Method: B
  File: C:\Test\Program.cs  Line: 11  Col: 22  Offset: 6  Method: A
  File: C:\Test\Program.cs  Line: 10  Col: 25  Offset: 6  Method: Main

A shortcut to obtaining the essential information for an entire StackTrace is to call ToString on it. Here's what the result looks like:

   at DebugTest.Program.C(  ) in C:\Test\Program.cs:line 16
   at DebugTest.Program.B(  ) in C:\Test\Program.cs:line 12
   at DebugTest.Program.A(  ) in C:\Test\Program.cs:line 11
   at DebugTest.Program.Main(  ) in C:\Test\Program.cs:line 10

To obtain the stack trace for another thread, pass the other Thread into StackTrace's constructor. This can be a useful strategy for profiling a program. The one proviso is that you suspend the thread first, by calling its Suspend method (and Resume when you're done). This is the one valid use for Thread's deprecated Suspend and Resume methods!

You can also obtain the stack trace for an Exception object (showing what led up to the exception being thrown) by passing the Exception into StackTrace's constructor.

Tip
Exception already has a StackTrace property; however, this property returns a simple string-not a StackTrace object. A StackTrace object is far more useful in logging exceptions that occur after deployment-where no .pdb files are available-because you can log the IL offset in lieu of line and column numbers. With an IL offset and ildasm, you can pinpoint where within a method an error occurred.

The Win32 platform provides a centralized logging mechanism, in the form of the Windows event logs.

The Debug and Trace classes we used earlier write to a Windows event log if you register an EventLogTraceListener. With the EventLog class, however, you can write directly to a Windows event log without using Trace or Debug. You can also use this class to read and monitor event data.

Tip
Writing to the Windows event log makes sense in a Windows Service application, because if something goes wrong, you can't pop up a user interface directing the user to some special file where diagnostic information has been written. Also, because it's common practice for services to write to the Windows event log, this is the first place an administrator is likely to look if your service falls over.

There are three standard Windows event logs, identified by these names:

  • Application

  • System

  • Security

The Application log is where most applications normally write.

Writing to the Event Log

To write to a Windows event log:

  1. Choose one of the three event logs (usually Application).

  2. Decide on a source name and create it if necessary.

  3. Call EventLog.WriteEntry with the log name, source name, and message data.

The source name is an easily identifiable name for your application. You must register a source name before you use it-the CreateEventSource method performs this function. You can then call WriteEntry:

const string SourceName = "MyCompany.WidgetServer";

if (!EventLog.SourceExists (SourceName))
  EventLog.CreateEventSource (SourceName, "Application");

EventLog.WriteEntry (SourceName,
  "Service started; using configuration file=...",
  EventLogEntryType.Information);

EventLogEntryType can be Information, Warning, Error, SuccessAudit, or FailureAudit. Each displays with a different icon in the Windows event viewer. You can also optionally specify a category and event ID (each is a number of your own choosing) and provide optional binary data.

CreateEventSource also allows you to specify a machine name: this is to write to another computer's event log, if you have sufficient permissions.

Reading the Event Log

To read an event log, instantiate the EventLog class with the name of the log you wish to access and optionally the name of another computer on which the log resides. Each log entry can then be read via the Entries collection property:

EventLog log = new EventLog ("Application");

Console.WriteLine ("Total entries: " + log.Entries.Count);

EventLogEntry last = log.Entries [log.Entries.Count - 1];
Console.WriteLine ("Index:   " + last.Index);
Console.WriteLine ("Source:  " + last.Source);
Console.WriteLine ("Type:    " + last.EntryType);
Console.WriteLine ("Time:    " + last.TimeWritten);
Console.WriteLine ("Message: " + last.Message);

You can enumerate over all logs for the current (or another) computer with the static method EventLog.GetEventLogs (this requires administrative privileges):

foreach (EventLog log in EventLog.GetEventLogs(  ))
  Console.WriteLine (log.LogDisplayName);

This normally prints Application, Security, and System.

Monitoring the Event Log

You can be alerted whenever an entry is written to a Windows event log, via the EntryWritten event. This works for event logs on the local computer, and it fires regardless of what application logged the event.

To enable log monitoring:

  1. Instantiate an EventLog and set its EnableRaisingEvents property to true.

  2. Handle the EntryWritten event.

For example:

static void Main(  )
{
  EventLog log = new EventLog ("Application");
  log.EnableRaisingEvents = true;
  log.EntryWritten += DisplayEntry;
  Console.ReadLine(  );
}

static void DisplayEntry (object sender, EntryWrittenEventArgs e)
{
  EventLogEntry entry = e.Entry;
  Console.WriteLine (entry.Message);
}

The logging mechanisms we've discussed to date are useful for capturing information for future analysis. However, to gain insight into the current state of an application (or the system as a whole), a more real-time approach is needed. The Win32 solution to this need is the performance-monitoring infrastructure, which consists of a set of performance counters that the system and applications expose, and the Microsoft Management Console (MMC) snap-ins used to monitor these counters in real time.

Performance counters are grouped into categories such as "System," "Processor," ".NET CLR Memory," and so on. These categories are sometimes also referred to as "performance objects" by the GUI tools. Each category groups a related set of performance counters that monitor one aspect of the system or application. Examples of performance counters in the ".NET CLR Memory" category include "% Time in GC," "# Bytes in All Heaps," and "Allocated bytes/sec."

Each category may optionally have one or more instances that can be monitored independently. For example, this is useful in the "% Processor Time" performance counter in the "Processor" category, which allows one to monitor CPU utilization. On a multiprocessor machine, this counter supports an instance for each CPU, allowing one to monitor the utilization of each CPU independently.

The following sections illustrate how to perform commonly needed tasks, such as determining which counters are exposed, monitoring a counter, and creating your own counters to expose application status information.

Warning
Reading performance counters or categories requires administrator privileges on the local or target computer.

Enumerating the Available Counters

The following example enumerates over all of the available performance counters on the computer. For those that have instances, it enumerates the counters for each instance:

PerformanceCounterCategory[] cats =
  PerformanceCounterCategory.GetCategories(  );

foreach (PerformanceCounterCategory cat in cats)
{
  Console.WriteLine ("Category: " + cat.CategoryName);

  string[] instances = cat.GetInstanceNames(  );
  if (instances.Length == 0)
  {
    foreach (PerformanceCounter ctr in cat.GetCounters(  ))
      Console.WriteLine ("  Counter: " + ctr.CounterName);
  }
  else   // Dump counters with instances
  {
    foreach (string instance in instances)
    {
      Console.WriteLine ("  Instance: " + instance);
      if (cat.InstanceExists (instance))
        foreach (PerformanceCounter ctr in cat.GetCounters (instance))
          Console.WriteLine ("    Counter: " + ctr.CounterName);
    }
  }
}
Warning
The result is more than 10,000 lines long! It also takes awhile to execute because PerformanceCounterCategory.InstanceExists has an inefficient implementation. In a real system, you'd want to retrieve the more detailed information only on demand.

The next example uses a LINQ query to retrieve just .NET performance counters, writing the result to an XML file:

var x =
  new XElement ("counters",
    from PerformanceCounterCategory cat in
         PerformanceCounterCategory.GetCategories(  )
    where cat.CategoryName.StartsWith (".NET")
    let instances = cat.GetInstanceNames(  )
    select new XElement ("category",
      new XAttribute ("name", cat.CategoryName),
      instances.Length == 0
      ?
        from c in cat.GetCounters (  )
        select new XElement ("counter",
          new XAttribute ("name", c.CounterName))
      :
        from i in instances
        select new XElement ("instance", new XAttribute ("name", i),
          !cat.InstanceExists (i)
          ?
            null
          :
            from c in cat.GetCounters (i)
            select new XElement ("counter",
              new XAttribute ("name", c.CounterName))
        )
    )
  );
x.Save ("counters.xml");

Reading Performance Counter Data

To retrieve the value of a performance counter, instantiate a PerformanceCounter object and then call the NextValue or NextSample method. NextValue returns a simple float value; NextSample returns a CounterSample object that exposes a more advanced set of properties, such as CounterFrequency, TimeStamp, BaseValue, and RawValue.

PerformanceCounter's constructor takes a category name, counter name, and optional instance. So, to display the current processor utilization for all CPUs, you would do the following:

using (PerformanceCounter pc = new PerformanceCounter ("Processor",
                                                       "% Processor Time",
                                                       "_Total"))
  Console.WriteLine (pc.NextValue(  ));

Or to display the "real" (i.e., private) memory consumption of the current process:

string procName = Process.GetCurrentProcess(  ).ProcessName;
using (PerformanceCounter pc = new PerformanceCounter ("Process",
                                                       "Private Bytes",
                                                       procName))
  Console.WriteLine (pc.NextValue(  ));

PerformanceCounter doesn't expose a ValueChanged event, so if you want to monitor for changes, you must poll. In the next example, we poll every 200 ms-until signaled to quit by an EventWaitHandle:

// need to import System.Threading as well as System.Diagnostics

static void Monitor (string category, string counter, string instance,
                     EventWaitHandle stopper)
{
  if (!PerformanceCounterCategory.Exists (category))
    throw new InvalidOperationException ("Category does not exist");

  if (!PerformanceCounterCategory.CounterExists (counter, category))
    throw new InvalidOperationException ("Counter does not exist");

  if (instance == null) instance = ";   // " == no instance (not null!)
  if (instance != " &
      !PerformanceCounterCategory.InstanceExists (instance, category))
    throw new InvalidOperationException ("Instance does not exist");

  float lastValue = 0f;
  using (PerformanceCounter pc = new PerformanceCounter (category,
                                                      counter, instance))
    while (!stopper.WaitOne (200, false))
    {
      float value = pc.NextValue(  );
      if (value != lastValue)         // Only write out the value
      {                               // if it has changed.
        Console.WriteLine (value);
        lastValue = value;
      }
    }
}

Here's how we can use this method to simultaneously monitor processor and hard-disk activity:

static void Main(  )
{
  EventWaitHandle stopper = new ManualResetEvent (false);
  new Thread (delegate(  )
    { Monitor ("Processor", "% Processor Time", "_Total", stopper); }
  ).Start(  );
  new Thread (delegate(  )
    { Monitor ("LogicalDisk", "% Idle Time", "C:", stopper); }
  ).Start(  );
  Console.WriteLine ("Monitoring - press any key to quit");
  Console.ReadKey(  );
  stopper.Set(  );
}

Creating Counters and Writing Performance Data

Before writing performance counter data, you need to create a performance category and counter. You must create the performance category along with all the counters that belong to it in one step, as follows:

string category = "Nutshell Monitoring";

// We'll create two counters in this category:
string eatenPerMin = "Macadamias eaten so far";
string tooHard = "Macadamias deemed too hard";

if (!PerformanceCounterCategory.Exists (category))
{
  CounterCreationDataCollection cd = new CounterCreationDataCollection(  );

  cd.Add (new CounterCreationData (eatenPerMin,
          "Number of macadamias consumed, including shelling time",
          PerformanceCounterType.NumberOfItems32));

  cd.Add (new CounterCreationData (tooHard,
          "Number of macadamias that will not crack, despite much effort",
          PerformanceCounterType.NumberOfItems32));

  PerformanceCounterCategory.Create (category, "Test Category",
    PerformanceCounterCategoryType.SingleInstance, cd);
}

The new counters then show up in the Windows performance-monitoring tool when you choose Add Counters, as shown in Figure 23-1, "Custom performance counter".

Figure 23-1. Custom performance counter

Custom performance counter

If you later want to define more counters in the same category, you must first delete the old category by calling PerformanceCounterCategory.Delete.

Tip
Creating and deleting performance counters requires administrative privileges. For this reason, it's usually done as part of the application setup.

Once a counter is created, you can update its value by instantiating a PerformanceCounter, setting ReadOnly to false, and setting RawValue. You can also use the Increment and IncrementBy methods to update the existing value:

string category = "Nutshell Monitoring";
string eatenPerMin = "Macadamias eaten so far";

using (PerformanceCounter pc = new PerformanceCounter (category,
                                                       eatenPerMin, "))
{
  pc.ReadOnly = false;
  pc.RawValue = 1000;
  pc.Increment(  );
  pc.IncrementBy (10);
  Console.WriteLine (pc.NextValue(  ));    // 1011
}

The Stopwatch class provides a convenient mechanism for measuring execution times. Stopwatch uses the highest-resolution mechanism that the operating system and hardware provide, which is typically 1-2 ms on a computer without a special high-resolution clock.

To use Stopwatch, call StartNew-this instantiates a Stopwatch and starts it ticking. (Alternatively, you can instantiate it manually and then call Start.) The Elapsed property returns the elapsed interval as a TimeSpan:

Stopwatch s = Stopwatch.StartNew(  );
System.IO.File.WriteAllText ("test.txt", new string ('*', 30000000));
Console.WriteLine (s.Elapsed);       // 00:00:01.4322661

Stopwatch also exposes an ElapsedTicks property, which returns the number of elapsed "ticks" as a long integer. To convert from ticks to seconds, divide by StopWatch.Frequency.

Calling Stop freezes Elapsed and ElapsedTicks. There's no background activity incurred by a "running" Stopwatch, so calling Stop is optional.

Show: