Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

Performance Monitoring a la .NET

Geschrieben von: Christoph Wille
Kategorie: ASP.NET

This printed page brought to you by AlphaSierraPapa

Im Artikel Einführung in Performance Monitoring habe ich grundsätzlich erklärt, wie Performance Monitoring funktioniert. Im daran anschließenden Artikel Monitoring von ASP ging es dann darum die Performance Counter innerhalb einer ASP Seite auszulesen - mit Hilfe einer Komponente.

Im heutigen Artikel werde ich Ihnen zeigen, um wieviel einfacher es in ASP.NET unter Zuhilfenahme des System.Diagnostics Namespaces des .NET Frameworks geht. Keine externen Komponenten mehr, dafür umso mehr Funktionalität. Man kann sogar Teile des NT Performance Monitors nachbauen, ohne wirklich viel Zeit dafür aufwenden zu müssen.

Voraussetzung um den Sourcecode dieses Artikels verwenden zu können ist eine Installation des Microsoft .NET Framework SDK's auf einem Webserver. Weiters ist es von Vorteil, daß der Leser schon Grundkenntnisse bzw. Erfahrung in Programmiertechniken besitzt - es finden sich etliche Artikel auf diesem Server, um das notwendige Wissen von Grund auf zu erlernen.

Sicherheitshinweis: die Beispielannahme ist, daß der ASP.NET Worker Prozess unter dem SYSTEM Konto läuft (standardmäßig läuft er unter dem ASPNET Account). Bitte ändern Sie processModel in machine.config entsprechend ab, oder geben nur Administratoren Zugriff auf die Dateien (Impersonation).

Was ist das Ziel des Artikels?

Schauen Sie sich den folgenden Screenshot an, das ist die Add Counters Dialogbox des Performance Monitors:

In diesem Artikel möchte ich Ihnen zeigen, wie man diese Dialogbox (fast) ident in ASP.NET nachprogrammieren kann, ohne in die Tiefen der Windows Programmierung eintauchen zu müssen. Die einzig prominente Auslassung ist die Auswahl des Computers, und zwar deswegen, weil man sich damit schnell Sicherheitsprobleme einhandelt - da das Auslesen von Performance Countern mehr Rechte benötigt, als der anonyme Websiteuser hat.

Die ersten Schritte

Starten wir also mit einem minimalen Beispiel. Dieses testet nur, ob ein spezifischer Counter in der Kategorie "ASP.NET" auf dem Rechner, auf dem es abläuft, installiert ist (basictest.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<%
if ( !PerformanceCounterCategory.CounterExists("State Server Sessions Active", 
     "ASP.NET", ".") ) 
{
  Response.Write("No state server counters on this machine");
}
else
{
  Response.Write("State Server counter is available.");
}
%>

Hinweisen möchte ich speziell auf das @Import Namespace Statement: dieses benötige ich, damit der Namespace System.Diagnostics korrekt zu meiner ASP.NET Seite gebunden wird. Sonst kann ich das PerformanceCounterCategory Objekt (hier die Klasse mit einer statischen Methode) nicht verwenden.

Kategorien abfragen

Gehen wir nun zu einem etwas umfangreicheren und funktionalerem Beispiel - das Auflisten aller Performance Counter Kategorien in einer Drop Down Liste (listcounters.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<% @Import Namespace="System.Collections" %>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E) 
{
  if (!Page.IsPostBack)
  {
    ArrayList arrItems = new ArrayList();
    PerformanceCounterCategory[] arrCategories = 
	          PerformanceCounterCategory.GetCategories();
    for (int i=0; i < arrCategories.Length;i++)
      arrItems.Add(arrCategories[i].CategoryName);
    Category.DataSource = arrItems;
    Category.DataBind();
  }
}
</script>

<html>
<body>

<h3>Performance Counter Kategorien auflisten</h3>

<form runat="server" method="post">
Kategorie:  
<asp:dropdownlist id="Category" runat="server"/>
</form>

</body>
</html>

Zäumen wir das Pferd von hinten auf, und zwar beim Formular: das ist ein Web Form, der nur eine einzige Drop Down Liste beinhaltet. Diese hat den Namen Category, und ist leer. Woher kommen also die Einträge in dieser Liste?

Gefüllt wird diese Drop Down im Page_Load Event. Dieses wird jedesmal aufgerufen, wenn die Seite ausgeführt wird, also auch dann, wenn es sich bereits um einen Post Back mit Resultaten handelt. Da der View State die Einträge für uns managt, kann man darauf verzichten, die Liste nochmals aufzufüllen.

Der interessante Teil des Codes, der die Kategorien ausliest ist folgender:

PerformanceCounterCategory[] arrCategories = 
         PerformanceCounterCategory.GetCategories();

Damit habe ich ein Array von PerformanceCounterCategory Objekten in der Hand, das in der Eigenschaft CategoryName den Namen der Kategorie speichert. Und wie bekomme ich nun dieses Array in die Drop Down Liste? Eine Brute-Force Variante zeigt dieser Code:

ArrayList arrItems = new ArrayList();
for (int i=0; i < arrCategories.Length;i++)
      arrItems.Add(arrCategories[i].CategoryName);
Category.DataSource = arrItems;
Category.DataBind();

Einfach das Array von PerformanceCounterCategory Objekten durchlaufen, die CategoryName's herauskopieren und in eine einfache ArrayList stecken. Diese kann als DataSource dem Drop Down zugewiesen werden, und mit DataBind wird die Liste dann aufgefüllt.

Daß das Herumkopieren nicht wahnsinnig elegant geschweige denn performanceförderlich ist, sieht man leicht. In den nächsten Beispielen zeige ich dann, wie es schneller und besser geht, und wie einfach ASP.NET diesen Task für uns macht.

Hilfetexte zu Kategorien und Countern anzeigen

Gehen wir mit dem Beispiel einen Schritt weiter. Der Performance Monitor kann zu jeder Kategorie als auch zu jedem Counter einen Hilfetext anzeigen. Wäre es nicht toll, wenn unsere Applikation das auch könnte? Der folgende Code zeigt, wie man es für die Kategorien realisiert (categoryhelp.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<% @Import Namespace="System.Collections" %>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E) 
{
  if (!Page.IsPostBack)
  {
    PerformanceCounterCategory[] arrCategories = 
         PerformanceCounterCategory.GetCategories();
    Category.DataSource = arrCategories;
    Category.DataBind();
  }
}

void OnDisplayCategory(Object sender, EventArgs e)
{
  string strCategory = Category.SelectedItem.Value;
  PerformanceCounterCategory pcInfo = 
         new PerformanceCounterCategory(strCategory);
  Message.Text = pcInfo.CategoryHelp;
}
</script>

<html>
<body>

<h3>Performance Counter Hilfe</h3>

<form runat="server" method="post">
Kategorie:  
<asp:dropdownlist id="Category" AutoPostBack="True" 
    DataTextField="CategoryName" DataValueField="CategoryName"
      runat=server OnSelectedIndexChanged="OnDisplayCategory"/>

<p><b>Hilfetext:</b>&nbsp;
<asp:Label id="Message" runat="server" />
</p>
</form>

</body>
</html>

Gut, fangen wir wieder von hinten an. Das Formular sieht jetzt bereits deutlich umfangreicher aus als zuvor. Die wichtigsten Änderungen betreffen die Drop Down Liste:

Die letzten beiden Attribute sind unser Ticket zur Vereinfachung des Page_Load Codes: adieu zusätzliche Collection, ASP.NET übernimmt das jetzt alles für uns, und zwar ohne Umkopieren!

PerformanceCounterCategory[] arrCategories = 
         PerformanceCounterCategory.GetCategories();
Category.DataSource = arrCategories;
Category.DataBind();

Das Event OnDisplayCategory ist im Grund sehr einfach gehalten. Die selektierte Kategorie wird ausgelesen, damit ein PerformanceCounterCategory Objekt erstellt, und von diesem die CategoryHelp Eigenschaft ausgelesen. Und um es dem Benutzer auch präsentieren zu können, wird dieser String in eine Label Control geschrieben (als SPAN Tag im Browser repräsentiert).

Damit haben wir allerdings erst einen Teil der Miete - die Performance Counter Kategorien. Ein Schritt weiter ist das Anzeigen der Counter für die jeweilige Kategorie, sowie der Hilfetexte für die Counter. Da der Sourcecode dafür doch etwas "länglich" ist, kann man das Listing hier betrachten (fullhelp.aspx).

Die wichtigste Neuerung ist eine Listbox für die Performance Counter:

<asp:ListBox id="Counters" Width="200px" runat="server" 
  DataTextField="CounterName" DataValueField="CounterName"
  AutoPostBack="True" 
  runat=server OnSelectedIndexChanged="OnHelpCounter"/>

Diese Listbox löst ebenfalls ein AutoPostBack aus, und ich verwende wieder DataTextField und DataValueField um die Werte hineinzuschreiben. Um das zum Laufen zu bringen, muß ich natürlich den Code in OnDisplayCategory dahingehend adaptieren, daß auch die Counters Listbox mit Daten gefüllt wird, nicht nur das Label mit der Hilfe über die Kategorie:

PerformanceCounter[] arrCntrs = pcInfo.GetCounters();
Counters.DataSource = arrCntrs;
Counters.DataBind();

Zu guter Letzt muß man noch den Hilfetext für den Counter anzeigen, und dies geschieht in OnHelpCounter:

void OnHelpCounter(Object sender, EventArgs e)
{
  string strCategory = Category.SelectedItem.Value;
  string strCounter = Counters.SelectedItem.Value;
  try
  {
    PerformanceCounter pcInfo = new PerformanceCounter(strCategory, strCounter);
    CounterHelp.Text = pcInfo.CounterHelp;
  }
  catch
  {
     // drop dead silently - mostly on category changes
  }
}

Wie man sieht, nicht gerade "Magic", aber sehr effektiv. Man hat zwar sehr viele Roundtrips zum Server, aber theoretisch könnte man mit versteckten SPAN's und Client-side Code arbeiten, um es Netzwerk-effizienter zu machen.

Performance Counter Werte abfragen

Wir nähern uns unserem Ziel mit Riesenschritten - also was fehlt uns noch? Das prominenteste sind die Instanzen der Counter. Instanzen für Counter werden für eine ganze Kategorie definiert, allerdings hat nicht jeder Counter der Kategorie auch Instanzen. Ein Beispiel für Instanzen wäre etwa die Processor Kategorie, wo jeder Prozessor über eine Instanz angesprochen werden kann (oder alle auf einmal).

Ebenso fehlt auf der Liste der To-Do's noch das Abfragen des Wertes des Performance Counters. Auch das habe ich in das Abschlußbeispiel eingebaut, das sich dem Anwender wie folgt präsentiert:

Aus Übersichtlichkeitsgründen im Sourcecode habe ich den Code für die Hilfetexte herausgenommen, damit man sich auf das Wesentliche konzentrieren kann. Zeilenmäßig gesehen ist das Listing dennoch lange genug! (querycounter.aspx)

Starten wir aus alter Gewohnheit "wieder mal" beim Formularcode:

<form runat="server" method="post">
<table width="400">
<tr><td>Kategorie:</td><td>Counter:</td><td>Instances</td></tr>
<tr><td valign="top">
<asp:dropdownlist id="Category"  AutoPostBack="True" 
	DataTextField="CategoryName" DataValueField="CategoryName"
	runat=server OnSelectedIndexChanged="OnDisplayCategory" />
</td><td valign="top">
<asp:ListBox id="Counters" Width="200px" runat="server" 
	DataTextField="CounterName" DataValueField="CounterName"
  	AutoPostBack="True" 
	OnSelectedIndexChanged="OnCounterInfo"/>
</td><td valign="top">
<asp:ListBox id="CounterInstances" Width="100px" runat="server" />
</td></tr>
</table>
<asp:button OnClick="SubmitBtn_Click" text="Lookup" runat="server"/>
</form>

Wie man sehen kann, ist eine weitere Listbox dazugekommen, und zwar für die etwaigen Instanzen eines Counters. Sonst hat sich eigentlich nichts geändert. Ach ja, eine Schaltfläche namens "Lookup" ist dazugekommen, die einen Postback auslöst, der dann das Auslesen des Performance Counters triggert.

Was uns zum Event OnCounterInfo bringt - dieses hat so einige Änderungen über sich ergehen lassen müssen:

void OnCounterInfo(Object sender, EventArgs e)
{
	string strCategory = Category.SelectedItem.Value;
	CounterInstances.Items.Clear();
	
	PerformanceCounterCategory pcCat = new PerformanceCounterCategory(strCategory);
	string[] arrInstanceNames = pcCat.GetInstanceNames();
	if (arrInstanceNames.GetLength(0) > 1)
	{
	  CounterInstances.DataSource = arrInstanceNames;
	  CounterInstances.DataBind();
	}
}

Wie ich bereits ausgeführt habe, sind die Instanzen pro Kategorie definiert, allerdings muß nicht jeder Counter der Kategorie die Instanzen auch zur Verfügung stellen - daher wird die CounterInstances Listbox nach Bedarf mit Werten gefüllt. Und ab diesem Zeitpunkt kann man durch Klicken auf die Lookup Schaltfläche den aktuellen Wert ermitteln:

void SubmitBtn_Click(Object sender, EventArgs e) 
{
  string strCat = Category.SelectedItem.Value;
  string strCounter = Counters.SelectedItem.Value;
  string strInstance = "";
  
  if (CounterInstances.Items.Count > 0)
    strInstanceName = CounterInstances.SelectedItem.Value;
    
  PerformanceCounter pc = 
        new PerformanceCounter(strCat, strCounter, strInstance);

  float dResult = pc.NextValue();
  Message.Text = "<" + strCat + "> [" + strCounter + 
	               "] {" + strInstance + "} = " + dResult.ToString();
}

Der Übersichtlichkeit halber habe ich den Exception Handling Code herausgenommen (für Sicherheitsverletzungen gedacht). Ein einziger Aufruf an NextValue liefert den aktuellen Wert zurück, und der wird dann dem Benutzer präsentiert. Damit wäre unsere heutige Aufgabe erledigt.

Wer mit den Performance Countern in diesem Beispiel jetzt ein wenig herumspielt wird sehen, daß einige Werte nicht so sind wie erwartet. Ich habe natürlich nicht die volle Funktionalität aller Countertypen implementiert (Prozent, Total, etc.). Wer das implementieren möchte, der findet die notwendigen Informationen, wie man korrekt mit Performance Countern rechnet, in der Hilfe des .NET Framework SDK unter folgendem Eintrag:

Schlußbemerkung

Im heutigen Artikel haben wir uns mit Performance Monitoring a la .NET beschäftigt, und ich hoffe die Leistungsfähigkeit anhand der trotzdem eigentlich simplen Beispiele ganz gut demonstriert zu haben. Nehmen Sie sich die Zeit, und lesen in der Dokumentation über den Leistungsumfang des Performance Monitorings nach!

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20000809.zip

Verwandte Artikel

Ausgabe der Event Log Einträge a la ASP.NET
http:/www.aspheute.com/artikel/20000811.htm
Einführung in ASP.NET Web Forms
http:/www.aspheute.com/artikel/20000808.htm
Einführung in Performance Monitoring
http:/www.aspheute.com/artikel/20000428.htm
Monitoring von ASP
http:/www.aspheute.com/artikel/20000502.htm
Performancemessungen in .NET
http:/www.aspheute.com/artikel/20011206.htm
Services über das Web managen
http:/www.aspheute.com/artikel/20000925.htm
Webserver-Tuning mit XTune
http:/www.aspheute.com/artikel/20000814.htm

Links zu anderen Sites

Die aspDEdotnet Diskussionsliste
http://www.dotnetgerman.com/listen/aspDEdotnet.asp

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.