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.
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.
RAS has two distinct performance objects:
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.
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.
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
Else
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.
' 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:
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.
Dim b() As Byte
Dim lngIncr As Long
'Dim lpcbData As Long, rtn as long
Do
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
rtn = RegCloseKey(HKEY_PERFORMANCE_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
'***** 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 prfCounterDef As PERF_COUNTER_DEFINITION
Dim prfInstanceDef As PERF_INSTANCE_DEFINITION
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
Else
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
NextObject:
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 |
| Y | PerfTime from PERF_DATA_BLOCK |
| Time base | PerfFreq from PERF_DATA_BLOCK |
| Data Size | 4 Bytes |
| Display Suffix | /Sec |
| Calculation | (X1-X0)/((Y1-Y0)/TB) |
If
typPerfObject(i).typInstances(j).typCounters(k).lngValueType AND &H410400
Then
' is a perf_counter_counter type
End If
' perf declarations
Public Type LARGE_INTEGER
lowpart As Long
highpart As Long
End Type
Public Type SYSTEMTIME
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
Public Type PERF_DATA_BLOCK
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
Public Type PERF_OBJECT_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
Public Type PERF_COUNTER_DEFINITION
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
Public Type PERF_INSTANCE_DEFINITION
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 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