C# 系统 CPU 使用情况并与 Windows 任务管理器同步
这是一个由两部分组成的问题,我想将我的代码发布到堆栈上以帮助其他人完成相同的任务。
问题 1:
我有一个代码子集,我相信它可以根据测量间隔正确测量 CPU 使用情况(根据检索的时间跨系统中的尽可能多的核心) - 我使用 1 秒在线程调用中。
我必须从网上极少数的文章和 C++ 代码中破译这一点。我的问题是,对于问题 1,我所做的正确吗?
有时返回的值是负数,这就是我乘以 -1 的原因。再次,由于文档很少,我假设这就是我应该做的。
我有以下代码:
public static class Processor
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetSystemTimes(out ComTypes.FILETIME lpIdleTime, out ComTypes.FILETIME lpKernelTime, out ComTypes.FILETIME lpUserTime);
private static TimeSpan _sysIdleOldTs;
private static TimeSpan _sysKernelOldTs;
private static TimeSpan _sysUserOldTs;
static Processor()
{
}
public static void Test()
{
ComTypes.FILETIME sysIdle, sysKernel, sysUser;
if(GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
{
TimeSpan sysIdleTs = GetTimeSpanFromFileTime(sysIdle);
TimeSpan sysKernelTs = GetTimeSpanFromFileTime(sysKernel);
TimeSpan sysUserTs = GetTimeSpanFromFileTime(sysUser);
TimeSpan sysIdleDiffenceTs = sysIdleTs.Subtract(_sysIdleOldTs);
TimeSpan sysKernelDiffenceTs = sysKernelTs.Subtract(_sysKernelOldTs);
TimeSpan sysUserDiffenceTs = sysUserTs.Subtract(_sysUserOldTs);
_sysIdleOldTs = sysIdleTs;
_sysKernelOldTs = sysKernelTs;
_sysUserOldTs = sysUserTs;
TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds);
if (cpuUsage < 0)
{
Console.WriteLine("CPU: " + ((int) (cpuUsage)*-1) + "%");
}
else
{
Console.WriteLine("CPU: " + (int) (cpuUsage) + "%");
}
Console.WriteLine("");
}
else
{
Console.WriteLine("Couldn't get CPU usage!");
Console.WriteLine("");
}
}
private static TimeSpan GetTimeSpanFromFileTime(ComTypes.FILETIME time)
{
return TimeSpan.FromMilliseconds((((ulong)time.dwHighDateTime << 32) + (uint)time.dwLowDateTime) * 0.000001);
}
}
问题 2:
我是否可以同步线程,在我的程序中,与Windows任务管理器的线程,为了将测量数据(例如CPU使用情况)与上面的代码相匹配?
我的意思是,如果您打开 Windows 任务管理器,您会注意到它每秒轮询一次 - 实际上它不需要比这个少。我想做的就是将时间与我的线程相匹配。
因此,当 Windows 任务管理器轮询时,我的线程也会轮询。
一些注意事项:
我不想使用性能计数器或 .NET 内置方法。事实上,我相信 - 根据我所读到的内容,.NET 没有计算计算机上 CPU 使用率的方法,否则需要性能计数器。
性能计数器有开销,此外还会导致 GC 增长,更不用说调用下一个结果的延迟。虽然我的软件不需要实时性能,但我确实需要它具有尽可能高的响应速度并使用尽可能少的 CPU 时间。上面的代码可以在不到一毫秒的时间内调用并返回。事实上,在我的开发机器上,时间跨度差异显示为 0ms。 我不相信性能计数器的响应能力那么强。
如果您好奇,我的软件正在收集许多项目,CPU、内存、事件日志项目等,其中所有这些都需要收集并在下一次轮询之前(1 秒后)存储在 SQL CE 中。然而,每个任务、项目都在其自己的线程上以促进这一点。
另外,上面的代码无论如何都没有优化,你会注意到我还没有评论它。原因是我想在优化之前确保它是正确的等。
更新 1
根据我一路上发表的评论,我删除了额外的“系统”时间跨度,因为它不是必需的并进行了修改检索“CPU 使用率”并对其进行适当转换的行。
int cpuUsage = (int)(((sysKernelDifferenceTs.Add(sysUserDifferenceTs).Subtract(sysIdleDifferenceTs).TotalMilliseconds) * 100.00) / sysKernelDifferenceTs.Add(sysUserDifferenceTs).TotalMilliseconds);
虽然我仍然不确定这个公式。虽然它似乎非常准确,但它有时会返回负数,这就是为什么我将其乘以 -1(如果是这种情况)。毕竟,不存在 -2% CPU 使用率等这样的事情。
更新 2
所以我使用“System.Diagnostics.PerformanceCounter”做了一个简单的测试。虽然非常方便并且完全按照预期执行,但它确实会产生开销。
以下是我的观察结果:
- 性能计数器的初始化时间要长得多。在我的 i7 2.6 Ghz 上大约长了三秒。
- 性能计数器似乎还仅仅通过使用它就增加了大约 5MB 的 RAM 使用量。我的意思是:使用上面的代码,我的应用程序最大内存为 7.5MB。使用性能计数器时,它“起始”为 12.5MB。
- 在 5 秒的时间内,我的线程运行了 5 次 - 每秒一次,我的应用程序的内存增长了 1 MB,这种增长与时间一致,尽管在我的情况下它确实稳定了 3-4MB以上开始。因此,如果我的应用程序的内存通常为 7.5MB,上面的代码,则 PC 代码稳定在 16.5MB 内存 - 比上面的代码增加了 9MB。 注意:上面的代码不会导致这种增加。
因此,如果您的应用程序是以资源使用和计时为关键的方式构建的,那么由于这些原因,我建议不要使用性能计数器。否则就继续吧,因为它可以正常工作,不会出现任何混乱。
至于我的应用程序,性能计数器将不利于我的软件的目的。
this is a two part question, I wanted to post my code here on stack to help others with the same task.
Question 1:
I have a subset of code, which I believe, is correctly measuring CPU usage (across as many cores in the system, as per times retrieved) as per the measurement interval - I use 1 second in the thread call.
I had to decipher this from the very few articles on the web and from C++ code. My question is, for question 1, is this correct what I have done?
Sometimes the value returned is a minus figure which is why I multiply by -1. Again, I am assuming, since there is very little documentation, that this is what I should be doing.
I have the following code:
public static class Processor
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetSystemTimes(out ComTypes.FILETIME lpIdleTime, out ComTypes.FILETIME lpKernelTime, out ComTypes.FILETIME lpUserTime);
private static TimeSpan _sysIdleOldTs;
private static TimeSpan _sysKernelOldTs;
private static TimeSpan _sysUserOldTs;
static Processor()
{
}
public static void Test()
{
ComTypes.FILETIME sysIdle, sysKernel, sysUser;
if(GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
{
TimeSpan sysIdleTs = GetTimeSpanFromFileTime(sysIdle);
TimeSpan sysKernelTs = GetTimeSpanFromFileTime(sysKernel);
TimeSpan sysUserTs = GetTimeSpanFromFileTime(sysUser);
TimeSpan sysIdleDiffenceTs = sysIdleTs.Subtract(_sysIdleOldTs);
TimeSpan sysKernelDiffenceTs = sysKernelTs.Subtract(_sysKernelOldTs);
TimeSpan sysUserDiffenceTs = sysUserTs.Subtract(_sysUserOldTs);
_sysIdleOldTs = sysIdleTs;
_sysKernelOldTs = sysKernelTs;
_sysUserOldTs = sysUserTs;
TimeSpan system = sysKernelDiffenceTs.Add(sysUserDiffenceTs);
Double cpuUsage = (((system.Subtract(sysIdleDiffenceTs).TotalMilliseconds) * 100) / system.TotalMilliseconds);
if (cpuUsage < 0)
{
Console.WriteLine("CPU: " + ((int) (cpuUsage)*-1) + "%");
}
else
{
Console.WriteLine("CPU: " + (int) (cpuUsage) + "%");
}
Console.WriteLine("");
}
else
{
Console.WriteLine("Couldn't get CPU usage!");
Console.WriteLine("");
}
}
private static TimeSpan GetTimeSpanFromFileTime(ComTypes.FILETIME time)
{
return TimeSpan.FromMilliseconds((((ulong)time.dwHighDateTime << 32) + (uint)time.dwLowDateTime) * 0.000001);
}
}
Question 2:
Is there anyway for me to sync a thread, in my program, with that of the Windows Task Manager, for the purpose of matching measurement figure e.g CPU Usage with the above code?
What I mean is, if you open Windows Task Manager, you will notice that it polls every second - which in reality it doesn't need to be less than that. What I want to do is match the timing with my thread.
So when Windows Task Manager polls, my thread polls.
Some notes:
I didn't want to use Performance Counters or .NET built in methods. In fact, I believe - from what I have read, .NET doesn't have methods for calculating the CPU usage on a machine, that Performance counters are required for this otherwise.
Performance counters have overhead and in addition make the GC grow, not to mention the delay in calling the next result. While my software does not need to be real-time performance I do need it to be as responsive and use as little CPU time as possible. The above code can be called and returned in less than a millisecond. In fact on my development machine, the time-span difference shows 0ms. I don't believe Performance Counters are as responsive.
In case you are curious, my software is gathering a number of items, CPU, Memory, Event Log items etc. of which these all need to be gathered and stored, in SQL CE, before the next poll, 1 second away. Each task, item, however is on its own thread to facilitate this.
Also, the code above is not optimized in anyway and you will notice I have yet to comment it also. The reason being is I want to make sure it is correct before optimization etc.
Update 1
As per a coment I made down the way, I removed the extra "System" timespan as it is not required and modified the line that retrieves the "CPU Usage" and cast it appropriately.
int cpuUsage = (int)(((sysKernelDifferenceTs.Add(sysUserDifferenceTs).Subtract(sysIdleDifferenceTs).TotalMilliseconds) * 100.00) / sysKernelDifferenceTs.Add(sysUserDifferenceTs).TotalMilliseconds);
Though I am still unsure of the formula. While it seems to be highly accurate it does on occasion return a minus figure which is why I multiply it by -1 if that is the case. After all, there is no such thing a -2% CPU usage etc.
Update 2
So I did a simple test using "System.Diagnostics.PerformanceCounter". While incredibly handy and does exactly what it is intended to do it does create overhead.
Here are my observations:
- It took the Performance Counter that much longer to initialize. In the order of roughly three seconds longer on my i7 2.6 Ghz.
- The performance counter also seemed to add on another approx 5MB of RAM usage simply by using it. What I mean by this is: With the code above ,my app maxes out at 7.5MB ram. With the performance counter it "starts" at 12.5MB.
- Over the space of 5 seconds, where my thread ran 5 times - once per second, the memory of my app had grown by 1 MB and this increase is consistent with time, although it does level out, in my case anyway, 3-4MB above starting. So where my app is usually 7.5MB ram with the code above, the PC code leveled out at 16.5 MB ram - an increase of 9MB over the code above. Note: The code above does not cause this increase.
So, if your application was built in a manner where resource usage and timing is key I would suggest against using Performance counters because of these reasons. Otherwise go ahead as it works without all the mess.
As for my app, performance counters will be detrimental to my software's purpose.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为你的公式中有一个错误。您希望基本上计算 CPU 使用率,如下所示:
因此,对您的代码进行如下快速修改:
您最初将 cpuUsage 声明为“Double”。我不确定您是否想要浮点精度,但在您的代码中,您肯定没有得到除整数精度之外的任何东西,因为赋值语句只是进行整数数学运算。如果您需要更高的计算精度,您可以通过混合一些浮点来轻松获得它:
此外,关于与任务管理器同步。据我了解,任务管理器使用性能计数器。 (我怀疑 GetSystemTimes 正在幕后进行性能计数器调用,但也许不是)。我也不知道为什么你不使用性能计数器。 “%处理时间”计数器是一个即时样本计数器,不需要计算与先前结果的差异。 (每个逻辑 CPU 有一个)。使用 PDH 帮助程序函数而不是旧的注册表项 API 来获取它。您可以从非托管 C/C++ DLL 中执行此操作,该 DLL 将“GetCpuUsage”函数导出回 C# 代码。但我也不知道为什么你不能直接从 C# 调用 PDH 函数。我不知道你所说的这个开销。我也不确定我是否理解您提到的“调用下一个结果的延迟”。
I think you have a bug in your formula. You want to basically compute CPU usage as this:
Thus, a quick mod to your code as follows:
You originally declared cpuUsage as "Double". I'm not sure if you wanted floating point precision, but in your code, you definitely weren't getting anything other than integer precision because the assignment statement was just doing integer math. If you need higher precision from the computation, you could easily get it by mixing in some floating point:
Also, in regards to being in sync with Task Manager. Task Manager, as I understand it, uses perf counters. (And I would suspect that GetSystemTimes is making perf counter calls under the hood, but perhaps not). And I'm not sure why you wouldn't use perf counters either. The "% Process Time" counter is an instant sample counter that doesn't require computing a diff with a previous result. (There's one per logical cpu). Use the PDH helper functions instead of the legacy registry key apis to get at it. You can do this from an unmanaged C/C++ DLL that exports a "GetCpuUsage" function back to your C# code. But I don't know why you couldn't just PInvoke the PDH functions from C# either. I don't know about this overhead that you speak of. I'm not sure I understand your reference to " the delay in calling the next result" either.