Hello, you fool, how are you? (Part 2 of 5)

by ManniAT 17. April 2009 21:31
Technorati-Tags: ,

In the first part of this series I describe the approach of this application.

In this part I will describe the PerfInfo class.
First of all – I return some calculated properties – so in reality it could be a good Idea to remove those properties to save bandwidth.

But on the other hand – in real life we would return other (more) properties so I keep these “the client could also calculate them” properties.

The class is used to transmit server performance information to the client. Let’s start with the properties.

  1. CPUPercent (as seen in Taskmanager)
  2. RamUsedPercent
  3. RamTotal
  4. RamFreee
  5. RamUsed
  6. ErrMess

The last parameter is added for simplified error handling. Instead of throwing an exception my class sets ErrMess to another value than “OK” – and the client simply uses this value to display error information.

To get the information (some of them) I use a class called PerformanceCounter.
The use of this class needs some privileges. To get them you can either do some kind of impersonation – or give the ASPX account the appropriate rights.

I’m not sure if this is default – but my “NetworkService” account already belongs to a group called “Performance Log Users”.
Maybe I did it years ago – anyhow ensure that your ASPX account is also in that group.

So now we are able to use the PerformanceCounter class. By the way – this class enables you to provide your own counters, but this is beyond the scope of this article.

m_pcCPUCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);

Let’s take a look at the constructor. The first parameter is the category, the second the name of the counter, followed by the instance name and finally a boolean which tells if we want to use the counter in read only mode.

If you are curious about the values you can use – simply start the performance monitor and add a counter. The values in the upcoming dialog are the values you can use here. And if you are (like me) a “victim of a non English OS” – yes you can use localized strings here also. But while localized values need the correct OS language on the server the English versions are always available.

You can also search the registry for the values – HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Perflib\xxx.
But expect a lot of entries there (it’s a REG_MULTI_SZ called Counter) – for me more than 2k elements exist.

Expect that you can retrieve everything you get in Performance Monitor.

I use 2 Counters in my example:

m_pcCPUCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);
m_pcRAMCounter = new PerformanceCounter("Memory", "Available MBytes", true);

 

The two variables used are private class members. There is a big difference between CPU and RAM – while CPU (%) is calculated RAM is a static counter.
This makes a difference in retrieving values. For CPU we use NextValue() while we can access RawValue on the RAM counter.

NextValue() (for a new calculated counter) will always return zero. The next call retrieves the first value. Depending on the counter this value makes sense – or not. For CPU usage % it makes sense – for  things like “Alerts activated/m” it may take some time until you get good samples.

And there is another thing – the documentation states that between two calls to NextValue() you must delay at least one second.
I my case I use 0.5 seconds – plus the time to do other things – it works :)

But calls to NextValue are synchronized and locked – so don’t build a service which calls this function every millisecond. Unless you want your webserver to have performance problems. This is (I guess) one of the reasons why Performance Monitor does not allow “ultra fast” refreshes.

I built my class in a way that allows the read of new values every 2 seconds (1 would be ok also).
And I do this transparent – so a call always looks OK – it simply returns old values.

For this my update function has the following section:

//don't allow new read faster than 2 seconds
//1 is technical OK - less does not meet performance counter read rules!!
if ((DateTime.Now - m_dtLastRead).TotalSeconds < 2) {
    return(true);    //this in not an error - simpyl use current values
}

My class offers three different constructors.
One which does not initialize the counters (to use it as holder for setting ErrMess) one initializing the counters and last not least one which sets a special flag indicating that the instance should not be disposed. (I’ll explain later why I do this).

Initialization creates the counters, makes a first (getting 0) read to the CPU – checks for total memory – and last not least it reads the CPU counter a second time to get a valid value.

//for serialization and errmess objects
public PerfInfos() {
    m_dtLastRead = DateTime.MinValue;
    ErrMess = "OK";
}
//init the counters
public PerfInfos(bool bInit) {
    m_dtLastRead = DateTime.MinValue;
    ErrMess = "OK";
    if (bInit) {
        if (InitCountersAndTotalRAM()) {
            ReadCurValues();
        }
    }
}
//create object for cache
public PerfInfos(bool bInit, bool bImInCache) {
    m_bIamInCache = bImInCache;
    m_dtLastRead = DateTime.MinValue;
    ErrMess = "OK";
    if (bInit) {
        if (InitCountersAndTotalRAM()) {
            ReadCurValues();
        }
    }
}

The real work is done in InitCountersAndTotalRAM:

//tries to init the performance counters and read total ram
private bool InitCountersAndTotalRAM() {
    try {
        m_pcCPUCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true);
        m_pcRAMCounter = new PerformanceCounter("Memory", "Available MBytes", true);
        //the cpu counter needs an initial call which returns 0
        m_pcCPUCounter.NextValue();
        ReadTotalRAM();
        Thread.Sleep(500);    //documentation says - 1 second befor next read :)
        return (true);
    }
    catch (Exception eX) {
        ErrMess = eX.Message;
        //cleanup if an error occured
        FreeCounters();
        return (false);
    }
}

There is a “FreeCounters” – the reason – counters have a Dispose function (unmanged stuff inside Winking) so we have to free them.

//also used by dispose
private void FreeCounters() {
    if (m_pcCPUCounter != null) {
        m_pcCPUCounter.Dispose();
        m_pcCPUCounter = null;
    }
    if (m_pcRAMCounter != null) {
        m_pcRAMCounter.Dispose();
        m_pcRAMCounter = null;
    }
}

Don’t think about “what’s going on inside” – follow a simple rule: If there is a Dispose call it ASAP.

And since we (as class) use things that need to be disposed – we also implement IDisposable.
If you build such a thing on your own – there is a great example in the online help for IDisposable.

You will notice that performance counters (Performance Monitor) has no value for “Total Ram”.
So how do we get this? System.Management is the solution:

//total ram should not change during object lifetime (hot added ram!!)
private void ReadTotalRAM() {
    //Performance counters for total memory do not exists - use Management to query it
    SelectQuery query = new SelectQuery("Win32_MemoryArray");
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
    ManagementObjectCollection mOC = searcher.Get();
    ManagementObject mO = (from ManagementObject X in mOC select X).First();
    RamTotal = Int32.Parse(mO["EndingAddress"].ToString()) / 1024;
}

We simply make a query about the memory and parse the value.
By the way – WMI enables much more than just information about memory.

I will discuss it later in detail – but at the moment assume that I want to cache an object of this class – and I want to do this via a WCF service. And (also explained later) this will result in an “unexpected Dispose”.

This is the reason for the third constructor which set’s a flag that is check by the Dispose function.
In simple words – someone calls dispose although the object still exists – and this flag prevents the object from this fact.

public void Dispose() {
    if (m_bIamInCache) {    //do not dispose cached objects!!
        return;
    }
Dispose(true);

Finally a look at the Update function for this class:

 

//a locked version for the simple use a service
public bool LockedReadCurValues() {
    lock (this) {
        return (ReadCurValues());
    }
}
public bool ReadCurValues() {
    //dispose already called?
    if (m_bDisposed) {
        throw (new Exception("Object is disposed and no longer usable"));
    }
    //don't allow new read faster than 2 seconds 
//1 is technical OK - less does not meet performance counter read rules!!
    if ((DateTime.Now - m_dtLastRead).TotalSeconds < 2) {
        return (true);    //this in not an error - simpyl use current values
    }
    //check if initialized
    if (ErrMess == "OK" && m_pcCPUCounter == null) {    //either both are null or both are set
//looks like not initialized
        if (!InitCountersAndTotalRAM()) {    // this sets ErrMess on problems
            return (false);
        }
    }
    try {
        //calculate the ram values
        RamFree = (int)m_pcRAMCounter.RawValue;    //this is not a calculated value 
//- no need for slower NextValue()
        RamUsedPercent = ((double)(RamTotal - RamFree)) / (double)RamTotal * 100.0;
        RamUsed = RamTotal - RamFree;
        //read CPU percent
        CPUPercent = m_pcCPUCounter.NextValue();    //here we get the value
        ErrMess = "OK";    //OK if we reach this point
        m_dtLastRead = DateTime.Now;
        return (true);
    }
    catch (Exception ex) {
        ErrMess = ex.Message;
        return (false);
    }
}

The first function simply encapsulates the ReadCurValues with a lock. Something like this is a must since the function get’s called from “somewhere” and the “somewhere” can have multiple instances.

Now I finish this part – and continue with an extra part – including an explanation about the “unwanted Dispose call”.

Tags: ,

telerik

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


Powered by BlogEngine.NET 2.0.0.0