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