The ServerMonitor Demo Program

This section talks about a demonstration program that can be run from the URL http://www.aspnet-solutions.com/ServerMonitorCS/Default.aspx. This application shows the basics of using WMI and Performance Monitor in an application.

Performance Monitor Security

Before we start working with custom performance counters, we need to discuss some of the security issues surrounding their use. ASP.NET is configured to run under a less privileged account usually referred to as Aspnet_user. This account was created with the bare minimum permissions in case a security flaw was found in ASP.NET that allowed a hacker to run code on a machine. If such a breach was found, because the ASP.NET process was using this account, then the hacker could do only so much damage.

The only downside to the ASP.NET process using the Aspnet_user account is that certain functions will not work anymore without some increased effort. Creating custom performance counters is one of the operations that cannot be performed by the Aspnet_user account. The Aspnet_user account can, however, write to these counters once they are created, which simply means that we need to create the counters using impersonation. Although we can switch the system back to using the high-level system account, doing so is highly discouraged because of the security issues it would create.

Let's take a quick look at what we will need to change to be able to create these custom counters. If you are using Visual Studio .NET then these security steps are not necessary, because we can use the Server Explorer utility to create the counters and counter category directly. Using the Server Explorer, you can connect to any server located in your domain. We will be creating a small administrative application to add our custom counters to our system. We will simply create an empty virtual directory called aspperftest in our wwwroot directory.

  1. The first step that we need to take is to add the following line to the web.config of our application:

     <identity impersonate="true"></identity> 

    This setting will tell our application to impersonate the user who is logged into our site or page.

  2. The next step is to make a change to the IIS settings of our application. First, we need to right-click our virtual directory and then select Properties.

  3. Next, we click the Directory Security tab and then click the Edit button under the Anonymous Access and Authentication Control section.

  4. Here, we are going to first uncheck the box for Anonymous access, and then, if everyone using this tool will be using Windows systems and logging into our domain, we do not need to make any other changes.

  5. If we will have users who use non-Windows systems, or do not log into our domain, we will need to check the box for Basic Authentication.

We will get a security warning when we check basic authentication, because this action will be sending the username and password in clear text, which is a potential security risk. If possible, it is always best to use only Windows authentication. Doing this will require us to enter a username and password when we access this application. The Web application will then use that username and password to create the custom counters. The login that we will use will need to have sufficient permissions to create these counters.

Enumerating Services

One thing that's important to view is the services that are installed on a server and their state. WMI lets you easily find out this information with a single query. The query itself is SELECT * FROM WMI_Service, and the code to execute it is as follows:

C#
 ManagementObjectSearcher s =   new ManagementObjectSearcher( "SELECT * FROM Win32_Service" ); 
VB
 Dim s As New ManagementObjectSearcher( "SELECT * FROM Win32_Service" ) 

The demonstration program has a page that allows you to see both the services on the server and their states. This program is shown in Figure 24.7.

Figure 24.7. This Page Shows All of the Services and Their States.

graphics/24fig07.jpg

Listing 24.1 shows the code that queries for the services and then enumerates them into a Label that's part of the user interface. The first thing is the creation of a ManagementObjectSearcher object with the query as an argument to its constructor. (As I noted previously, the query is SELECT * FROM Win32_Service.)

To enumerate through the collection, the ManagementObjectSearch.Get() method is used (in the case below, the code is actually s.Get()). This method is used in the following code.

Listing 24.1 This Code Enumerates the Services and Can Be Found in EnumerateServices.aspx.cs (or EnumerateServices.aspx.vb for the VB Version).
 private void Page_Load(object sender, System.EventArgs e) {   //Request the collection of services   ManagementObjectSearcher s =     new ManagementObjectSearcher( "SELECT * FROM Win32_Service" );   //Enumerate through the collection   foreach( ManagementObject service in s.Get() )   {     // Store the information in the user interface label.     Enumeration.Text += ( "<tr><td>" + service["Name"] +       "</td><td>" +       service["State"] + "</td></tr>" );   } } 

Enumerating WMI Objects

The code in Listing 24.1 shows how to query the Win32_Service object, but before that, you might need to know what objects are available. The second page of the demonstration application shows which WMI objects are available. There are two ways, though, to get the information. The first is not a deep enumeration, and the second is a deep enumeration. The deep enumeration will show all WMI objects with a recursive enumeration; otherwise, only immediate classes are found. The page is shown in Figure 24.8 with the available WMI objects.

Figure 24.8. This Page Lets You View an Enumeration of All WMI Objects.

graphics/24fig08.jpg

Listing 24.2 shows the code that enumerates the WMI objects. The code starts off by clearing the ListBox (named Enumeration) and Label (named Details) user interface objects. A Management object is created with which the enumeration will be done. A special object type of EnumerationOptions is created, with which we'll let the ManagementClass know how to perform the enumeration.

Iterating through the enumeration gives us a series of Management Object objects, each of which represents a WMI class. The name of the class is retrieved with the following code:

C#
 string strClassName = ( "" + obj["__Class"] ); 
VB
 Dim strClassName As String = ( "" + obj["__Class"] ) 

Once the class name has been retrieved, it is added to the ListBox object with its Add() method.

There is a CheckBox that allows the user to ask to have related classes shown. To enable the user to do this, for each class that's found and added to the ListBox, we must enumerate its classes with a newly created ManagementClass object. These ManagementClass objects take the class name in as an argument to the constructor. Before, we didn't give the constructor any arguments and it did a top-level enumeration.

We'll enumerate through each of these related ManagementClass objects to retrieve the classes that belong to it. The class names are retrieved and added to the ListBox. But these class names begin with three blank spaces and an asterisk so that the user will see that they are related classes.

Also seen in Listing 24.2 is the Enumeration_SelectedIndexChanged() method. This method is fired when the user clicks any of the items in the ListBox. The first thing, though, that's examined is the starting character of the selected item in the ListBox. If that is a blank space, then it represents a related WMI class, and we won't show the details.

If the selected item is a top-level WMI object, we'll perform a query on it using a ManagementObjectSearcher. These objects take an argument in the constructor that contains the query that's to be performed. The following shows example queries for the Win32_Desktop class:

C#
 ManagementObjectSearcher s =   new ManagementObjectSearcher( "SELECT * FROM Win32_Desktop" ); 
VB
 Dim s As New ManagementObjectSearcher( "SELECT * FROM Win32_Desktop" ) 

Enumerating through the ManagementObjectSearcher object yields a sequence of ManagementObject classes. Each of these classes contains a collection of PropertyData objects that have the properties for the WMI class.

Listing 24.2 This Code Enumerates the WMI Objects and Can Be Found in EnumerateWMIObjects.aspx.cs (or EnumerateWMIObjects.aspx.vb for the VB Version).
 private void PopulateEnumerationList() {   // Clear the ListBox and the Label.   Enumeration.Items.Clear();   Details.Text = "";   // We'll need a ManagementClass object, so create   //   one here.   ManagementClass newClass = new ManagementClass();   // We'll need an EnumerationOptions object, so create   //   one here and set the EnumerateDeep property   //   according to the DeepEnumeration CheckBox.   EnumerationOptions options = new EnumerationOptions();   options.EnumerateDeep = DeepEnumeration.Checked;   // Iterate through the ManagementObjects that were found.   foreach( ManagementObject obj in newClass.GetSubclasses( options ) )   {     // Create a string in a local variable that     //   represents the class name. This makes     //   the code easier later in this method.     string strClassName = ( "" + obj["__Class"] );     Enumeration.Items.Add( strClassName );     // If we're set to show related classes, do     //   the following code.     if( ShowRelated.Checked )     {       try       {         // Get a ManagementClass object that represents         //   this class.         ManagementClass c =           new ManagementClass( strClassName );         // Find all ManagemenuClass instances.         foreach( ManagementClass r in c.GetRelatedClasses() )         {           // Add to the user interface label.           Enumeration.Items.Add( " &nbsp;&nbsp;&nbsp;* " +             r["__CLASS"] );         }       }       catch( Exception ex )       {         // Let user know about the error.         Enumeration.Items.Add(             string.Format( " &nbsp;&nbsp;&nbsp;Exception: {0} for {1}",               ex.Message.ToString(), obj["_Class"] ) );       }     }   } } private void Page_Load(object sender, System.EventArgs e) {   if( !IsPostBack )   {     PopulateEnumerationList();   } } private void Enumeration_SelectedIndexChanged(object sender, System.EventArgs e) {   // Get the selected class in a local string variable.   string strSelection = Enumeration.SelectedItem.Value;   // Clear the user interface label.   Details.Text = "";   // If this selection starts with a leading ' '   //   then it is a related class and we won't do this.   if( strSelection[0] != ' ' )   {     //Request the collection of services     ManagementObjectSearcher s =       new ManagementObjectSearcher( "SELECT * FROM " +         strSelection );     //Enumerate through the collection     foreach( ManagementObject obj in s.Get() )     {       // Find PropertyData objects from the ManagementObject.       foreach( PropertyData objPropertyData in obj.Properties )       {         // Display the results in the user interface         //   Label.         Details.Text +=             string.Format( "{0} ({1}): {2}<br>\r\n",               objPropertyData.Name,           objPropertyData.Type,           objPropertyData.Value );       }     }   } } 

WMI Security

Your code will often make WMI queries to remote servers. In most of these cases, you'll have to authenticate to the remote servers. This section talks about doing that.

The biggest difference between the code you're going to look at now and the examples you've already seen is that this next example uses a ManagementObject constructor that takes an additional argument in its constructor. The constructor used takes a ManagementScope object and a ManagementPath object. The examples in the previous section specified only a ManagementPath.

The ManagementScope object is used to specify information with regard to the remote server. The code below shows how to create a ManagementScope object where the constructor takes a server path string and a ConnectionOptions object as arguments. The server path string specifies the path to the server. The ConnectionOptions object contains information that allows user credentials to be presented for authentication and gets management objects for the C drive.

C#
 ConnectionOptions options = new ConnectionOptions(); options.Username = "domain\\username"; options.Password = "password"; ManagementScope scope = new ManagementScope(  "\\\\servername\\root\\cimv2",     options ); try {     scope.Connect();     ManagementObject disk = new ManagementObject( scope,         new ManagementPath( "Win32_logicaldisk='c:'" ), null );     disk.Get(); } catch( Exception ex ) {     // Handle exception here. } 
VB
 Dim options As New ConnectionOptions() options.Username = "domain\username" options.Password = "password" Dim scope As New ManagementScope(  "\\servername\root\cimv2", _     options ) Try     scope.Connect()     Dim disk As New ManagementObject( scope, _         New ManagementPath( "Win32_logicaldisk='c:'" ), Nothing )     disk.Get() Catch ex As Exception     ' Handle exception here. End Try 

Querying a WMI Object

The third page in the demonstration application allows users to query a single WMI object by name. This page can be seen in Figure 24.9.

Figure 24.9. You Can Query a Single WMI Object from This Page.

graphics/24fig09.jpg

The first thing the code in Listing 24.3 does is to get the object name from the user interface TextBox object (named ObjectName). This TextBox is cleared, and the Details Label is emptied. A ManagementSearcherObject object is created with the query as a parameter to the constructor. The query will end up being something similar to SELECT * FROM Win32_Printer.

Enumerating through the ManagementSearchObject object yields a collection of ManagementObject objects. These in turn have a collection of PropertyData objects. The PropertyData objects have three properties that we're interested in: the Name, Type, and Value properties. These three properties are converted to text and added to the Details Label object.

Listing 24.3 This Code Queries a Single WMI Object and Can Be Found in QuerySingle.aspx.cs (or QuerySingle.aspx.vb for the VB Version).
 private void Query_Click(object sender, System.EventArgs e) {   // Get the object that the user typed in.   string strSelection = ObjectName.Text;   // Clear out the object name TextBox and the   //   user interface Label into which the   //   results will be placed.   ObjectName.Text = "";   Details.Text = "";   try   {     //Request the collection of services     ManagementObjectSearcher s =       new ManagementObjectSearcher( "SELECT * FROM " +         strSelection );     //Enumerate through the collection     foreach( ManagementObject obj in s.Get() )     {       // Find PropertyData objects from the ManagementObject.       foreach( PropertyData objPropertyData in obj.Properties )       {         // Display the results in the user interface         //   Label.         Details.Text += ( objPropertyData.Name + " (" +           objPropertyData.Type + "): " +           Convert.ToString( objPropertyData.Value ) +           "<br>\r\n" );       }     }   }   catch( Exception ex )   {     // Alert the user to the error.     Details.Text = ex.Message.ToString();   } } 

Viewing the Event Log

There are times when you'll want to view the Event Log to see what's happened. The EventLog class allows you to retrieve all events in the Event Log. The fourth page in the demonstration application does this, and can be seen in Figure 24.10. This information would be good for identifying problems in specific applications. For instance, if an application keeps failing, then many times diagnostic information can be found in the EventLog.

Figure 24.10. You Can View the Event Log through This Page.

graphics/24fig10.jpg

The code in Listing 24.4 shows how the Event Log is accessed. The first thing, though, that's done in the Page_Load() method is to retrieve the Event Log categories. To do this, an array of EventLog objects is retrieved with the EventLog.GetEventLogs() static method. Each member of the array has a property named Log that names the Event Log category to which the array element is associated. These values are added to the DropDownList object named EventLogList.

A helper method named EventData() populates the DataList object with the Event Log items. This method retrieves that data collection by creating an EventLog object and setting the Log property to the selected Event Log category. The results are bound to the DataList with its DataSource property and DataBind() method.

Listing 24.4 This Code Shows Event Log Entries and Can Be Found in ViewEventLog.aspx.cs (or ViewEventLog.aspx.vb for the VB Version).
 private void Page_Load(object sender, System.EventArgs e) {   // Only do this code the first time through, after   //   that the DropDownList will already be   //   populated.   if( !IsPostBack )   {     // Get the list of EventLog objects.     EventLog[] logs = EventLog.GetEventLogs();     // Loop through and add the logs to the     //   DropDownList.     for( int i=0; i<logs.Length; i++ )     {       EventLogList.Items.Add( logs[i].Log );     }     // Call the method that populates the DataList with     //   the event log items.     EventData();   } } void EventData() {   // Don't do anything if there is   //   no selection.   if( EventLogList.SelectedIndex < 0 )   {     return;   }   // Create an EventLog object.   EventLog log = new EventLog();   // Get the selected item and set the Log   //   property.   log.Log = EventLogList.SelectedItem.Text;   // Set the DataSource property and data bind   //   the DataList object.   EventList.DataSource = log.Entries;   EventList.DataBind(); } 

Using Performance Monitor

The demonstration application has a page in which users can view three of the built-in Performance Monitors. Developers might want these in order to monitor the server's health to make sure that their application isn't affecting it in a very adverse way. The monitors show the CPU utilization, the network throughput, and the available memory. This part of the application can be seen in Figure 24.11.

Figure 24.11. These Performance Monitor Figures Might Be Very Important to You.

graphics/24fig11.jpg

To avoid redundant code, I created a method that accepts strings specifying the Performance Monitor category, name, and instance name. Also given to this method is a string that specifies the formatting of the Performance Monitor value.

The method creates a PerformanceCounter object, giving its constructor the category, name, instance name, and server name. The server name is hard-coded because it doesn't change for this demonstration program. You could put this name into the Web.config file to make changing it easy.

With the PerformanceCounter object created, it's a simple matter of calling the NextValue() method to retrieve the counter's value. The value is then formatted and added to the user interface Label object named CounterValues.

The Page_Load() method calls this method three times: one for the Processor, one for the Network Interface, and one for the Memory Performance Monitors.

Listing 24.5 This Code Shows Some Performance Monitor Objects and Can Be Found in ViewPerfmon.aspx.cs (or ViewPerfmon.aspx.vb for the VB Version).
 // This is a helper method that takes a category, name, //   instance, and string format; gets the performance //   monitor object, and populates the user interface //   label with the results. void AddPerfValue( string strCategory, string strName,   string strInstance, string strFormat ) {   try   {     // Get the PerformanceCounter object. The third     //   argument is the server name.     PerformanceCounter objPerf =       new PerformanceCounter( strCategory, strName,       strInstance,       "BOOKWEBSITES");     // Get the next value with the NextValue()     //   method--this is a float.     float fValue = objPerf.NextValue();     // Store the results in the user interface Label     //   as a table row with three columns.     CounterValues.Text += ( "<tr><td>" + strCategory +       "</td><td>" + strName + "</td><td>" +       fValue.ToString( strFormat ) + "</td></td>\r\n" );   }   catch( Exception ex )   {     // Alert the user to the error.     CounterValues.Text += ( "<tr><td>" + strCategory +       "</td><td>" + strName + "</td><td>Error: " +       ex.Message.ToString() + "</td></td>\r\n" );   } } private void Page_Load(object sender, System.EventArgs e) {   // Here we call our helper method three times for the   //   Processor, Network Interface, and Memory objects.   AddPerfValue( "Processor", "% Processor Time",     "_Total", "0.00" );   AddPerfValue( "Network Interface", "Bytes Total/sec",     "Intel 8255x-based Integrated Fast Ethernet", "0.00" );   AddPerfValue( "Memory", "Available Bytes",     "", "N" ); } 

Custom Performance Monitors

There are times when your application may need custom Performance Monitors. A good example of this might be when you need to determine the performance of an application. This section talks about adding a custom Performance Monitor that keeps track of page hits to the page, and an example can be seen in Figure 24.12.

Figure 24.12. This Simple Page Shows How to Create a Custom Performance Monitor Object.

graphics/24fig12.jpg

The code in Listing 24.6 creates a custom Performance Monitor that counts page hits. The code starts off by calling the PerformanceCounterCategory.Exists() static method to see whether the counter named "ASP.NET Solutions" exists. If it doesn't, then it needs to be created.

To create the counter, a CounterCreationDataCollection object is instantiated named CounterGroup. The code then creates a Counter CreationData object named CounterData. The CounterData object has three properties that are set, determining its name, help string, and type. The CounterCreationData object is then added to the CounterCreationDataCollection object.

With the counter either found or newly created, we get a Performance Counter object that references it. The counter is incremented with the Increment() method. Finally, the counter value is shown in the user interface Label named PageHits.

Listing 24.6 This Code Shows a Custom Performance Monitor Object and Can Be found in PageViews.aspx.cs (or PageViews.aspx.vb for the VB Version).
 private void Page_Load(object sender, System.EventArgs e) {   // See if the performance counter already exists.   if( !PerformanceCounterCategory.Exists( "ASP.NET Solutions" ) )   {     // If not, we'll create and add it.     // Create the counter collection object.     CounterCreationDataCollection CounterGroup =       new CounterCreationDataCollection();     // Create the counter data object.     CounterCreationData CounterData =       new CounterCreationData();     // Give it a name, enter some useful help,     //   and set the item type for 32-bit     //   number of items.     CounterData.CounterName = "Page Hits";     CounterData.CounterHelp = "This gives the number of page hits.";     CounterData.CounterType = PerformanceCounterType.NumberOfItems32;     // Add the data object.     CounterGroup.Add( CounterData );     // Create the performance counter.     PerformanceCounterCategory.Create( "ASP.NET Solutions",       "This gives the number of page hits.", CounterGroup );   }   // Create a PerformanceCounter object with   //   which we'll get the performance data.   PerformanceCounter Counter =     new PerformanceCounter( "ASP.NET Solutions",       "Page Hits", "BOOKWEBSITES", false );   // But first increment the counter.   Counter.Increment();   // Dislay the count in the user interface label.   PageHits.Text = "Page Hits: " +     Counter.NextValue().ToString( "0" ); } 


ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ISBN: 321159659
EAN: N/A
Year: 2003
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net