BUG: NetStatisticsGet2 Not Implemented

ID: Q178885

The information in this article applies to:

SYMPTOMS

A Visual C++ compiler and linker fails to build an application using the NetStatisticsGet2 function. The compiler reports "function not defined." If the function prototype is explicitly declared in the application, the linker reports an "unresolved external" for this function.

CAUSE

The NetStatisticsGet2 function is not included in the lmstats.h Win32 SDK header file, and this function is not defined in the NETAPI32 library.

RESOLUTION

This function provides various statistics for the Windows NT Workstation or Server components. There is no alternative to get Windows NT Workstation information, but some statistics for server information can be found within the performance registry of Windows NT. An application can get the counter data for the server object.

STATUS

Microsoft has confirmed this to be a bug in the Microsoft products listed at the beginning of this article. We are researching this bug and will post new information here in the Microsoft Knowledge Base as it becomes available.

MORE INFORMATION

You can include the following function definition in an application for Windows NT to gather server statistics for a Windows NT Server or Windows NT Workstation. You can use this function to determine how busy Windows NT is performing as a network server.

The function defined here is named GetServerStats. There are some other supporting subroutines that need to be included with any code to implement the GetServerStats function.

Sample Code

   ================== netperf.h ================================
   #define TYPE_MASK         0x00000300
   #define TYPE_NOT_SUPPORTED   -1
   #define TYPE_DWORD         0
   #define TYPE_LARGE         1
   #define MAX_INDEX         3000

   typedef struct
   {
      DWORDLONG dwlPerf_Time;
      DWORDLONG dwlPerf_Frequency;
      DWORDLONG dwlBytes_Total;
      DWORDLONG dwlBytes_Received;
      DWORDLONG dwlBytes_Transmitted;
      DWORD dwSessions_Timed_Out;
      DWORD dwSessions_Errored_Out;
      DWORD dwSessions_Logged_Off;
      DWORD dwSessions_Forced_Off;
      DWORD dwErrors_Logon;
      DWORD dwErrors_Access_Permissions;
      DWORD dwErrors_Granted_Access;
      DWORD dwErrors_System;
      DWORD dwBlocking_Requests_Rejected;
      DWORD dwWork_Item_Shortages;
      DWORD dwFiles_Opened_Total;
      DWORD dwFiles_Open;
      DWORD dwServer_Sessions;
      DWORD dwContext_Blocks_Queued;
      DWORD dwLogon_Total;
   }SERVER_STATS, *PSERVER_STATS;

   typedef struct
   {
      TCHAR *szName;
      int  nType;

      union
      {
      DWORD  dwCounter;
      DWORDLONG dwlCounter;
      };
   }COUNTER;

   // Function to return server statistics.
   LONG GetServerStats(TCHAR *szMachine, PSERVER_STATS pServerStats);

   LONG GetCounterNames(TCHAR *szMachine, LPVOID *lplpCounterNames);
   void GetCounterIndices(DWORD Index[], LPVOID lpCounterNames,
                       COUNTER Counter[], DWORD dwNumCounters);
   LONG GetObjectData(TCHAR *szMachine, int nIndex, LPVOID *lplpObj);
   void InitServerStats(PSERVER_STATS pServerStats, COUNTER Counter[]);
   LONG GetCounters(LPVOID lpObj, COUNTER Counter[],
                 DWORD dwNumCounters, DWORD Index[]);

   ================== netperf.c ================================
   #include "netperf.h"

   LONG GetServerStats(TCHAR *szMachine, PSERVER_STATS pServerStats)
   {
      LPVOID lpServerObj, lpCounterNames;
      DWORD dwNumCounters;
      LONG lErr;
      DWORD Index[MAX_INDEX + 1];

      COUNTER Counter[] = {{_T("Server"), -1, 0},
                           {_T("Bytes Total/sec"), -1, 0},
                           {_T("Bytes Received/sec"), -1, 0},
                           {_T("Bytes Transmitted/sec"), -1, 0},
                           {_T("Sessions Timed Out"), -1, 0},
                           {_T("Sessions Errored Out"), -1, 0},
                           {_T("Sessions Logged Off"), -1, 0},
                           {_T("Sessions Forced Off"), -1, 0},
                           {_T("Errors Logon"), -1, 0},
                           {_T("Errors Access Permissions"), -1, 0},
                           {_T("Errors Granted Access"), -1, 0},
                           {_T("Errors System"), -1, 0},
                           {_T("Blocking Requests Rejected"), -1, 0},
                           {_T("Work Item Shortages"), -1, 0},
                           {_T("Files Opened Total"), -1, 0},
                           {_T("Files Open"), -1, 0},
                           {_T("Server Sessions"), -1, 0},
                           {_T("Context Blocks Queued/sec"), -1, 0},
                           {_T("Logon Total"), -1, 0}};

      // Get the list of counter names and indices.
      lErr = GetCounterNames(szMachine, &lpCounterNames);

      if(lErr != ERROR_SUCCESS)
      {
         return lErr;
      }

      dwNumCounters = sizeof(Counter) / sizeof(COUNTER);

      // Retrieve and save the indices for counters of interest.
      GetCounterIndices(Index, lpCounterNames, Counter, dwNumCounters);

      // Get the performance data for the "Server" object.
      lErr = GetObjectData(szMachine, Index[0], &lpServerObj);

      if(lErr != ERROR_SUCCESS)
      {
         GlobalFreePtr(lpCounterNames);
         return lErr;
      }

      // Retrieve selected counter data from the "Server" object.
      lErr = GetCounters(lpServerObj, Counter, dwNumCounters, Index);

      if(lErr != ERROR_SUCCESS)
      {
         GlobalFreePtr(lpServerObj);
         GlobalFreePtr(lpCounterNames);
         return lErr;
      }

      // Initialize the SERVER_STATS data structure with our results.
      InitServerStats(pServerStats, Counter);

      // Save PerfTime and PerfFreq as well.
      pServerStats->dwlPerf_Time =
         ((PPERF_DATA_BLOCK)lpServerObj)->PerfTime.HighPart << 32 |
         ((PPERF_DATA_BLOCK)lpServerObj)->PerfTime.LowPart;
      pServerStats->dwlPerf_Frequency =
         ((PPERF_DATA_BLOCK)lpServerObj)->PerfFreq.HighPart << 32 |
         ((PPERF_DATA_BLOCK)lpServerObj)->PerfFreq.LowPart;

      GlobalFreePtr(lpServerObj);
      GlobalFreePtr(lpCounterNames);

      return lErr;
   }

   LONG GetCounterNames(TCHAR *szMachine, LPVOID *lplpCounterNames)
   {
      DWORD dwType, dwCount = 10000, dwCountRet;
      LONG lErr;
      HKEY hKey1, hKey2;

      if(szMachine && *szMachine)
      {
         lErr = RegConnectRegistry(szMachine, HKEY_LOCAL_MACHINE, &hKey1);

         if(lErr != ERROR_SUCCESS)
         {
            return lErr;   // Unable to connect
         }
      }
      else
      {
         hKey1 = HKEY_LOCAL_MACHINE;
      }

      lErr = RegOpenKeyEx(hKey1,
        (LPCTSTR)_T(
        "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009"
         ),
         0, KEY_READ, &hKey2);
      RegCloseKey(hKey1);

      if(lErr != ERROR_SUCCESS)
      {
         return lErr; // Not running NT
      }

      *lplpCounterNames = NULL;

      // Keep trying until the buffer is large enough.
      do
      {
         if(*lplpCounterNames) GlobalFreePtr(*lplpCounterNames);

         *lplpCounterNames = (LPVOID *)GlobalAllocPtr(GHND, dwCount);
         dwCountRet = dwCount;

         // Get the counters data.
         lErr = RegQueryValueEx(hKey2, (LPCTSTR)_T("Counters"), NULL,
                &dwType, (LPBYTE)*lplpCounterNames, &dwCountRet);
         dwCount += 1000;
      }while(lErr != ERROR_SUCCESS);

      RegCloseKey(hKey2);

      return ERROR_SUCCESS;
   }

   void GetCounterIndices(DWORD Index[], LPVOID lpCounterNames,
                          COUNTER Counter[], DWORD dwNumCounters)
   {
      DWORD i;
      TCHAR *szCur = (TCHAR *)lpCounterNames;

      // While not at end of list.
      while(*szCur)
      {
         for(i = 0; i < dwNumCounters; i++)
         {
            if(!lstrcmp(szCur, Counter[i].szName))
            {
               TCHAR *szIndex;

               // We have a match, so back up to index.
               szIndex = szCur;
               szIndex -= 2;

               while(*szIndex) szIndex--;

               szIndex++;

               if(i == 0)
               {  // Store "Server" index as Index[0].
                  Index[i] = _ttoi((const TCHAR *)szIndex);
               }
               else
               {  // Otherwise, store index into Counter array.
                  Index[_ttoi((const TCHAR *)szIndex)] = i;
               }

               break;
            }
         }

      // Next string.
      szCur = _tcschr(szCur, 0);
      szCur++;
      }
   }

   LONG GetObjectData(TCHAR *szMachine, int nIndex, LPVOID *lplpObj)
   {
      HKEY hKey;
      DWORD dwType, dwCount = 1000, dwCountRet;
      LONG lErr;
      TCHAR szIndex[17];

      if(szMachine && *szMachine)
      {
         // Connect to remote machine.
         lErr = RegConnectRegistry(szMachine, HKEY_PERFORMANCE_DATA,
                                   &hKey);

         if(lErr != ERROR_SUCCESS)
         {
            return lErr;   // Unable to connect.
         }
      }
      else
      {
         hKey = HKEY_PERFORMANCE_DATA;
      }

      *lplpObj = NULL;
      _itot(nIndex, szIndex, 10);

      // Keep trying until the buffer is large enough.
      do
      {
         if(*lplpObj) GlobalFreePtr(*lplpObj);

         *lplpObj = (LPVOID)GlobalAllocPtr(GHND, dwCount);
         dwCountRet = dwCount;

         // Get the server object data.
         lErr = RegQueryValueEx(hKey, szIndex, NULL, &dwType,
                (LPBYTE)*lplpObj, &dwCountRet);
         dwCount += 1000;
      }while(lErr != ERROR_SUCCESS);

      RegCloseKey(HKEY_PERFORMANCE_DATA);

      return ERROR_SUCCESS;
   }

   void InitServerStats(PSERVER_STATS pServerStats, COUNTER Counter[])
   {
      pServerStats->dwlBytes_Total = Counter[1].dwlCounter;
      pServerStats->dwlBytes_Received = Counter[2].dwlCounter;
      pServerStats->dwlBytes_Transmitted = Counter[3].dwlCounter;
      pServerStats->dwSessions_Timed_Out = Counter[4].dwCounter;
      pServerStats->dwSessions_Errored_Out = Counter[5].dwCounter;
      pServerStats->dwSessions_Logged_Off = Counter[6].dwCounter;
      pServerStats->dwSessions_Forced_Off = Counter[7].dwCounter;
      pServerStats->dwErrors_Logon = Counter[8].dwCounter;
      pServerStats->dwErrors_Access_Permissions = Counter[9].dwCounter;
      pServerStats->dwErrors_Granted_Access = Counter[10].dwCounter;
      pServerStats->dwErrors_System = Counter[11].dwCounter;
      pServerStats->dwBlocking_Requests_Rejected = Counter[12].dwCounter;
      pServerStats->dwWork_Item_Shortages = Counter[13].dwCounter;
      pServerStats->dwFiles_Opened_Total = Counter[14].dwCounter;
      pServerStats->dwFiles_Open = Counter[15].dwCounter;
      pServerStats->dwServer_Sessions = Counter[16].dwCounter;
      pServerStats->dwContext_Blocks_Queued = Counter[17].dwCounter;
      pServerStats->dwLogon_Total = Counter[18].dwCounter;
   }

   LONG GetCounters(LPVOID lpObj, COUNTER Counter[],
                 DWORD dwNumCounters, DWORD Index[])
   {
      PPERF_DATA_BLOCK pDataBlock;
      PPERF_OBJECT_TYPE pObject;
      PPERF_COUNTER_DEFINITION pCounterDef;
      DWORD i;

      pDataBlock = (PPERF_DATA_BLOCK)lpObj;

      if(!pDataBlock->LittleEndian)
      {
         return ERROR_NOT_SUPPORTED;   // Big-endian format not handled.
      }

      pObject = (PPERF_OBJECT_TYPE)((BYTE *)pDataBlock +
         pDataBlock->HeaderLength);

      // Multiple objects may be returned.
      while(pObject->ObjectNameTitleIndex != Index[0])
      {
         pObject = (PPERF_OBJECT_TYPE)((BYTE *)pObject +
            pObject->TotalByteLength);
      }

      // Find the first counter definition.
      pCounterDef = (PPERF_COUNTER_DEFINITION)((BYTE *)pObject +
         pObject->HeaderLength);

      for(i = 0; i < pObject->NumCounters; i++)
      {
        DWORD dwIndex;
        // Ignore any data that you do not explicitly seek.
         if(Index[pCounterDef->CounterNameTitleIndex] > 0)
         {
            switch(pCounterDef->CounterType & TYPE_MASK)
            {
               case PERF_SIZE_DWORD:

                  dwIndex = pCounterDef->CounterNameTitleIndex;
                  Counter[Index[dwIndex]].nType = TYPE_DWORD;

                  // Save the offsets first, and update with data later.
                  dwIndex = pCounterDef->CounterNameTitleIndex;
                  Counter[Index[dwIndex]].dwCounter =
                      pCounterDef->CounterOffset;
               break;

               case PERF_SIZE_LARGE:
                  dwIndex = pCounterDef->CounterNameTitleIndex;
                  Counter[Index[dwIndex]].nType = TYPE_LARGE;

                  // Save the offsets first, and update with data later.
                  dwIndex = pCounterDef->CounterNameTitleIndex;
                  Counter[Index[dwIndex]].dwCounter =
                     pCounterDef->CounterOffset;
               break;

               default:
                  dwIndex = pCounterDef->CounterNameTitleIndex;
                  Counter[Index[dwIndex]].nType = TYPE_NOT_SUPPORTED;
               break;
            }
         }

         // Next counter.
         pCounterDef = (PPERF_COUNTER_DEFINITION)((BYTE *)pCounterDef +
            pCounterDef->ByteLength);
      }

      // No instances, so pCounterDef is now pointing
      // at the PERF_COUNTER_BLOCK.
      // Update data.
      for(i = 1; i < dwNumCounters; i++)
      {
         if(Counter[i].dwlCounter != (DWORDLONG)-1)
         {
            switch(Counter[i].nType)
            {
               case TYPE_DWORD:
                  Counter[i].dwCounter = *(DWORD *)((BYTE *)pCounterDef +
                     Counter[i].dwCounter);
               break;
               case TYPE_LARGE:
                  Counter[i].dwlCounter =
                     *(DWORDLONG *)((BYTE *)pCounterDef +
                         Counter[i].dwCounter);
               break;
            }
         }
      }

      return ERROR_SUCCESS;
   }

Additional query words:
Keywords          : kbnetwork kbKernBase kbNTOS400bug kbSDKPlatform kbNetAPI kbCodeSam kbGrpNet 
Issue type        : kbbug

Last Reviewed: September 11, 1998