Ras Statistics  for winNT

In windows NT, we can get the RAS statistics by using some of the performance monitoring techniques.

Before I go any further, let me state this : " I am a novice at NT !".  I only recently got NT (special thanks to the Microsoft MVP program), and although I have it loaded on my computer, I haven't switched over from win98 yet (actually, I probably won't go to NT, but rather to win2000).  So please don't take what I say here as gospel...  all I know is that it works.  Oh, and if you find this information useful, let Microsoft know that you want them to continue the MVP program, because without it, this page would not exist.

Having said that, it's onto performance monitoring on NT aimed specifically at RAS.

Performance monitoring:

On NT performance monitoring gives us the statistics for different processes, such as system, processor and the one we are interested in: RAS.

To get the performance data we can use a helper dll, access the registry data, or use the RAS's performance dll.  I can't find the performance helper dll (phd.dll) on my system, so I won't discuss that method at all. And although it would seem simpler and more direct to use the rasctrs.dll directly, the registry interface methods can be used with any performance data and is the method documented in the Platform SDK.  So I will only concentrate on the registry method here... 

First let's have a look at how the RAS statistics are stored.

Performance Object model:

RAS has two distinct performance objects:

  1. RAS Port
  2. RAS Total

The RAS Port object provides statistics for each port. You could think of it is a collection of the all the RAS ports.  Each port is referred to as an instance of the RAS Port object. The RAS Port object defines 17 counters (statistics) and then returns a value for each counter for each port.

The RAS Total object doesn't have any instances.  After all, how could it, there's only one total (unless you're doing your tax, that is).  It defines 18 counters, and returns one value for each counter.

See the RAS statistics overview page, for a list of the counters (if you haven't already)

So our two object models look like this:

Note: the RAS Port may have any number of instance definitions depending on the number of ports. 

The above diagram shows basically how the data is laid out when we use the registry or rasctrs.dll. Each block is just a consecutive series of bytes. When using the registry to get this data, the object blocks are preceded by a PERF_DATA_BLOCK, that tells us a bit about the system, but most importantly tells us the how many objects there are, and where the first one is in our series of bytes.

Using the registry:

To get an array of bytes from the registry that contains the above structures, we make a call to the regQueryValueEx function, using HKEY_PERFORMANCE_DATA as the key.  The difficulty is in determining what string to use for the lpValueName argument of the regQueryValueEx function. We could use "Global", but that would return all the performance data for the machine and we would then have to sift through the objects to find the ones we want. The alternative is to specify an object's title index, eg "2" would return the system.  The RAS Port object might have an index of "780", but we can't be sure.. we have to find it's index first.

Note: if you are using the rasctrs.dll you don't have to worry about finding the objects index, using "Global" returns just the RAS Port object and the RAS Total object.

NT stores each object's description and it's index in the counter value of this key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\langid 
where langid is the language identifier (usually you can use 009).

The data stored there is in the form of a null separated strings  ( REG_MULTI_SZ ) and  looks like this:1|1847|2|System|4|Memory|6|% Processor Time|10|File Read Operations/sec|12|File Write       and so on....   As, hopefully, you should be able to see, these null separated strings are in pairs of Index followed by Title.

This following code shows how to find the indexes for the RAS Port and RAS Total objects, and put them into a string such as "870 906" which we can then use in our call to get the performance data.  You will need the registry declares (at end of this page) for this code to work. The string we want is the variable named strId.

1. Getting the indexes for Ras Port and Ras Total objects Sample Code:
Dim rtn As Long, lngHkey As Long
Dim lngEnd As Long, lngStart As Long
Dim lpcbData As Long, strCounters As String
Dim strId As String, strTemp As String

rtn = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _
       "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009", _
       0&, KEY_READ, lngHkey)

rtn = RegQueryValueEx(lngHkey, "Counter", 0&, ByVal 0&, ByVal 0&, lpcbData)

strCounters = String(lpcbData, 0)

rtn = RegQueryValueEx(lngHkey, "Counter", 0&, ByVal 0&, _
                              	ByVal strCounters, lpcbData)

rtn = RegCloseKey(lngHkey)

lngEnd = InStr(3, strCounters, Chr$(0) & "RAS Port" & Chr$(0))
If lngEnd Then
   lngStart = InStrRev(strCounters, Chr$(0), lngEnd - 1)
   strId = Mid$(strCounters, lngStart + 1, lngEnd - lngStart - 1)
End If

lngEnd = InStr(3, strCounters, Chr$(0) & "RAS Total" & Chr$(0))
If lngEnd Then
   lngStart = InStrRev(strCounters, Chr$(0), lngEnd - 1)
   strTemp = Mid$(strCounters, lngStart + 1, lngEnd - lngStart - 1)
   If strId <> "" Then
      strId = strId & " " & strTemp
      strId = strTemp
   End If
End If

Okay, we have got our  indexes to the objects we want the data for in the strId variable from the code above.  But there was just one more thing before we go to far....

the object and counter definitions have fields in them that we can use to  identify the data, such as "Bytes received" etc. Unfortunately those name fields are usually empty, instead we have to use the name's Index field to tell us what the counter or object is.  So we have to use that string we got in the code before, the strCounters variable.

The problem is that we will need to find the corresponding description to the index for 35 counter description blocks, and for 2 object blocks, AND the string we are searching is large, often larger than 10Kb.  So what do we do ... well, one option is to use the split function to put the string into an array, then add the descriptions to a collection, using the index (as a string) as the key.

2. creating a counter names  collection
' note: this uses the strCounters from the code above
Dim i As Long
Dim astrNames() As String
Dim colNames As New Collection

astrNames = Split(strCounters, Chr$(0))

'On Error Resume Next

For i = 2 To UBound(astrNames) Step 2
   colNames.Add astrNames(i + 1), astrNames(i)
Next i

now let's go get some data:

Getting the data.

The following code gets the data for our objects specified by the strId that we got in 1.  and returns the data as an array of bytes, b().  Note how we don't have to call the RegOpenKey to access the HKEY_PERFORMANCE_DATA key, but we do have to close the key.

3. Getting the data
Dim b() As Byte
Dim lngIncr As Long
'Dim lpcbData As Long, rtn as long

   lngIncr = lngIncr + 2048
   lpcbData = lngIncr
   ReDim b(lpcbData - 1)
   rtn = RegQueryValueEx(HKEY_PERFORMANCE_DATA, strId, 0&, _
                                    ByVal 0&, b(0), lpcbData)

Loop While rtn = ERROR_MORE_DATA


Now all that is left to do is number crunch the data the byte array to sort into a format we can use.  

In the code below, I am going to use arrays of user defined types to put the data into.  This is just to keep it short and simple.  In reality, when you go to do this, you might want to replace the user defined types with classes, and the arrays with collections, so as you could access your data like:
Object("RAS Port").Instance("Com1").Counter("Bytes Recieved").Value

This code below needs all the other bits of code on this page. The PERF_XXX structures are declared at the end of this code

4. Crunching the bytes
'***** UDTs to store data in ****

Public Type VBRasStatCounter
   strName As String
   lngNameIndex As Long
   lngValueType As Long
   varValue As Variant
   base10 As Long
End Type

Public Type VBRasStatInstance
   strName As String
   typCounters() As VBRasStatCounter
End Type

Public Type VBRASStatObject
   strName As String
   lngNameIndex As Long
   typInstances() As VBRasStatInstance
End Type

Dim typPerfObject() As VBRASStatObject
 '***** Crunch those bytes ******

Dim prfDataBlock As PERF_DATA_BLOCK
Dim prfObjectDef As PERF_OBJECT_TYPE
Dim nObjects As Long ' number of objects
Dim nObjIdx As Long  ' used to loop through objects
Dim nInsts As Long ' number of instances
Dim nCntrs As Long ' number of counters
Dim iPos As Long ' marker for b()
Dim iNextObjPos As Long 'marker for next object in b()
Dim aValuePos() As Long 'markers for where the values start
Dim j As Long, k As Long, n As Long 'used for iteration
'Dim i as long 'should have already been dimmed.
'Dim strTemp As String 'should have already been dimmed.

'Get the number of objects & pointer to first object
CopyMemory prfDataBlock, b(0), LenB(prfDataBlock)
nObjects = prfDataBlock.NumObjectTypes
iPos = prfDataBlock.HeaderLength
' check to see that are some objects
If nObjects < 1 Then Exit Sub
nObjects = nObjects - 1 ' zero base our array
ReDim typPerfObject(nObjects)

'start our outer loop of objects
For nObjIdx = 0 To nObjects
'get the first object
   CopyMemory prfObjectDef, b(iPos), LenB(prfObjectDef)
   iNextObjPos = iPos + prfObjectDef.TotalByteLength
   nInsts = prfObjectDef.NumInstances
   nCntrs = prfObjectDef.NumCounters - 1
   If nInsts = -1 Then nInsts = 1
   nInsts = nInsts - 1 ' zero base our array
   On Error GoTo NextObject
   ReDim typPerfObject(nObjIdx).typInstances(nInsts)
   With typPerfObject(nObjIdx)
      For i = 0 To nInsts
         ReDim .typInstances(i).typCounters(nCntrs)
      Next i
   End With
   ' On Error Resume Next
   'get our object's name
   With typPerfObject(nObjIdx)
      .lngNameIndex = prfObjectDef.ObjectNameTitleIndex
      .strName = colNames(CStr(.lngNameIndex))
   End With
      'get some markers to retrieve values
   ReDim aValuePos(nInsts)
   If prfObjectDef.NumInstances = -1 Then
      'no instance def blocks
      aValuePos(0) = iPos + prfObjectDef.DefinitionLength
      With typPerfObject(nObjIdx)
         .typInstances(0).strName = .strName
      End With
      j = iPos + prfObjectDef.DefinitionLength
      'get instances names and data offsets
      For i = 0 To nInsts
         CopyMemory prfInstanceDef, b(j), LenB(prfInstanceDef)
         'get name of instance while we're here
         k = prfInstanceDef.NameLength - 2
         If k > 0 Then
            strTemp = String(k \ 2, 0)
            CopyMemory ByVal StrPtr(strTemp), _
		 b(j + prfInstanceDef.NameOffset), k
            typPerfObject(nObjIdx).typInstances(i).strName = strTemp
         End If
         'get the offset for each values block
         j = j + prfInstanceDef.ByteLength
         aValuePos(i) = j
         'find the next instancedef block
         ' first four bytes tells us length of value block
         ' which is followed by next instance def (if any)
         CopyMemory k, b(j), 4
         j = j + k
      Next i
   End If

   ' put our iPos to the first CounterDef block
   iPos = iPos + prfObjectDef.HeaderLength
   For i = 0 To nCntrs
      CopyMemory prfCounterDef, b(iPos), LenB(prfCounterDef)
      For j = 0 To nInsts
         With typPerfObject(nObjIdx).typInstances(j).typCounters(i)
            .lngNameIndex = prfCounterDef.CounterNameTitleIndex
            .strName = colNames(CStr(.lngNameIndex))
            .base10 = prfCounterDef.DefaultScale
            .lngValueType = prfCounterDef.CounterType
            'normally we would check the countertype
            ' and decide if it's a string or byte array
            ' or Long, but all of RAS is long, so we'll
            ' only cover longs here
            If (.lngValueType And &H100) = False Then
               If (.lngValueType And &H200) = False Then
                  CopyMemory k, _
                     b(aValuePos(j) + prfCounterDef.CounterOffset), 4
                  .varValue = k
               End If
            End If
         End With
      Next j
      iPos = iPos + prfCounterDef.ByteLength
   Next i

   iPos = iNextObjPos
Next nObjIdx

Well, that's almost it.  You have the data and it's in an array of UDT's called typPerfObject.

But there is just one more thing.  Some of RAS's statistics values are of the PERF_COUNTER_COUNTER type.  This means you have to record the previous value, and the previous PerfTime of the PERF_DATA_BLOCK (prfDataBlock in the above code) then calculate the value to display as follows:

Element Value
X CounterData
Time base PerfFreq from PERF_DATA_BLOCK
Data Size 4 Bytes
Display Suffix /Sec
Calculation (X1-X0)/((Y1-Y0)/TB)

To check to see if any of the values returned in the code above are of PERF_COUNTER_COUNTER type, you can check the counter's lngValueType for logical AND &H410400.  eg.

If typPerfObject(i).typInstances(j).typCounters(k).lngValueType AND &H410400 Then
        ' is a perf_counter_counter type
End If



PERF Declares
 ' perf declarations

   lowpart As Long
   highpart As Long
End Type

        wYear As Integer
        wMonth As Integer
        wDayOfWeek As Integer
        wDay As Integer
        wHour As Integer
        wMinute As Integer
        wSecond As Integer
        wMilliseconds As Integer
End Type

    Signature(7) As Byte      ' Signature: Unicode "PERF"
    LittleEndian As Long
    Version As Long
    Revision As Long
    TotalByteLength As Long    ' Total length of data block
    HeaderLength As Long       ' Length of this structure
    NumObjectTypes As Long     ' Number of types of objects  being reported
    DefaultObject As Long      ' Object Title Index of default object to display
    SysTime As SYSTEMTIME      'Time at the system under measurement
    PerfTime As LARGE_INTEGER  ' Performance counter value at the system under measurement
    PerfFreq As LARGE_INTEGER  ' Performance counter frequency at the system under measurement
    PerfTime100nSec As LARGE_INTEGER  'Performance counter time in 100 nsec units at the system under measurement
    paddingBytes As Long      ' fixes alignment of this structure ??
    SystemNameLength As Long    'Length of the system name
    SystemNameOffset As Long    ' Offset, from beginning of this structure, to name of system being measured
End Type

   TotalByteLength As Long      'offset to the next object
   DefinitionLength As Long     ' offset to data or instancedef
   HeaderLength  As Long        'offset to the first counterdef
   ObjectNameTitleIndex As Long 'Index to name in Title Database
   ObjectNameTitle As Long      'Initially NULL
   ObjectHelpTitleIndex As Long 'Index to Help in Title Database
   ObjectHelpTitle As Long      'Initially NULL, for use by
   DetailLevel As Long          'Object level of detail
   NumCounters As Long          'Number of counters in each counter block
   DefaultCounter As Long       'Default counter to display
   NumInstances As Long         'Number of object instances
   CodePage As Long             '0 if instance strings are in UNICODE
   PerfTime As LARGE_INTEGER    'Sample Time in "Object" units
   PerfFreq As LARGE_INTEGER    'Frequency of "Object" units in counts per second.
End Type

    ByteLength As Long            'Length in bytes of this structure
    CounterNameTitleIndex As Long 'Index of Counter name into Title Database
    CounterNameTitle As Long      'Initially NULL
    CounterHelpTitleIndex As Long 'Index of Counter Help into Title Database
    CounterHelpTitle As Long      'Initially NULL
    DefaultScale As Long          'Power of 10 by which to scale
    DetailLevel As Long           'Counter level of detail
    CounterType As Long           'Type of counter
    CounterSize As Long           'Size of counter in bytes
    CounterOffset As Long         'Offset from the start to counter's value
End Type

    ByteLength As Long         'Length in bytes of this structure including the subsequent name
    ParentObjectTitleIndex As Long 'Title Index to name of "parent"
    ParentObjectInstance As Long 'Index to instance of parent object
    UniqueID As Long
    NameOffset As Long          'Offset from beginning of this struct to the Unicode name
    NameLength As Long         'Length in bytes of name; 0 = none
End Type

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
		 (Destination As Any, Source As Any, ByVal Length As Long)
Registry Declares
' * REGISTRY declarations *

Public Declare Function RegCloseKey _
      Lib "advapi32.dll" _
      (ByVal hKey As Long) As Long

Public Declare Function RegOpenKeyEx _
      Lib "advapi32.dll" Alias "RegOpenKeyExA" _
      (ByVal hKey As Long, _
      ByVal lpSubKey As String, _
      ByVal ulOptions As Long, _
      ByVal samDesired As Long, _
      phkResult As Long) As Long

Public Declare Function RegQueryValueEx _
      Lib "advapi32.dll" Alias "RegQueryValueExA" _
      (ByVal hKey As Long, _
      ByVal lpValueName As String, _
      ByVal lpReserved As Long, _
      lpType As Long, _
      lpData As Any, _
      lpcbData As Long) As Long

Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const HKEY_PERFORMANCE_DATA = &H80000004
Public Const KEY_READ = &H20019
Public Const ERROR_MORE_DATA = 234





See Also: Contents, Introduction, RasStats Overview, RasStats95, RasStats2000