您的位置:首页 > 运维架构

使用性能计数器监控应用程序

2013-12-31 22:55 260 查看
监控应用程序的性能的一个很好的办法就是使用Performance Counter。Windows提供了系统工具来显示PerformanceCounter收集的数据,在运行对话框中输入perfmon,就可以调出该工具来显示PerformanceCounter收集的数据。如果在工具中添加一个Performance Counter,就会发现,每一个Performance Counter都会属于一个目录,以便于组织Performance Counter。每一个目录下都有许多个Performance Counter,每一个Performance
Counter又会有很多实例,例如:在Process目录下,有很多Performance Counter,其中一个是%ProcessTime,表示CPU占用时间的Performance Counter,选择此Performance Counter,又会看到很多实例,每个实例的名字就是进程的名字,选择一个实例,就可以看到指定进程占用CPU时间的Performance Counter数据。有时候你会发现选择一个Performance Counter后并没有与之相关的任何实例,这表示该Performance Counter只能有一个实例。

在.Net中读取和写入PerformanceCounter数据都非常简单。在操作PerformanceCounter数据之前,首先要创建PerformanceCounterCategory,这只是对PerformanceCounter的一个简单分类,以便于查看PerformanceCounter数据。由于目录不能重复创建,所以需要在创建之前检测目录是否存在,如果存在可以继续使用,也可以对其进行删除,然后再创建它。对目录的删除和创建都需要管理员权限,所以,假如运行应用程序的账户不是管理员,操作目录就会抛出异常。所以应该在安装的时候创建目录,运行的时候只需要读取目录信息就可以了。

string categoryName = "My Service";

if (PerformanceCounterCategory.Exists(categoryName))

{

PerformanceCounterCategory.Delete(categoryName);

}

上面的代码展示了如何检测和删除目录。

对于创建目录,我们需要指定如下信息:

• 目录名

• 帮助描述,用于帮助用户了解此目录的用途

• 目录类型,目录类型是一个枚举类型,具有如下定义:

public enum PerformanceCounterCategoryType

{

Unknown = -1,

SingleInstance = 0,

MultiInstance = 1,

}

SingleInstance表示目录内部的PerformanceCounter只能有一个实例。MultiInstance表示目录内部的PerformanceCounter可以有多个实例,如果指定了SingleInstance,则在创建PerformanceCounter实例的时候,不需要指定InstanceName属性,否则会抛出异常。按照我的理解,此属性更应该属于CounterCreationData类。此类用于设置PerformanceCounter相关属性。

• 一个CounterCreationDataCollection的实例

此集合对象包含了创建PerformanceCounter的信息,它的项的类型是CounterCreationData,此类型的实例中,我们可以指定CounterName,CounterType等属性,有了这些信息我们就可以在安装时定义PerformanceCounter。而运行时,就可以根据定义创建PerformanceCounter实例来读取或者写入性能数据。

CounterCreationDataCollection ccdc = new CounterCreationDataCollection();

CounterCreationData ccd = new CounterCreationData();

ccd.CounterName = "# of Request";

ccd.CounterType = PerformanceCounterType.NumberOfItems32;

ccdc.Add(ccd);

ccd = new CounterCreationData();

ccd.CounterName = "# of Event";

ccd.CounterType = PerformanceCounterType.NumberOfItems32;

ccdc.Add(ccd);

var category = PerformanceCounterCategory.Create(

categoryName,

"Monitor Performance of My Service",

PerformanceCounterCategoryType.SingleInstance,

ccdc);

上面的代码创建了包含两个PerformanceCounter的目录。通过Perfmon工具,可以看到我们创建的目录和PerformanceCounter,如果将其添加到监控面板,我们会发现图表中没有任何数据。这是因为我们还没有向其写入数据。

下面的代码将会写入性能数据,此代码只是为了测试PerformanceCounter。

PerformanceCounter pc = new PerformanceCounter();

pc.CategoryName = categoryName;

pc.CounterName = counterName;

//pc.InstanceName = "# of Session Request";

pc.ReadOnly = false;

pc.RawValue = 0;

Timer timer = new Timer(

delegate

{

long nextValue = pc.RawValue + 1;

if (nextValue > 1000)

{

pc.RawValue = 0;

}

else

{

pc.RawValue = nextValue;

}

},

null,

10,

10);

Console.WriteLine("Press any key to exit...");

Console.ReadKey();

我们让性能数据每次递增1,直到递增到1000,完成后再重新从0开始。很容易想象此中数据的线图形状,从perfmon工具中查看此PerformanceCounter,会发现图像基本相同,但不会完全准确,因为其中涉及到一个采样频率的问题,不太可能做到完全准确。

上面的代码中,我们使用那个RawValue属性直接设置了性能数据。但是此属性并不是线程安全的。如果想要在多个线程中同时写入同一个PerformanceCounter,那么就需要使用如下三个方法:

public long Decrement();

public long Increment();

public long IncrementBy(long value);

这三个方法以线程安全的方式写入性能数据。对value传递负值,可以对当前性能计数做减法。

使用Performance Counter中,最重要的一个属性就是PerformanceCounterType,此属性定义在CounterCreationData中。此属性决定了性能计数的含义。

对于前面的示例,我们使用了NumberOfItems32类型,此类型表示我们写入的性能计数是一个整数,表示项的个数。还有其他三个类型也表示相同的含义,NumberOfItems64,NumberOfItemsHEX32,NumberOfItemsHEX64。它们之间的差别只是整数的数值范围和表示形式。

RateOfCountsPerSecond32类型的PerformanceCounter表示在取样间隔的每一秒内完成的操作的平均数目。如下所示,创建一个类型为RateOfCountsPerSecond32的PerformanceCounter:

CounterCreationData ccd = new CounterCreationData();

ccd.CounterName = "Operations per second";

ccd.CounterType = PerformanceCounterType.RateOfCountsPerSecond32;

PerformanceCounter pc1 = new PerformanceCounter();

pc1.CategoryName = categoryName;

pc1.CounterName = "Operations per second";

pc1.ReadOnly = false;

pc1.RawValue = 0;

下面的代码模拟一个操作,该操作耗时10毫秒,并且被一直调用。

ThreadPool.QueueUserWorkItem(delegate

{

int opTime = 10;

while (true)

{

pc1.Increment();

Thread.Sleep(opTime);

}

});

检测此性能计数,会发现性能曲线图是一条直线,每个采样点的值大概为100,这是因为我们每秒模拟调用操作100次。RateOfCountsPerSecond64类型与RateOfCountsPerSecond32具有相同的含义。

CountPerTimeInterval32和CountPerTimeInterval64表示两次采样数据与采样间隔的比值,类似于加速度值。例如第一次采样时,队列里有100个消息,第二次采样的时候有200个消息,两次采样间隔为1秒,那么采样值为200-100/1 = 100,个人认为这个性能计数类型几乎没啥用处。

RawFraction是一个很有用的计数类型,它表达一个资源使用率的概念。它必须要与RawBase结合使用。RawFraction代表的PerformanceCounter表达正在使用的资源量,RawBase表达的是总资源量。例如系统中内存使用计数器,表达的是已经使用的内存和总内存的比值。具体的计算公式是:(使用的资源/总资源)×100。在创建RawFraction类型的PerformanceCounter时,必须在其后定义RawBase类型的PerformanceCounter。如下:

CounterCreationDataCollection ccdc = new CounterCreationDataCollection();

CounterCreationData ccd = new CounterCreationData();

ccd.CounterName = "Message Count";

ccd.CounterType = PerformanceCounterType.RawFraction;

ccdc.Add(ccd);

ccd = new CounterCreationData();

ccd.CounterName = "Max Message Count";

ccd.CounterType = PerformanceCounterType.RawBase;

ccdc.Add(ccd);

创建PerformanceCounter实例的代码如下:

PerformanceCounter pc1 = new PerformanceCounter();

pc1.CategoryName = categoryName;

pc1.CounterName = "Message Count";

pc1.ReadOnly = false;

pc1.RawValue = 0;

PerformanceCounter pcb1 = new PerformanceCounter();

pcb1.CategoryName = categoryName;

pcb1.CounterName = "Max Message Count";

pcb1.ReadOnly = false;

pcb1.RawValue = 10000;

总资源量既可以是固定的,也可以是可变的,这取决于具体的需求,如果资源表示的是内存,那么总资源就不太可能是变化的。如下代码模拟一个使用的资源一直在增加的场景:

ThreadPool.QueueUserWorkItem(delegate

{

int opTime = 100;

while (true)

{

pc1.Increment();

Thread.Sleep(opTime);

}

});

可以看到,总资源量为10000,每100毫秒资源使用量都加1,所以资源将在1000秒后耗尽。我们看到的线图是一个是一个一直增长的曲线。

在测试上述代码的时候,一定要先创建性能计数器。然后再次启动进程写入性能数据。性能计数器不能创建后立刻使用,立刻使用将会导致数据无法被正确处理。这也正是为什么微软推荐在安装包中创建性能计数器的原因。

AverageTimer32类型表示完成一个操作所需要的时间,时间单位是秒。该类型需要必须与AverageBase类型配合使用,后者表示操作个数,前者表示操作总时间。两者相除就是单个操作所需时间。示例代码如下:

ThreadPool.QueueUserWorkItem(delegate

{

int opTime = 100;

while (true)

{

pc1.RawValue = Stopwatch.GetTimestamp();

pcb1.Increment();

Thread.Sleep(opTime);

}

});

上述代码模拟了一个100毫秒的操作。那么输出的采样值就应该为0.1秒。个人感觉这个性能计数只在连续调用的时候才有意义。假如调用不是连续的,那么两次采样的值应该包含中间的空闲时间,显然这样的采样值是毫无意义的,并不能代表单个操作的执行时间。

AverageCount64类型与AverageBase类型联合使用表示每个操作处理了多少项。例如在十次操作中总共发送了10k字节,那么采样值为10k/10=1k。表示每次操作平均发送1k字节。AverageBase表示两次采样间的操作数,AverageCount64表示两次采样间处理的总项数。

CounterDelta32类型表示两次采样之间的差异,CounterDelta64类型具有相同的功能。(不知道具体应用在哪种场景?)

SampleFraction类型和SampleBase类型的计数器结合使用,可以获取最近两个采用时间间隔内的命中与所有操作的平均比率。公式:((N 1 - N 0) / (D 1 - D 0)) x 100。此计数可以表示进程中用户占用的CPU时间和全部CPU时间之间的比例。还可以表示缓存命中次数和全部缓存探测次数的比例,即缓存命中率。

看上去SampleCounter和RateOfCountsPerSecond32之间没有什么分别?

Timer100Ns类型的计数器表示占用时间百分比。它以 100 毫微秒 (ns) 为单位来测量时间。公式为(N 1 - N 0) / (D 1 - D 0) x 100,其中分子表示活动状态的时间,分母表示取样时间间隔的总运行时间。这种类型的计数器包括 Processor\ % User Time。具体来说,分子表示用户CPU时间,分母表示总时间间隔,即两次调用Increment方法的时间间隔。鉴于此,此计数器的RawValue属性必须为时间,以100ns为单位,也就是DateTime中的Ticks的单位。Timer100NsInverse类型的计数器是Timer100Ns的反向计数器,公式为:(1-
((N 1 - N 0) / (D 1 - D 0))) x 100,表示非活动时间的占用百分比。这种类型的计数器包括 Processor\ % Processor Time。

CounterTimer和CounterTimerInverse类型的计数器与上面的两个计数器相对应,他们之间的区别是时间单位。CounterTimer和CounterTimerInverse类型的计数器的时间单位是系统的Tick,而不是100ns。

ElapsedTime计数器表示系统的启动时间。公式:(D 0 - N 0) / F,其中 D 0 表示启动结束时间,N 0 表示对象的启动开始时间,F 表示一秒内经过的时间单位数。 F表示需要使用系统的Tick刻度,而不是标准的Tick刻度。系统的。Tick刻度是Stopwatch.GetTimestamp方法返回值的刻度。标注的Tick刻度是DateTime的Ticks的刻度。这种类型的计数器包括 System\ System Up Time。个人认为这种计数器几乎无用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: