DEV Community

Bruce Axtens
Bruce Axtens

Posted on

Dealing with an overabundance of chromedriver.exe

The code below is in the context of having memory fill up with lots of chromedriver.exe. This was due to programmer (yeah, me) error.

However, fixing the error would necessitate killing off some stuff we wanted to leave running until it was finished. So I opted for the band-aid approach until everything was done and I had time to fix stuff.

So the idea is this: Get all the chromedriver.exe processes. With each of the processes, see if any have descendants. Those without descendants, that is, chromedriver.exes that haven't started any chrome.exes, can be terminated.

Whether this has applicability to other tasks I don't know. I will eventually get the code up onto Github. Until then, it here. (And now there too.)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management;

namespace ChromeDriversWithNoChromes
{
    class Program
    {
        static readonly Dictionary<string, object> Settings = new Dictionary<string, object>();
        static readonly List<string> Args = new List<string>();

        static readonly List<Process> FoundProcesses = new List<Process>();
Enter fullscreen mode Exit fullscreen mode

There are three data structures there. One is to hold the processes we find. The others are for command-line argument handling. Slashed items go into Settings and everything else into Args.

        static void Main(string[] args)
        {
            ParseCommandLineToSettingsAndArgs(args);
            var count = StoreProcesses(new string[] { "chromedriver" });
            if (count > 0)
            {
                Console.WriteLine($"{count} chromedrivers found.");

                foreach (var proc in FoundProcesses)
                {
                    var descendants = GetChildren(proc);
                    string tag = string.Empty;

                    if (descendants.Count() == 0)
                    {
                        if (Settings.ContainsKey("/K"))
                        {
                            try
                            {
                                proc.Kill();
                                tag = "TERMINATED.";
                            }
                            catch
                            {
                                tag = "COULD NOT TERMINATE";
                            }
                        }
                        else
                        {
                            tag = "COULD BE TERMINATED";
                        }
                    }
                    else
                    {
                        tag = "IGNORED";
                    }

                    Console.WriteLine($"{tag} {proc.ProcessName}[{proc.Id}]({GetCommandLine(proc)}), {descendants.Count()} descendant/s");

                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

There's the Main which is fairly self explanatory. The try/catch is because a process could cease to exist between running the program and processing actually getting as far as the Kill.

        static void ParseCommandLineToSettingsAndArgs(string[] args)
        {
            foreach (string arg in args)
            {
                if (arg.StartsWith("/"))
                {
                    var colonPos = arg.IndexOf(":");
                    if (colonPos > -1)
                    {
                        Settings[arg.Substring(0, colonPos)] = arg.Substring(colonPos + 1);
                    }
                    else
                    {
                        Settings[arg] = true;
                    }
                }
                else
                {
                    Args.Add(arg);
                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

That's my standard command-line parser.

        static int StoreProcesses(string[] processNames)
        {
            int count = 0;
            foreach (string name in processNames)
            {
                var processes = Process.GetProcessesByName(name);
                if (processes.Count() > 0)
                {
                    FoundProcesses.AddRange(processes);
                    count += processes.Count();
                }
            }
            return count;
        }
Enter fullscreen mode Exit fullscreen mode

That gets all the processes that match the names in the processNames list. Maybe having the list of processes as a global is a bad idea. Someone may fork the project (when it gets githubbed) and demonstrate a better way.

        static Process[] GetChildren(Process process)
        {
            try
            {
                using (var query = new ManagementObjectSearcher(
                    $"SELECT * FROM Win32_Process WHERE ParentProcessId={process.Id}"))
                {
                    return query
                        .Get()
                        .OfType<ManagementObject>()
                        .Select(p => Process.GetProcessById((int)(uint)p["ProcessId"]))
                        .ToArray();
                }
            }
            catch (ArgumentException ae)
            {
                Console.WriteLine(ae.Message);
                return null;
            }
        }
Enter fullscreen mode Exit fullscreen mode

Apparently there's a P/Invoke way of doing that that is faster than the System.Management approach. Essentially one gets all the processes that have a ParentProcessId that's the same as the Id of the process parameter.

        static string GetCommandLine(Process proc)
        {
            string commandLine = string.Empty;
            try
            {
                ManagementObjectSearcher commandLineSearcher = new ManagementObjectSearcher(
                    "SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + proc.Id);

                foreach (ManagementObject commandLineObject in commandLineSearcher.Get())
                {
                    commandLine += (String)commandLineObject["CommandLine"];
                }
            }
            catch
            {
                commandLine = $"Command-line data not available";
            }

            return commandLine;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Another candidate for the P/Invoke approach (if one exists) but again, speed of processing wasn't the primary goal: speed of writing the code was an this routine is from another project written some years ago.

I hope that helps someone. It was (almost) fun to write. Might have been more fun if it weren't for the hundred or more chromedriver.exes filling up available RAM and processor.

Top comments (1)

Collapse
 
bugmagnet profile image
Bruce Axtens

Project now on Github at ChromeDriversWithNoChromes