Avatar of peer754
peer754
 asked on

WMI API causes memory leak

Hi,
I'm developing a small in-house application to monitor the performance such as CPU usage, available memory, disk spaceour etc. I start with a small test settig up the CPU usage monitoring.
I am reading the CPU% once per second using WMI API function and then produce an average value each minute.
The problem with this is I get some memory leakage while looking at the process in the Taskmanager.
I've been googling this but all I found was a hotfix concerning leak in Win Server 2008 while I'm on XP/Win Server 2003 machines.
So, there's anyone here that could provide a working solution based on my code input, I'd be more than pleased!

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Management;
using System.Configuration;
using System.Threading;

namespace ServerMonitoringConsole
{
    class Program
    {
        static string clientName = "myName";
        static string clientPwd = "myPwd";
        static string domain = "ntlmdomain:myDomain";
        static List<int> cpuLoads = new List<int>();
        
        static string errorStr = "";

        static void Main(string[] args)
        {
            int load = 0;
            int avg = 0;
            long mem = 0;
            int cpuTimePeriod = 60;
 
            string[] stringServers = { "serv1", "serv2" };

            ConnectionOptions co = new ConnectionOptions();
            co.Username = clientName;
            co.Password = clientPwd;
            co.Authority = domain;
            List<ManagementScope> msList = new List<ManagementScope>();
            msList.Add(new ManagementScope(@"\\" + stringServers[0] + @"\root\cimv2", co));
            msList.Add(new ManagementScope(@"\\" + stringServers[1] + @"\root\cimv2", co));
            ObjectQuery sq = new ObjectQuery("SELECT * FROM Win32_Processor");
            List<ManagementObjectSearcher> mosList = new List<ManagementObjectSearcher>();

            foreach (ManagementScope ms in msList)
            {
                ms.Connect();
                mosList.Add(new ManagementObjectSearcher(ms, sq));
            }
            while (true)
            {
                load = 0;
                int counter = 0;
                while (cpuLoads.Count <= cpuTimePeriod)
                {
                    Thread.Sleep(1000);
                    foreach (string server in stringServers)
                    {
                        GetCPUPerformance(server, ref load, mosList[counter]);
                        cpuLoads.Add(load);
                        counter++;
                    }
                }
                avg = (int)cpuLoads.Sum() / cpuTimePeriod;
                cpuLoads.Clear();

            }
        }

        static void GetCPUPerformance(string computerName, ref int cpuLoad, ManagementObjectSearcher mos)
        {
            using (ManagementObjectCollection moObjs = mos.Get())
            {
                foreach (ManagementObject mo in moObjs)
                {
                    try
                    {
                        cpuLoad = Convert.ToInt32(mo["LoadPercentage"]);
                    }
                    finally
                    {
                        mo.Dispose();
                    }
                }
            }
        }
    }
}

Open in new window

Windows XP.NET ProgrammingC#

Avatar of undefined
Last Comment
peer754

8/22/2022 - Mon
SOLUTION
Ron Malmstead

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
peer754

ASKER
Thanks I have already tried that with no visual diffence. There are also several threads saying that you should not call this function manually since you then can mess up the OS thread scheduling/handeling schemas.
SOLUTION
Ron Malmstead

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
peer754

ASKER
Once again, I did have GC.Collect() at the end on all methods but with no effect at all, i.e. still memory leaking on every call (one per second).

Ok, but I don't see how ensuring a synchronous thread creation should prevent memory leak?

Interesting to read about performance but this is not the main issue here, however I did try to move out the creation of the objects before entering the main loop which would be something like scenario #3

The connections are not closed since I reuse them according to your link (there's only 2 connections created in this example, one for each machine to monitor)
Ron Malmstead

How about this hotfix for 2003?

http://support.microsoft.com/kb/933230
I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
peer754

ASKER
My Console app is running on a XP machine Querying Win 2003 and Win XP machines. No noticable difference in the memory leak between the machines.
This Hotfix says "a memory leak of up to 20 bytes may occur for each WQL query to the decoupled WMI provider" while I have 20 KB / Query. Since I need to query several of our intranet machines (~ 40) every second, the machine hosting the Console app will quickly stop working normally so ...

Is there only one expert here in this area? :/
Ron Malmstead

I posted a request for assistance to the moderator to see if we can get some additional experts to look at this.
peer754

ASKER
Thanks!

I've also been trying to find a work-around to prevent from querying too frequently as now now, every second.
I don't see how though regarding the CPU usage if you're not fine with taking a snapshot every minute or so.
If there was a way to get history CPU usage in one query / minute I'm done. Since I then could run my app as a scheduled task and hence, killing the thread and free the memory through OS normal scheduling.
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
Ron Malmstead

It's a strange problem no doubt.

I'm wondering if the same query/program would cause the memory leak when ran directly on the machine versus remotely.
peer754

ASKER
Exact same behaviour on local machine (XP SP3).
sarabande

GetCPUPerformance(server, ref load, mosList[counter]);

Open in new window


the above code accesses 'mosList' at index 'counter' though counter obviously was independent of the size of the list.

i would have assumed that an exception was thrown when counter is equal or greater to size but in case it grows the list instead it could be a reason for the growing memory consumption.

in my opinion task manager is not suitable to detect leakage cause both the heap manager and the CLR garbage collector would reserve some of the freed memory for efficience and optimization reasons what means that task manager's report is not accurate.

Sara
All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
peer754

ASKER
counter is not an issue here I'm afraid, it's only used to increment the objects corresponding to each server I want to monitor.

I have simplyfied the code only to one machine and one query (the CPU usage) + I have also tried querying the local machine to exclude the RPC from the error search.
Still, there's a leak according to, yes,  the Task manager. I know looking at the Task manager won't give very accurate result in precise memory leak numbers but it still gives a hint that something's wrong.

In my last test on my local machine, I ran the code for about 15 minutes and the thread's memory increased to the double compare to when it started.

Here's my new simplyfied code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Management;
using System.Configuration;
using System.Threading;

namespace ServerMonitoringConsole
{
    class Program
    {
        static List<int> cpuLoads = new List<int>();

        static void Main(string[] args)
        {
            int avg = 0;
            int cpuReadPeriod = 5000; //Read CPU load every 5 sec
            int cpuTimePeriod = 60000 / cpuReadPeriod;

            while (true)
            {
                while (cpuLoads.Count <= cpuTimePeriod)
                {
                    Thread.Sleep(cpuReadPeriod);
                    using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2",
                                                                           "SELECT * FROM Win32_Processor"))
                    {
                        foreach (ManagementObject queryObj in searcher.Get())
                        {
                            try
                            {
                                Console.WriteLine("LoadPercentage: {0}", queryObj["LoadPercentage"]);
                                cpuLoads.Add(Convert.ToInt32(queryObj["LoadPercentage"]));
                            }
                            finally
                            {
                                queryObj.Dispose();
                            }
                        }
                    }
                        
                };
                avg = (int)cpuLoads.Sum() / cpuTimePeriod;
                cpuLoads.Clear();
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Win32_Processor instance");
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Average CPU LOAD = " + avg.ToString() + "%.");

                GC.Collect();
            }
        }
    }
}

Open in new window

SOLUTION
peer754

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
ASKER CERTIFIED SOLUTION
Christopher Kile

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
Ron Malmstead

I converted your latest code to vb and it seems to work without any issue.
I tested on Windows 7 and 2003
peer754

ASKER
As suggested by one of the experts, removing some of the objects might have contributed to the final solution
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.