Microsoft KB Archive/307515

From BetaArchive Wiki
Knowledge Base


FIX: Custom performance counters do not publish performance data to WMI

Article ID: 307515

Article Last Modified on 10/29/2004



APPLIES TO

  • Microsoft .NET Framework 1.0
  • Microsoft Windows Management Instrumentation in . NET



This article was previously published under Q307515

SYMPTOMS

Custom performance counters that are exposed when you use the PerformanceCounter class in the System.Diagnostics namespace do not publish performance data to Windows Management Instrumentation (WMI) as expected.

Additionally, you are unable to view the .NET CLR counter and .NET Networking counter through WMI.

CAUSE

This behavior occurs because the following circumstances prevent the WMI schemas from being generated correctly:

  • The export value is handled on the Linkage registry key. Applications that supply performance counters create a Linkage key in the registry similar to the following:

    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MyApplication\Linkage

    The export value should be present on this key. To support the .NET side by side feature and work around a bug in Microsoft Windows NT 4.0 Performance Monitor (PerfMon), the .NET PerformanceCounters component relies on this key value to load a versioned DLL file from a non-versioned shim that lives on the System32 directory. However, this export value is type REG_SZ, which is not recognized by WMI (WMI expects a value of type REG_MUTI_SZ).

    -and-
  • Performance date is being collected for services that use the same performance counter library. To facilitate performance counter instrumentation, a single performance library for all the new performance object types is provided. Because PerfMon supports only eight object types under any given registry key, a new key has to be created for each new object type. In Visual Studio .NET Beta1, there is a single DLL with hundreds of Open, Collect, and Close entry points -- one set for each object type. To support the .NET side by side feature and to address the size issue, Visual Studio .NET Beta2 and later provide a single DLL with only three entry points. The DLL's operation is very tightly coupled with the PerfMon collect sequence, which differs dramatically from the sequence followed by WMI.

The sequence followed by PerfMon

  1. Loads the perf DLL (each DLL being specific to a given counter).Calls the Open function.For each registry key, calls the Global function.Calls the Close function.
  2. Calls the Open function.For each registry key, calls the Global function.Calls the Close function.
  3. For each registry key, calls the Global function.Calls the Close function.
  4. Calls the Close function.

The sequence followed by WMI (when generating the schema; only for new registry keys)

  1. Loads the perf DLL.Calls Open.Calls Global.Calls the Costly function.Calls Close.
  2. Calls Open.Calls Global.Calls the Costly function.Calls Close.
  3. Calls Global.Calls the Costly function.Calls Close.
  4. Calls the Costly function.Calls Close.
  5. Calls Close.

When a global query is executed, it is easy to return the right object type for each Global collect call (for PerfMon or anything that relies on the RegQueryValueEx function or PDH), because the performance DLL knows exactly how many object types that the .NET PerformanceCounters component provides and how many times the collect call will be called. Therefore, the objects are returned in sequence.

RESOLUTION

To resolve this problem, install the hotfix in the following Microsoft Knowledge Base article, and then run the C# code that follows:

824336 FIX: Custom performance counters do not publish performance data to WMI


C# code

Note You can either incorporate the C# code into released projects or run the C# code as a stand-alone application. For executing the C# code or utility, you must pass in the names of all categories that are multi-instance.

using System;
using Microsoft.Win32;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Globalization;
using System.ServiceProcess;
using System.ComponentModel;
using System.Threading;

namespace FixPerfCounters {
    
    public class FixCounters {

        public static void Main(string[] args) {

            if (args.Length > 0 && (args[0] == "/?" || args[0] == "-?")) {
                Console.WriteLine("Usage: " + Environment.GetCommandLineArgs()[0] + " [<category> <category2> ...]");
                Console.WriteLine("\tWhere <category>, <category2>, etc., are categories which will be used with multi instance counters.");
                return;
            }

            Console.WriteLine("Fixing up old custom counters...");
            FixPerfCounters();

            Console.WriteLine("Setting new counters as MultiInstance...");
            SetMultiInstance(args);

            Console.WriteLine("Restarting Winmgmt...");
            RestartWingmgmt();
            
            Console.WriteLine("Resyncing WMI cache...");
            ResyncWMI();

            Console.WriteLine("Done.");
            
            
        }

        internal static void FixPerfCounters() {
            RegistryKey servicesKey = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services");

            string[]  servicesSubKeyNames = servicesKey.GetSubKeyNames();
            foreach (string subKeyName in servicesSubKeyNames) {
                RegistryKey perfObjectKey = servicesKey.OpenSubKey(subKeyName, true);

                RegistryKey performanceKey = perfObjectKey.OpenSubKey("Performance", true);
                if (performanceKey != null) {
                    if ( ((string) performanceKey.GetValue("library")) == "netfxperf.dll") {
                        RegistryKey linkageKey = perfObjectKey.CreateSubKey("Linkage");
                        int index = perfObjectKey.Name.LastIndexOf("\\");
                        linkageKey.SetValue("Export", new string[] { perfObjectKey.Name.Substring(index + 1)} );
                        linkageKey.Close();

                        performanceKey.SetValue("WbemAdapStatus", 0);
                    }
                    performanceKey.Close();
                }

                perfObjectKey.Close();
            }

            servicesKey.Close();
        }
            
        internal static void SetMultiInstance(string[] args) {
            RegistryKey categoryKey;
            foreach (string category in args) {
                categoryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\" + category + "\\Performance", true);
                if (categoryKey != null) {
                    Console.WriteLine(category);
                    categoryKey.SetValue("IsMultiInstance", 1);
                    categoryKey.Close();
                }
                else
                    Console.WriteLine("Category '" + category + "' does not exist");
            }

            categoryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\.NET CLR Data\\Performance", true);
            if (categoryKey != null) {
                categoryKey.SetValue("IsMultiInstance", 1);
                categoryKey.Close();
            }
            
            categoryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\.NET CLR Networking\\Performance", true);
            if (categoryKey != null) {
                categoryKey.SetValue("IsMultiInstance", 1);
                categoryKey.Close();
            }
        }
        
        internal  static void RestartWingmgmt() {
            ServiceController winmgmt = new ServiceController("winmgmt");

            if (winmgmt.Status != ServiceControllerStatus.Stopped && winmgmt.Status != ServiceControllerStatus.StopPending )
                winmgmt.Stop();
            winmgmt.WaitForStatus(ServiceControllerStatus.Stopped);
            winmgmt.Start();
            winmgmt.WaitForStatus(ServiceControllerStatus.Running);
            // wait to ensure that wmiadap is not running
            Thread.Sleep(5000);
        }
        
        internal  static int GetWinmgmtPid() {
            IntPtr serviceManagerHandle = OpenSCManager(null, null, SC_MANAGER_ENUMERATE_SERVICE);

            IntPtr serviceHandle = OpenService(serviceManagerHandle, "winmgmt", SERVICE_QUERY_STATUS);
            if (serviceHandle == (IntPtr)0) {
                throw new Win32Exception();
            }

            SERVICE_STATUS_PROCESS serviceStatus = new SERVICE_STATUS_PROCESS();
            byte[] buffer = new byte[1000];
            int bytesNeeded;
            GCHandle bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            try {
                bool success = QueryServiceStatusEx(serviceHandle,  SC_STATUS_PROCESS_INFO, buffer, 1000, out bytesNeeded);
                if (!success)
                    throw new Win32Exception();
                IntPtr buffIntPtr = bufferHandle.AddrOfPinnedObject();
                Marshal.PtrToStructure( buffIntPtr, serviceStatus);
            }
            finally { 
                bufferHandle.Free();
            }

            CloseServiceHandle(serviceHandle);
            CloseServiceHandle(serviceManagerHandle);
            
            return serviceStatus.dwProcessId;
        }


        internal static void ResyncWMI() {

            OperatingSystem os = Environment.OSVersion;
            if (os.Version < new Version(5, 1)) {
                // Windows 2000 case
                string pid = GetWinmgmtPid().ToString(CultureInfo.InvariantCulture);
                Process p = Process.Start("winmgmt.exe", "/resyncperf " + pid);
                p.WaitForExit();
            } 
            else {
                // Windows XP case
                ProcessIdleTasks();

                Thread.Sleep(1000);

                Process p = Process.Start("wmiadap.exe", "/F");
                p.WaitForExit();
            }
                
        }

        internal  const int SC_MANAGER_ENUMERATE_SERVICE = 0x0004;
        internal const int SERVICE_QUERY_STATUS = 0x0004;
        internal const int SC_STATUS_PROCESS_INFO = 0;
        
        [DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, SetLastError=true)]
        internal extern static IntPtr OpenSCManager(string machineName, string databaseName, int access);        
        [DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, SetLastError=true)]
        internal static extern bool QueryServiceStatusEx (IntPtr serviceHandle, int InfoLevel, byte[] lpBuffer, int cbBufSize, out int pcbBytesNeeded);
        [DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, SetLastError=true)]
        internal extern static bool CloseServiceHandle(IntPtr handle);        
        [DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, SetLastError=true)]
        internal extern static IntPtr OpenService(IntPtr databaseHandle, string serviceName, int access);

        [DllImport("advapi32.dll", SetLastError=true)]
        internal extern static int ProcessIdleTasks();

    }

    [StructLayout(LayoutKind.Sequential)]
    internal class SERVICE_STATUS_PROCESS {
        public int dwServiceType = 0;
        public int dwCurrentState = 0;
        public int dwControlsAccepted = 0;
        public int dwWin32ExitCode = 0;
        public int dwServiceSpecificExitCode = 0;
        public int dwCheckPoint = 0;
        public int dwWaitHint = 0;
        public int dwProcessId = 0;
        public int dwServiceFlags = 0;
    }
}

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article. This bug was corrected in Microsoft Visual Studio .NET (2003).


Additional query words: kbreadme

Keywords: kbbug kbfix kbreadme KB307515