nik*_*3ro 12 c# asp.net iis performancecounter
我有以下类返回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();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
问题是有时抛出以下异常:
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");]]]
Run Code Online (Sandbox Code Playgroud)
我没有正确关闭PerformanceCounter的先前实例吗?我做错了什么,以便我有时会遇到这种异常?
编辑: 只是为了记录,我在IIS网站托管这个类(当然,托管在具有管理权限的应用程序池)和从ASMX服务调用方法.使用Counter值(显示它们)的站点每1分钟调用一次RefreshCounters,每5秒调用一次RequestsPerSecond; RequestPerSecond在调用之间缓存.
我每1分钟调用一次RefreshCounters,因为值往往变得"陈旧" - 过于受旧值的影响(例如1分钟前的实际值).
Ben*_*yne 16
Antenka在这里引领你走向了一个好方向.您不应该在每次更新/请求值时处置和重新创建性能计数器.实例化性能计数器是有成本的,并且第一次读取可能不准确,如下面的引用中所示.你的lock() { ... }陈述也非常广泛(它们涵盖了很多陈述)并且会很慢.最好让你的锁尽可能小.我正在给Antenka投票,以获得质量参考和好建议!
但是,我想我可以为您提供更好的答案.我在监控服务器性能和准确理解您的需求方面拥有相当丰富的经验.您的代码没有考虑到的一个问题是,无论代码显示您的性能计数器(.aspx,.asmx,控制台应用程序,winform应用程序等)都可能无论如何都要求此统计信息; 它可以每10秒请求一次,也许每秒5次,你不知道也不应该关心.因此,您需要将PerformanceCounter集合代码与实际报告当前请求/秒值的代码进行监视.出于性能原因,我还将向您展示如何在第一次请求时设置性能计数器,然后继续运行直到没有人提出任何请求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
}
Run Code Online (Sandbox Code Playgroud)
现在好了解一下.
PerformanceCounter.PerformanceCounter和一个将用于轮询的计时器
PerformanceCounter.PerformanceCounterif需要并获取其下一个值.此外,我们每隔5次迭代就会看到自上次请求该PerformanceCounter值以来已经过了多长时间
.如果它超过5秒,我们将关闭轮询计时器,因为此时它是不需要的.如果我们再次需要,我们可以在以后再次启动它.GetRequestsPerSecond(),它将返回RequestsPerSecond的当前值
PerformanceCounter.此实现的好处是,您只需创建一次性能计数器,然后继续使用,直到完成它为止.它易于使用,因为您可以RequestsPerSecondCollector.GetRequestsPerSecond()从任何需要的地方进行简单的调用(.aspx,.asmx,控制台应用程序,winforms应用程序等).总是只有一个PerformanceCounter,无论你多快打电话,它总是会以每秒1次的方式进行轮询RequestsPerSecondCollector.GetRequestsPerSecond().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;
}
}
Run Code Online (Sandbox Code Playgroud)
我应该指出,示例中的这个性能计数器不应该"过时".我认为'Request/Sec'应该是一个平均值而不是移动平均值统计数据.但是这个示例只是说明了一种方法,你可以PerformanceCounter在常规时间间隔内进行任何类型的清理或"保姆" .在这种情况下,我们正在关闭并处理性能计数器,这将导致它在下一次计时器回调时重新创建.您可以根据您的使用情况和您正在使用的特定PerformanceCounter修改它.大多数读这个问题/答案的人不应该这样做.检查所需PerformanceCounter的文档,以查看它是连续计数,平均值,移动平均值等...并适当调整您的实现.