做服务端的程序,经常要监控服务的性能,比如某个方法每秒执行了多少次了呀?某个方法同一时间有多少个并发方法啦?某个对象在内存里有多少个实例啦?执行某个操作的平均操作时间啦等等。.net提供了创建自定义性能计数器的API来让我们方便的实现这些需求,我改造了一下《.net企业应用高级编程》里的WEO框架的计数器部分,让大家方便的创建自己的计数器,原来是.net 1.0的,用的反射,我移植到了.net 2.0,改用的泛型,性能会好一些。先来看看大概思路。
要想创建自定义的计数器,先得实现IPerfCounterProvider接口,CreateCounters方法声明要创建的计数器,CountersCreated方法把创建的计数器赋值给本类的成员,PerformanceObjectName返回要创建自定义计数器的分类名。
PerfCounterFactory主要用来创建一个IPerfCounterProvider的实例,它有个GetCounters的泛型方法用来创建一个IPerfCounterProvider的实例,CreateCounters方法用来实际的创建性能计数器,另两个AddPerfCounter方法和Find方法是让IPerfCounterProvider的两个方法用的,下面的例子里会有。
PerfCounter是一个实体类,保存每个计数器的名称,帮助,类型。
好,我们要监控一个web服务的性能,这个Web服务提供相乘和相加的服务,我们要知道这个Web服务的总请求数,每秒调用次数,并发调用数,每个web方法每秒的调用次数,创建计数器如下,关于计数器的类型,及性能计数器的相关内容,请查看MSDN,计数器类型有总量计数器,平均计数器,速率计数器,并发计数器等,分别适用于不同的需求。
{
static MathServicePerfCounter _instance =
PerfCounterFactory.GetCounters<MathServicePerfCounter>();
public static MathServicePerfCounter Instance
{
get
{
return _instance;
}
}
public PerformanceCounter TotalOfRequest;
public PerformanceCounter RateOfRequest;
public PerformanceCounter RateOfAddition;
public PerformanceCounter RateOfMultiplication;
public PerformanceCounter CountOfCurrentRequest;
const string TotalOfRequestStr = "Total # req";
const string RateOfRequestStr = "req/sec";
const string RateOfAdditionStr = "Addition/sec";
const string RateOfMultiplicationStr = "Multiplication/sec";
const string CountOfCurrentRequestStr = "current # req";
IPerfCounterProvider 成员
}
从以上可以看到,接口实现里要做点儿啥,MathServicePerfCounter怎么用,你要想创建自己的计数器,比猫画虎就OK了,咱们开始创建一个Web服务,添加俩web方法
public int Addition(int a, int b)
{
MathServicePerfCounter.Instance.RateOfAddition.Increment();
Random r = new Random();
Thread.Sleep(r.Next(500, 2000));
return a + b;
}
[WebMethod]
public int Multiplication(int a, int b)
{
MathServicePerfCounter.Instance.RateOfMultiplication.Increment();
Random r = new Random();
Thread.Sleep(r.Next(500, 2000));
return a * b;
}
为了模拟真实环境,每个方法随机休眠几秒,然后右键点那个服务,在浏览器测试一下那个服务,看看能浏览不?然后在开始运行里输入perfmon,添加计数器里看看有没有咱们的MathServicePerfCounter计数器,反正我这里有。
对了,global.asax里加上如下代码,为了监控每秒请求和总请求量及并发请求量
{
MathServicePerfCounter.Instance.CountOfCurrentRequest.Increment();
MathServicePerfCounter.Instance.RateOfRequest.Increment();
MathServicePerfCounter.Instance.TotalOfRequest.Increment();
}
void Application_EndRequest(object sender, EventArgs e)
{
MathServicePerfCounter.Instance.CountOfCurrentRequest.Decrement();
}
服务有了,咱得有单元测试呀,在这个Web服务上点右键,创建一个单元测试,vs会自动给你创建一个测试项目,项目的名字你可以自己指定,然后会自动给你添加一个Web引用,然后自动生成一个叫MathService.asmxTest.cs的测试类,里面给你生成两个测试方法,修改一下初始值及期望值,去除Assert.Inconclusive语句,最后如下。
///Addition (int, int) 的测试
///</summary>
[TestMethod()]
public void AdditionTest()
{
MathService target = new MathService(); // TODO: 使用 [AspNetDevelopmentServer] 和 TryUrlRedirection() 自动启动并绑定 Web 服务。
int a = 5; // TODO: 初始化为适当的值
int b = 6; // TODO: 初始化为适当的值
int expected = 11;
int actual;
actual = target.Addition(a, b);
Assert.AreEqual(expected, actual, "MathServiceTest.localhost.MathService.Addition 未返回所需的值。");
}
/// <summary>
///Multiplication (int, int) 的测试
///</summary>
[TestMethod()]
public void MultiplicationTest()
{
MathService target = new MathService(); // TODO: 使用 [AspNetDevelopmentServer] 和 TryUrlRedirection() 自动启动并绑定 Web 服务。
int a = 5; // TODO: 初始化为适当的值
int b = 6; // TODO: 初始化为适当的值
int expected = 30;
int actual;
actual = target.Multiplication(a, b);
Assert.AreEqual(expected, actual, "MathServiceTest.localhost.MathService.Multiplication 未返回所需的值。");
}
单元测试也OK了,先打开测试管理器(上面有个按钮,“测试/窗口”菜单也可以打开),选中咱们那俩单元测试方法run一把,看看能通过不?应该问题不大,这么简单的俩方法,不应该出意外,我反正顺利通过。
OK,下一步,该做压力测试了,不做压力测试,咱的计数器也看不出啥来,VS就可以做压力测试(负载测试的一种),在MathServiceTest项目(测试项目)右键点“添加负载测试”
在“编辑负载测试方案的设置”里输入方案名,其它默认(我也不懂),如图
下一步,“编辑负载测试方案的负载模式设置”里,选择分级负载,因为咱们是压力测试,所以压力要慢慢上,虚拟用户从10个加到200个,每隔10秒,增加10个用户。要做冒烟测试的话,就选个常量负载,上来就弄个2000个用户,呵呵。咱们的设置如图
下一步,“向负载测试方案添加测试并编辑测试组合“,这里是要添加你要压的单元测试,其实负载测试可以压web测试,可以压单元测试,后者更灵活,啥都能压,不仅仅是web应用,当然vs的web测试在录制后可以转换成可编码的web测试,也可以灵活的修改。咱们把仅有的两个单元测试都添加进来,上面有个百分比控制条,表示做压力测试的时候,每个测试所承担的压力比重,就是调用次数的比例估计是。如果在这儿没调好,向导完了还可以在编辑测试里设置的,咱们先设置如图。具体每个选项的意思,MSDN里有,我重复也没用,重复错了就误人了。
下一步,”指定负载测试其间要使用的计数器集监视的计算机“,这里啥也不用选,直接下一步,其实这里是添加测试相关的机器的,比如咱们的那俩web服务,如果还调用其它机器的服务,就可以把那台服务器也加进来,到时候把那台机器的计数器也监控一下,看看瓶颈在哪台机器上。关于啥是Controller,啥是Agent,同样看MSDN,图略。
下一步后,如图,”查看并编辑负载测试的运行设置“,这里设置压力测试要执行的时间,预热时间等,如果在负载模式里设置了分级负载,而总体测试时间太短的话,有可能不会压到最大用户数,这点知道就行了。
最后,在Controller/计数器类别节点上点‘添加计数器”把咱们的自定义计数器加进来,再在测试组合里把相加测试调成30%,相乘测试调成70%,测试时间设置为5分钟,最后如图
好了,右键点根节点,选择“运行测试”,这时候会出现一个计数器图表界面,你可以双击左边的计数器,把计数器显示在图上,图表下面是图例,图表右边是绘图点,还是比较直观的,我就不截图了。关于怎么查看压力测试结果,MSDN里说的也很详细。
最后跑了5分钟,测试结果出来了,咱们来分析一下。如图
看到相加服务每秒调用次数四五次,别看它是最高的哪条线,但它的显示比例是乘以10,而相乘服务每秒调用次数是十三四次,别看它比较矮,它的显示比例十乘以100的,这个结果是因为咱们设置的两个服务的压力比重不一样。
每秒http请求数 req/sec在运行40秒后基本处于平稳状态,说明请求比较均匀。
总请求数total # req是一条歇着的直线,没有波动,这就对了,它是一直长,不跌,买股票就买这样的。
最后的并发请求数,current # req,基本上都是0,偶尔有个小尖儿,说明并发请求不是很严重。
下面这张图是说测试的,分别表示每秒执行的测试用例数,当前的虚拟用户数(虚拟用户是loadrunner的概念,这里借用一下),还有平均每个测试耗费的时间。
如图
可以看到虚拟用户到3分20秒的时候已经长到最大值200,测试的平均消耗时间也随着用户数的增加而增加,平稳而平稳,【每秒执行的测试数】起来后也是平稳的,和刚才那张图,【每秒的请求数】走势一样,毕竟在这里一个测试一个请求嘛,其实你把这两个计数器都显示出来,发现他们几乎是重叠的,真的,不骗你。
总结:这只是起个头儿,详细的没写,我改造的计数器组件也许还有BUG,关于压力测试的知识也远不止这些,关于性能优化的东西就更多了。最后祝大家做出支持高压力,高并发,响应快的NB程序。
说说不足的地方,
1、这个计数器组件,一个计数器类别只能有一个实现类,要把这个类别的计数器,都在一个类里声明,如果是俩类,访问第二个类的时候会把第一个类创建的计数器都给删了,具体逻辑看代码,想改自己改。好像是计数器类别里的计数器如果已经开始采样的话就不能往里面添加计数器了,只能把这个类别的计数器都删了,重添加一遍,具体说法看MSDN。我觉得一个服务用一个计数器类别就行了,把所有的计数器都放进去,弄个单件,别的类调用就行了,应该适用于大多需求了。
2、没有实现多实例的计数器,.net1.1的计数器不用关心计数器是否支持多实例,2.0需要指定,我这里没指定,想要支持的话自己支持,多实例就是你可以多起几个服务,计数器分别给每个进程实例采样和计数。
3、平均计数器没有演示,平均计数器一般用来计算某个操作所耗费的时间,在操作前用stopwatch开始计时,操作完了停止计时,把中间间隔的时间告诉给计数器对象就行了。另外并发的计数器也说一下,把计数器在执行操作前加1,执行操作后减1,就可以统计某个操作的并发量了。
4、计算内存里某个对象有多少个实例的计数器没有演示,你可以为某个类创建一个NumberOfItems64类型计数器,然后在其构造函数里Increment在析构函数里Decrement,要注意的是析构函数受GC控制,不要对析构函数里做任何假设,在析构函数里的任何代码都放在一个try里,防止抛出异常,这样基本上就可以正常运行了,但你的服务突然关闭的话,比如说windows关闭,析构函数里加try也还是防止不了出问题的,具体啥问题,怎么解决我也不懂了。
源码下载:PerfmonTest.zip