WMI 进程监视使用过多的 CPU! 还有更好的方法吗?

发布于 2024-07-10 01:23:24 字数 187 浏览 12 评论 0原文

我需要观察 Windows 计算机上某些进程何时启动或停止。 我目前正在使用 WMI 系统并每 5 秒查询一次,但这会导致每 5 秒出现一次 CPU 峰值,因为 WMI 是 WMI。 有更好的方法吗? 我可以创建一个正在运行的进程列表,并通过 System.Diagnostics 命名空间将 Exited 事件附加到它们,但没有用于创建的事件处理程序。

I need to watch when certain processes are started or stopped on a Windows machine. I'm currently tapped into the WMI system and querying it every 5 seconds, but this causes a CPU spike every 5 seconds because WMI is WMI. Is there a better way of doing this? I could just make a list of running processes and attach an Exited event to them through the System.Diagnostics Namespace, but there is no Event Handler for creation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

枕花眠 2024-07-17 01:23:27

这并不完全是您在现实世界中的做法,但应该会有所帮助。 这似乎对我的 CPU 没有太大的推动作用。

    static void Main(string[] args)
    {
        // Getting all instances of notepad
        // (this is only done once here so start up some notepad instances first)
        // you may want use GetProcessByPid or GetProcesses and filter them as required
        Process[] processesToWatch = Process.GetProcessesByName("notepad");

        foreach (var process in processesToWatch)
        {
            process.EnableRaisingEvents = true;
            process.Exited +=
                (s, e) => Console.WriteLine("An instance of notepad exited");
        }

        Thread watchThread = new Thread(() =>
            {
                while (true)
                {
                    Process[] processes = Process.GetProcesses();
                    foreach (var process in processes)
                    {
                        Console.WriteLine("{0}:{1}", process.Id, process.ProcessName);
                    }
                    // Don't dedicate a thread to this like I'm doing here
                    // setup a timer or something similiar
                    Thread.Sleep(2000);
                }
            });
        watchThread.IsBackground = true;
        watchThread.Start();

        Console.WriteLine("Polling processes and waiting for notepad process exit events");
        Console.ReadLine();
    }

This is not exactly how you'd do it in the real world but should help. This seems not to drive my CPU much at all.

    static void Main(string[] args)
    {
        // Getting all instances of notepad
        // (this is only done once here so start up some notepad instances first)
        // you may want use GetProcessByPid or GetProcesses and filter them as required
        Process[] processesToWatch = Process.GetProcessesByName("notepad");

        foreach (var process in processesToWatch)
        {
            process.EnableRaisingEvents = true;
            process.Exited +=
                (s, e) => Console.WriteLine("An instance of notepad exited");
        }

        Thread watchThread = new Thread(() =>
            {
                while (true)
                {
                    Process[] processes = Process.GetProcesses();
                    foreach (var process in processes)
                    {
                        Console.WriteLine("{0}:{1}", process.Id, process.ProcessName);
                    }
                    // Don't dedicate a thread to this like I'm doing here
                    // setup a timer or something similiar
                    Thread.Sleep(2000);
                }
            });
        watchThread.IsBackground = true;
        watchThread.Start();

        Console.WriteLine("Polling processes and waiting for notepad process exit events");
        Console.ReadLine();
    }
椒妓 2024-07-17 01:23:27

我在这里的回答提到了 WMI 以外的替代方案:https://stackoverflow.com/a/50315772/3721646
如果设计不当,WMI 查询可能会消耗大量 CPU 性能。 如果使用 Win32_Process 类的内部事件来跟踪进程创建事件,则会严重影响性能。 另一种方法是利用安全审核日志。 您可以使用本地安全策略启用进程跟踪,或者在多台计算机的情况下使用 GPO。 进程跟踪开始后,您可以使用自定义 XML 查询订阅安全事件日志,以监控您感兴趣的某些进程。 进程创建事件 ID 为 4688。`

<QueryList>
 <Query Id="0" Path="Security">
   <Select Path="Security">
       *[EventData[Data[@Name='NewProcessName'] ='C:\Windows\explorer.exe']]
       and
       *[System[(EventID=4688)]]
   </Select>
 </Query>
</QueryList>

My answer here mentions an alternative other than WMI:https://stackoverflow.com/a/50315772/3721646
WMI queries can cost heavy CPU performance if not designed properly. If an intrinsic event from Win32_Process class is used to track process creation event, this impacts performance heavily. An alternate approach is to leverage Security Audit logs. You can enable Process Tracking using Local Security Policy or using a GPO in case of multiple machines. Once the process tracking starts, you can subscribe to security event logs with a custom XML query to monitor certain processes of your interest. The process creation event ID is 4688. `

<QueryList>
 <Query Id="0" Path="Security">
   <Select Path="Security">
       *[EventData[Data[@Name='NewProcessName'] ='C:\Windows\explorer.exe']]
       and
       *[System[(EventID=4688)]]
   </Select>
 </Query>
</QueryList>
虚拟世界 2024-07-17 01:23:26

如果您只想查找进程的 PID/名称,您可能希望使用 WQL 查询来获取 Win32_ProcessTrace 事件,例如“SELECT * FROM Win32_ProcessTrace WHERE TargetInstance.ProcessName = 'name'”如果适用*

使用“SELECT * FROM __InstanceModificationEvent Within 10 WHERE TargetInstance ISA 'Win32Process' AND TargetInstance.Name = 'name'”的陷阱在于它在后端的工作方式。 如果您检查 %windir%\system32\wbem\logs 目录中的 wbemess.log,您将注意到以下日志(使用 __InstanceDeletionEvent):

(Wed Jul 22 13:58:31 2009.73889577) : Registering notification sink with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 047209E0 with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 0225E560 with query select * from __ClassOperationEvent where TargetClass isa "Win32_Process" in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 'select * from __ClassOperationEvent where TargetClass isa "Win32_Process"' with provider $Core
(Wed Jul 22 13:58:31 2009.73889587) : Activating filter 'select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process'' with provider $Core
(Wed Jul 22 13:58:31 2009.73889587) : Instituting polling query select * from Win32_Process to satisfy event query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process'
(Wed Jul 22 13:58:31 2009.73889587) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2'
(Wed Jul 22 13:58:31 2009.73889697) : Polling query 'select * from Win32_Process' done
(Wed Jul 22 13:58:41 2009.73899702) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2'
(Wed Jul 22 13:58:41 2009.73899792) : Polling query 'select * from Win32_Process' done

如您所见,远程计算机上的实际事件实现是针对 Win32_Process 执行查询在由WITHIN 子句中的值指定的间隔上。 因此,在该轮询中启动和停止的任何进程都不会触发事件。

您可以将WITHIN子句设置为一个较小的值,以尽量减少这种影响,但更好的解决方案是使用像Win32_ProcessTrace这样的真实事件,它应该始终触发。

*请注意,MSDN 表明 Win32_ProcessTrace 至少需要客户端计算机上的 Windows XP 和服务器计算机上的 Windows 2003 才能工作。 如果您使用的是较旧的操作系统,则可能会因使用 __InstanceModificationEvent 查询而陷入困境。

If you are only looking for PID/Name of your processes, you may instead wish to pick up on Win32_ProcessTrace events, using a WQL query such as "SELECT * FROM Win32_ProcessTrace WHERE TargetInstance.ProcessName = 'name'" if applicable*.

The pitfall of using "SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32Process' AND TargetInstance.Name = 'name'" is in how it works on the back end. If you inspect wbemess.log within your %windir%\system32\wbem\logs directory, you will notice the following logs (using __InstanceDeletionEvent):

(Wed Jul 22 13:58:31 2009.73889577) : Registering notification sink with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 047209E0 with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 0225E560 with query select * from __ClassOperationEvent where TargetClass isa "Win32_Process" in namespace //./root/CIMV2.
(Wed Jul 22 13:58:31 2009.73889577) : Activating filter 'select * from __ClassOperationEvent where TargetClass isa "Win32_Process"' with provider $Core
(Wed Jul 22 13:58:31 2009.73889587) : Activating filter 'select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process'' with provider $Core
(Wed Jul 22 13:58:31 2009.73889587) : Instituting polling query select * from Win32_Process to satisfy event query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process'
(Wed Jul 22 13:58:31 2009.73889587) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2'
(Wed Jul 22 13:58:31 2009.73889697) : Polling query 'select * from Win32_Process' done
(Wed Jul 22 13:58:41 2009.73899702) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2'
(Wed Jul 22 13:58:41 2009.73899792) : Polling query 'select * from Win32_Process' done

As you can see, the actual event implementation on the remote machine is to perform a query against Win32_Process on an interval that is specified by your value in the WITHIN clause. As a result, any processes that start and stop within that poll will never fire an event.

You can set the WITHIN clause to a small value to try and minimize this effect, but the better solution is to use a true event like Win32_ProcessTrace, which should always fire.

*Note that MSDN indicates Win32_ProcessTrace requires a minimum of Windows XP on a client machine and Windows 2003 on a server machine to work. If you are working with an older OS, you may be stuck using the __InstanceModificationEvent query.

墨小沫ゞ 2024-07-17 01:23:26

在侦听 WMI 事件时,如果我未能在退出/清理时正确地从事件中分离,我会遇到 CPU 峰值。 您可能需要检查是否“泄漏”了 WMI 事件订阅。 以防万一,尽早脱离活动,并确保你始终这样做。

为了进一步说明,这里有一个来自 我的 PowerShell 书籍 的示例,该示例侦听 WMI 事件使用 PSEventing 库:

添加 PSSnapin PSEventing -ErrorAction
默默地继续

$queryString = @'
选择 *
来自 __InstanceModificationEvent
10以内
在哪里
目标实例 ISA“Win32_Service”
AND TargetInstance.Name = 'w3svc'
AND TargetInstance.State = '已停止' '@

$query = 新对象 System.Management.WQLEventQuery `
-argumentList $queryString

$watcher = 新对象
System.Management.ManagementEventWatcher($query)

Connect-EventListener 观察者
活动已到达

$watcher.Start()

echo“等待W3CSVC服务
停止...” Get-Event -wait |
foreach {

Write-Host -foreground 红色“W3SVC 服务已停止!”
}

$watcher.Stop()

Disconnect-EventListener 观察器 EventArrived

回显“完成”

如果我​​在脚本退出时不执行 Disconnect-EventListener 位,我会在第三次或第四次附加到事件。 我的猜测是系统仍在尝试传递事件。

I've had CPU spikes when listening to WMI events in cases where I have failed to detach properly from my events on exit/cleanup. You might want to check you are not "leaking" WMI event subscriptions. Just in case detach from the event as early as possible and make sure you always do it.

To illustrate further, here's an example from my PowerShell book that listens to WMI events using the PSEventing library:

Add-PSSnapin PSEventing -ErrorAction
SilentlyContinue

$queryString = @'
SELECT *
FROM __InstanceModificationEvent
WITHIN 10
WHERE
TargetInstance ISA 'Win32_Service'
AND TargetInstance.Name = 'w3svc'
AND TargetInstance.State = 'Stopped' '@

$query = New-Object System.Management.WQLEventQuery `
-argumentList $queryString

$watcher = New-Object
System.Management.ManagementEventWatcher($query)

Connect-EventListener watcher
EventArrived

$watcher.Start()

echo "Waiting for the W3CSVC service
to stop..." Get-Event -wait |
foreach {

Write-Host -foreground Red "The W3SVC service has stopped!"
}

$watcher.Stop()

Disconnect-EventListener watcher EventArrived

echo "done"

If I do not do the Disconnect-EventListener bit upon script exit, I get CPU spikes the third or fourth time I attach to the event. My guess is that the system still tries to deliver events.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文