我有以下类,它返回 IIS 每秒的当前请求数。我每分钟调用 RefreshCounters 以保持每秒请求数刷新(因为它是平均值,如果我将它保留太久,旧值会影响结果太多)......当我需要显示当前 RequestsPerSecond 时,我调用该属性。
public class Counters
{
private static PerformanceCounter pcReqsPerSec;
private const string counterKey = "Requests_Sec";
public static object RequestsPerSecond
{
get
{
lock (counterKey)
{
if (pcReqsPerSec != null)
return pcReqsPerSec.NextValue().ToString("N2"); // EXCEPTION
else
return "0";
}
}
}
internal static string RefreshCounters()
{
lock (counterKey)
{
try
{
if (pcReqsPerSec != null)
{
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
pcReqsPerSec = new PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true);
pcReqsPerSec.NextValue();
PerformanceCounter.CloseSharedResources();
return null;
}
catch (Exception ex)
{
return ex.ToString();
}
}
}
}
问题是有时会抛出以下异常:
System.InvalidOperationException: Category does not exist.
at System.Diagnostics.PerformanceCounterLib.GetCategorySample(String machine,\ String category)
at System.Diagnostics.PerformanceCounter.NextSample()
at System.Diagnostics.PerformanceCounter.NextValue()
at BidBop.Admin.PerfCounter.Counters.get_RequestsPerSecond() in [[[pcReqsPerSec.NextValue().ToString("N2");]]]
我没有正确关闭以前的 PerformanceCounter 实例吗?我做错了什么以至于有时会出现异常?
编辑:
并且只是为了记录,我在 IIS 网站中托管这个类(当然,也就是托管在具有管理权限的 App Pool 中)并从 ASMX 服务调用方法。使用计数器值(显示它们)的站点每 1 分钟调用一次 RefreshCounters,每 5 秒调用一次 RequestsPerSecond; RequestPerSecond 在调用之间缓存。
我每 1 分钟调用一次 RefreshCounters ,因为值往往会变得“陈旧” - 太受旧值的影响(例如,实际 1 分钟前)。
最佳答案
Antenka 在这里为您指引了一个好的方向。您不应该在每次更新/请求值(value)时处理和重新创建性能计数器。实例化性能计数器是有成本的,并且第一次读取可能不准确,如下面的引用所示。还有您的 lock() { ... }
语句非常广泛(它们涵盖了很多语句)并且速度会很慢。最好让你的锁尽可能小。我正在给 Antenka 投票以获取质量引用和好的建议!
但是,我想我可以为您提供更好的答案。我在监控服务器性能方面有相当多的经验,并且完全了解您的需求。您的代码没有考虑到的一个问题是,任何显示性能计数器的代码(.aspx、.asmx、控制台应用程序、winform 应用程序等)都可能以任何方式请求此统计信息;它可以每 10 秒请求一次,也许每秒 5 次,您不知道也不应该在意。因此,您需要将 PerformanceCounter 收集代码与实际报告当前 Requests/Second 值的代码分开。出于性能原因,我还将向您展示如何在第一次请求时设置性能计数器,然后保持它运行,直到 5 秒内没有人提出任何请求,然后正确关闭/处置 PerformanceCounter。
public class RequestsPerSecondCollector
{
#region General Declaration
//Static Stuff for the polling timer
private static System.Threading.Timer pollingTimer;
private static int stateCounter = 0;
private static int lockTimerCounter = 0;
//Instance Stuff for our performance counter
private static System.Diagnostics.PerformanceCounter pcReqsPerSec;
private readonly static object threadLock = new object();
private static decimal CurrentRequestsPerSecondValue;
private static int LastRequestTicks;
#endregion
#region Singleton Implementation
/// <summary>
/// Static members are 'eagerly initialized', that is,
/// immediately when class is loaded for the first time.
/// .NET guarantees thread safety for static initialization.
/// </summary>
private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector();
#endregion
#region Constructor/Finalizer
/// <summary>
/// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself.
/// </summary>
private RequestsPerSecondCollector()
{
LastRequestTicks = System.Environment.TickCount;
// Start things up by making the first request.
GetRequestsPerSecond();
}
#endregion
#region Getter for current requests per second measure
public static decimal GetRequestsPerSecond()
{
if (pollingTimer == null)
{
Console.WriteLine("Starting Poll Timer");
// Let's check the performance counter every 1 second, and don't do the first time until after 1 second.
pollingTimer = new System.Threading.Timer(OnTimerCallback, null, 1000, 1000);
// The first read from a performance counter is notoriously inaccurate, so
OnTimerCallback(null);
}
LastRequestTicks = System.Environment.TickCount;
lock (threadLock)
{
return CurrentRequestsPerSecondValue;
}
}
#endregion
#region Polling Timer
static void OnTimerCallback(object state)
{
if (System.Threading.Interlocked.CompareExchange(ref lockTimerCounter, 1, 0) == 0)
{
if (pcReqsPerSec == null)
pcReqsPerSec = new System.Diagnostics.PerformanceCounter("W3SVC_W3WP", "Requests / Sec", "_Total", true);
if (pcReqsPerSec != null)
{
try
{
lock (threadLock)
{
CurrentRequestsPerSecondValue = Convert.ToDecimal(pcReqsPerSec.NextValue().ToString("N2"));
}
}
catch (Exception) {
// We had problem, just get rid of the performance counter and we'll rebuild it next revision
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
}
stateCounter++;
//Check every 5 seconds or so if anybody is still monitoring the server PerformanceCounter, if not shut down our PerformanceCounter
if (stateCounter % 5 == 0)
{
if (System.Environment.TickCount - LastRequestTicks > 5000)
{
Console.WriteLine("Stopping Poll Timer");
pollingTimer.Dispose();
pollingTimer = null;
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
}
System.Threading.Interlocked.Add(ref lockTimerCounter, -1);
}
}
#endregion
}
好的,现在解释一下。
你不能加载它的多个副本,它有一个私有(private)构造函数
并且急切地初始化了自身的内部实例。这使得
确保您不会意外创建相同的多个副本
PerformanceCounter
. 一旦第一次访问类时)我们创建两个
PerformanceCounter
和一个计时器,用于轮询PerformanceCounter
. PerformanceCounter
如果需要并获取其下一个值可用。也是每 5 次迭代
我们将查看自您上次请求
PerformanceCounter
的值(value)。如果超过 5 秒,我们将关闭轮询计时器,因为它目前不需要。我们可以
如果我们再次需要它,请稍后再启动它。
GetRequestsPerSecond()
的静态方法为你将返回 RequestsPerSecond 的当前值的调用
PerformanceCounter
. 这种实现的好处是你只创建一次性能计数器,然后继续使用直到你完成它。它易于使用,因为您只需拨打
RequestsPerSecondCollector.GetRequestsPerSecond()
从任何你需要的地方(.aspx、.asmx、控制台应用程序、winforms 应用程序等)。永远只有一个 PerformanceCounter
并且无论您拨打 RequestsPerSecondCollector.GetRequestsPerSecond()
的速度有多快,它都会以每秒精确的速度轮询 1 次。 .它还会自动关闭并处理 PerformanceCounter
如果您在 5 秒内没有请求它的值。当然,您可以调整计时器间隔和超时毫秒以满足您的需要。你可以在 60 秒而不是 5 秒内更快地轮询和超时。我选择了 5 秒,因为它证明它在 Visual Studio 中调试时工作得非常快。一旦您测试它并知道它可以工作,您可能需要更长的超时时间。希望这不仅可以帮助您更好地使用 PerformanceCounters,而且可以安全地重用这个类,它与您想要显示统计信息的任何东西都是分开的。可重用的代码总是一个加分项!
编辑:作为后续问题,如果您想在此性能计数器运行时每 60 秒执行一次清理或保姆任务,该怎么办?好吧,我们已经让计时器每 1 秒运行一次,并且有一个变量跟踪我们的循环迭代,称为
stateCounter
每个计时器回调都会增加。所以你可以添加一些这样的代码:// Every 60 seconds I want to close/dispose my PerformanceCounter
if (stateCounter % 60 == 0)
{
if (pcReqsPerSec != null)
{
pcReqsPerSec.Close();
pcReqsPerSec.Dispose();
pcReqsPerSec = null;
}
}
我应该指出示例中的这个性能计数器不应该“过时”。我相信“请求/秒”应该是平均值而不是移动平均统计数据。但是这个示例只是说明了您 可以 定期对您的
PerformanceCounter
进行任何类型的清理或“照看”的方式时间间隔。在这种情况下,我们正在关闭并处理性能计数器,这将导致它在下一个计时器回调时重新创建。您可以根据您的用例和您正在使用的特定 PerformanceCounter 修改它。大多数人阅读这个问题/答案应该不需要这样做。检查您想要的 PerformanceCounter 的文档,看看它是否是连续计数、平均值、移动平均值等......并适当调整您的实现。
关于c# - 性能计数器 - System.InvalidOperationException : Category does not exist,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8171865/