Как сделать запрос Win32_Process быстрее? - PullRequest
0 голосов
/ 12 декабря 2018

У меня есть следующая функция для извлечения информации о процессе в DataTable:

public DataTable getProcesses()
        {
            DataTable dt = new DataTable();

            dt.Columns.Add("ID");
            dt.Columns.Add("Name");
            dt.Columns.Add("Path");
            dt.Columns.Add("User");
            dt.Columns.Add("Priority");

            string pid = "-";
            string name = "-";
            string path = "-";
            string priort = "-";
            string user = "-";

            string query = "Select * From Win32_Process";
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
            ManagementObjectCollection processList = searcher.Get();

            foreach (ManagementObject proc in processList)
            {
                pid = proc["ProcessID"].ToString();
                name = proc["Name"].ToString();

                if (proc["ExecutablePath"] != null)
                {
                    path = proc["ExecutablePath"].ToString();
                    priort = proc["Priority"].ToString();
                }
                string[] argList = new string[2];
                int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));
                if (returnVal == 0)
                {
                    // return DOMAIN\user
                    user = argList[1] + "\\" + argList[0];
                }

                dt.Rows.Add(pid, name, path, user, priort);
            }

            return dt;
        }

В конечном итоге это работает и дает мне желаемый результат, но для его выполнения требуется 20-30 секунд.

Мне было интересно, есть ли способ оптимизировать функцию или конкретный запрос, который, скорее всего, является источником "задержки".

edit

После выполнения некоторых действий, предложенных в комментариях, оно сократилось в среднем до 15-20 секунд, но это все еще слишком долго.Максимум, 4-5 секунд будет терпимо.Я до сих пор не изменил запрос, могу ли я что-нибудь сделать, чтобы он был быстрее?

edit 2

После применения некоторых изменений, предложенных @NicoRiffи, продолжая получать то же время выполнения, я провел некоторую отладку, чтобы увидеть, что эффективно занимает так много времени.Оказывается, это одна конкретная строка: int returnVal = Convert.ToInt32(proc.InvokeMethod("GetOwner", argList));, которая даст мне пользователя, который «владеет» каждым процессом.

Эта строка занимает около 60 мс, в то время как все остальные занимают 1-2 мс.Более 200 итераций (это количество процессов, которые у меня есть, я могу только представить себе, сколько времени потребуется для большого списка), это занимает около 12-13 секунд (только для этой одной строки), давая 15-20итого.

Теперь, когда я "выделил" проблему, как я могу оптимизировать эту функцию?

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Технически, это не отвечает на ваш вопрос, потому что он не использует запрос Win32_Process.Тем не менее, он дает тот же результат в течение доли времени (~ 1,5 против ~ 25 секунд), используя Powershell.

Вам необходимо запустить его в режиме X64 для опроса 64-битных процессов и требует повышенных прав дляPowershell возвращает имя пользователя.

Обратите внимание, что я впервые вызываю скрипты Powershell из C # и, возможно, есть более эффективные способы сделать это.Он также нуждается в некотором отслеживании ошибок, чтобы сделать его более устойчивым (не достойным производства как есть).

using System.Management.Automation;
using System.Management.Automation.Runspaces;

private DataTable getProcesses() 
{
    // Create the datatable and columns
    DataTable dt = new DataTable();
    dt.Columns.Add("ID");
    dt.Columns.Add("Name");
    dt.Columns.Add("Path");
    dt.Columns.Add("User");
    dt.Columns.Add("Priority");
    dt.Columns.Add("BasePriority"); 

    string script = $"get-process -IncludeUserName | select id, processname, path, username, priorityclass";

    List<string[]> psOutput = new List<string[]>();

    // Invoke Powershell cmdlet and get output
    using (PowerShell ps = PowerShell.Create())
    {
        ps.AddScript(script);
        var output = ps.Invoke();
        if(ps.Streams.Error.Count > 0)
        {
            throw new Exception($"Error running script:{Environment.NewLine}{string.Join(Environment.NewLine, ps.Streams.Error.Select(e => e.ToString()))}");
        }

        // clean and split the output
        psOutput.AddRange(output.Select(i => i.ToString().Replace("; ", ";").TrimStart("@{".ToCharArray()).TrimEnd('}').Split(';')));
    }

    // populate the DataTable
    psOutput
        .AsParallel()           // this does not really help when not calling Process.GetProcessById
        .Select(p => p.Select(f => f.Split("=")).ToDictionary(k => k.FirstOrDefault(), v => v.LastOrDefault()))
        .Select(kv => new object[] {    // "flatten" the dictionaries into object[]'s that will become the datarows
                        kv["Id"]
                        , kv["ProcessName"]
                        , kv["Path"]
                        , kv["UserName"]
                        , kv["PriorityClass"]
                        , Process.GetProcessById(int.Parse(kv["Id"])).BasePriority  // if you need the numerical base priority - takes quite a bit longer though (Not sure how to get this via Powershell)
                    }
            )
        .ToList()
        .ForEach(r => dt.Rows.Add(r));  // add each object[] to the datatable

    // return the datatable 
    return dt;
}
0 голосов
/ 13 декабря 2018

Лично я всегда хотел избавиться от использования DataTable.Вы можете работать с List<> или коллекциями, которые решат многие потенциальные проблемы, которые у вас будут.

Сказав это, вы можете проверить библиотеку ORMi , чтобы получить информацию WMI на List<>.Вы можете добиться того, что вы пытаетесь, следующим образом:

        WMIHelper helper = new WMIHelper("root\\CimV2");

        string pid = "-";
        string name = "-";
        string path = "-";
        string priort = "-";
        string user = "-";

        var processes = helper.Query("Select * From Win32_Process").ToList();

        foreach (var p in processes)
        {
            pid = p.ProcessID;
            name = p.Name;
            path = p.ExecutablePath ?? String.Empty;
            priort = p.Priority ?? String.Empty;
        }

Приведенный выше код работает с dynamic объектами и не требует написания какой-либо модели.Просто этот маленький код.Если вам нужно использовать методы, вы можете объявить свою модель и работать со строго типизированными объектами:

1) Определить вашу модель:

[WMIClass(Name = "Win32_Process", Namespace = "root\\CimV2")]
public class Process
{
    public int Handle { get; set; }
    public string Name { get; set; }
    public int ProcessID { get; set; }
    public string ExecutablePath { get; set; }
    public int Priority { get; set; }

    /// <summary>
    /// Date the process begins executing.
    /// </summary>
    public DateTime CreationDate { get; set; }

    public dynamic GetOwnerSid()
    {
        return WMIMethod.ExecuteMethod(this);
    }

    public ProcessOwner GetOwner()
    {
        return WMIMethod.ExecuteMethod<ProcessOwner>(this);
    }

    public int AttachDebugger()
    {
        return WMIMethod.ExecuteMethod<int>(this);
    }
}

public class ProcessOwner
{
    public string Domain { get; set; }
    public int ReturnValue { get; set; }
    public string User { get; set; }
}

2) Запрос WMI

        List<Process> processes = helper.Query<Process>().ToList();

        foreach (Process p in processes)
        {
            pid = p.ProcessID;
            name = p.Name;
            path = p.ExecutablePath ?? String.Empty;
            priort = p.Priority ?? String.Empty;

            dynamic d = p.GetOwnerSid();
            ProcessOwner po = p.GetOwner();
        }

Даже если для этой задачи второй способ может показаться слишком трудоемким, вы получите гораздо более понятный и понятный код.

ПРИМЕЧАНИЕ. Я пробовал ваш код с ORMi, и яполучение результатов за 1-2 секунды.Как говорили другие, это может зависеть от вашей среды.

ПРИМЕЧАНИЕ 2. Всегда используйте только те свойства, которые вам нужны в операторе SELECT.Это очень дорого для WMI SELECT *.ВСЕГДА указывайте свойства.В вашем случае это будет:

Select ProcessID, Name, ExecutablePath, Priority From Win32_Process

(ORMi решит это для вас также, так как всегда запрашивает свойства, заданные в модели.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...