在开发Azure应用时,性能监控是一个不可忽视的环节。大多数文章只提供了基础的示例,并没有深入讨论实际开发中可能遇到的问题。本文将分享在Azure应用性能监控过程中遇到的一些问题以及相应的解决方案。
创建了一个Azure应用,包含Web角色(1个实例)和Worker角色(2个实例),并使用Windows Azure Storage。为了检查性能,在Worker角色中启用了诊断功能,并创建了自定义的性能计数器,这些计数器属于“多实例”类别,通过启动任务以提升权限运行的PowerShell脚本创建。这些计数器随后在应用程序代码中被递增,应用程序代码也以提升权限运行。
PowerShell脚本创建了一个AverageTimer32和AverageBase计数器对。这些计数器(例如,'ResponseProcessTime'和'ResponseProcessTimeBase')帮助计算完成方法执行所需的时间。
尽管自定义性能计数器在Windows机器上可见(通过perfmon和服务器资源管理器的“机器名”性能计数器查看),但它们并没有被传输到WADPerformanceCounters表中,无论是在本地仿真器还是云存储中。偶尔可以看到计数器被保存在WADPerformanceCounters表中。有时,计数器只从单个实例出现在WADPerformanceCounters表中。如果没有这些计数器,将无法计算性能。非自定义计数器“已提交字节”和“%处理器时间”始终在WADPerformanceCounters表中可见。
在仿真器中遇到的错误在WADDiagnosticInfrastructureLogTable中可见。错误详细信息如下:
PdhGetFormattedCounterArray(\MSBAdapter(deployment18(283).MyAdapter.MyAdapter.Worker_IN_0)\ResponseProcessTime) failed pdhStatus=c0000bc6; retry 2
PdhGetFormattedCounterArray(\MSBAdapter(deployment18(283).MyAdapter.MyAdapter.Worker_IN_0)\ResponseProcessTimeBase) failed pdhStatus=800007d5; retry 2
AverageTimer32计数器并不总是如预期那样表现。有时,当计数器值成功传输到WADPerformanceCounterTable时,计数器的值为零。
文章的剩余部分将讨论如何解决这些问题。
本文不会详细介绍性能计数器的起源和类型,而是专注于问题及其解决方案。如需了解有关性能计数器的信息,请参阅以下资源:
上述问题被发现是Windows的一个已知限制,即一个进程在启动后无法检索另一个进程创建的实例计数器。这表明MonAgent(MA)启动和计数器实例创建之间存在竞态条件,因为使用了多个Worker实例。
“最佳解决方案可能是在启动任务中将实例计数器创建为‘Singleinstance’,但由于实例计数器需要在角色的生命周期中任意时间创建,因此需要按如下方式通过程序重置WAD配置,同时创建新的计数器实例”,因此继续通过PowerShell在启动时将性能计数器类别创建为'multiinstance',而不是在不同级别上任意时间创建计数器,将创建一个包含想要维护的每个计数器的公共属性的类,并在构造函数中创建它们。然后通过这些属性访问实际需要的所有计数器。可以使用Ninject注入单个实例。代码如下:
public interface IDiagnosticHelper
{
PerformanceCounter ResponseProcessTime { get; }
PerformanceCounter ResponseProcessTimeBase { get; }
}
public class DiagnosticHelper : IDiagnosticHelper
{
public static string defaultPerformanceCounterCategory = "MyAdapter";
public static string defaultPerformanceCounterInstance = RoleEnvironment.CurrentRoleInstance.Id;
public PerformanceCounter ResponseProcessTime { get; private set; }
public PerformanceCounter ResponseProcessTimeBase { get; private set; }
public DiagnosticHelper()
{
ResponseProcessTime = new PerformanceCounter(defaultPerformanceCounterCategory, "ResponseProcessTime ", defaultPerformanceCounterInstance, false);
ResponseProcessTimeBase = new PerformanceCounter(defaultPerformanceCounterCategory, "ResponseProcessTimeBase", defaultPerformanceCounterInstance, false);
Trace.TraceWarning("DiagnosticHelper Created performance counters.");
}
}
使用Ninject进行依赖注入。在Ninject模块中,指定单例范围(为了避免由于运行2个Worker角色实例而创建的竞态条件),如下绑定:
Bind<IDiagnosticHelper>().To<DiagnosticHelper>().InSingletonScope();
在Worker角色中,在Onstart和Run中注入这个接口,以便如下创建计数器:
ninjectKernel.Get<IDiagnosticHelper>();
现在在任何想要递增计数器的类/处理程序中,不会显式地再次创建计数器,而是调用诊断助手:
public class MyHandler
{
private readonly IDiagnosticHelper diagnosticHelper;
public MyHandler(IDiagnosticHelper diagnosticHelper)
{
this.diagnosticHelper = diagnosticHelper;
}
void CallMe()
{
var watch = new Stopwatch();
watch.Start();
int i = 3;
int j = 7;
int h = i + j;
Thread.Sleep(5000);
watch.Stop();
if (PerformanceCounterCategory.Exists("MyAdapter"))
{
if (null != diagnosticHelper.GetFacilityTime)
{
diagnosticHelper.GetFacilityTime.IncrementBy(watch.ElapsedTicks);
diagnosticHelper.GetFacilityTimeBase.Increment();
}
}
}
}
这段代码帮助解决了pdhStatus=c0000bc6错误(这意味着数据无效),因为现在竞态条件不再是问题。
错误pdhStatus=800007d5(没有数据返回)发生是因为最初将基础计数器‘ResponseProcessTimeBase’添加到了PerformanceCounters.DataSources中,这是不需要的,因为它是一个平均基础计数器,仅用于补充AverageTimer32计数器,本身不需要添加到数据源。
如果不需要显式地使用多实例,最好选择单实例性能类别。