Redis系列(五)-Opserver的监控

基本介绍

Opserver是Stack Exchange的一个开源监控系统,基于Net、MVC开发,所以Net程序员可以轻松基于它二次开发。它主要监控:

  • servers
  • SQL clusters/instances
  • redis
  • elastic search
  • exception logs
  • haproxy

Opserver提供详细的面板,用来快速展示被监控系统的总体情况。 下面Opserver的监控UI界面示例,非常详细: 技术分享技术分享

使用配置

项目地址:https://github.com/opserver/Opserver

下载后用VS打开或IIS直接部署即可,下面是它的支持监控系统的view目录,结构比较清晰。

技术分享

安全配置

Opserver系统本身后登陆验证,支持3种安全认证方式:

<?xml version="1.0" encoding="utf-8"?>
<SecuritySettings provider="AD">
    <!-- 可选, 下面的网络可以不用验证直接访问 -->
    <InternalNetworks>
        <Network name="SE Internal" cidr="10.0.0.0/8" />
    </InternalNetworks>
</SecuritySettings>

<!-- 
每个人都是管理都可访问
<SecuritySettings provider="alladmin" />
-->

如果使用活动目录验证,可以直接在web.config配置ViewGroups、AdminGroups,也可以单独在每个系统监控json配置文件里面添加ViewGroups、AdminGroups:

"viewGroups": "*",
"adminGroups": "SysAdmins",
"user": "user",
"password": "pass",
"adminUser": "adminuser",
"adminPassword": "adminpass",

监控配置

配置监控的地方在/Config/目录,Stack Exchange提供对应系统的配置示例,如图: 如果没有配置任何系统监控文件,浏览OpServer页面时,会报‘No Configuration‘的警告提示。 这里以Redis为例,监控配置如下:

{
    "allServers": {
        "name": "All",
        "instances": [
              {
                "name": "本地master",
                "port": "6379"
            },
            {
                "name": "本地slave1",
                "port": "6380"
            },
            {
                "name": "本地master2",
                "port": "6382"
            }
        ]
          
    },
    "Servers": [
        { "name": "127.0.0.1" }
    ]
}

部署实例

认证配置<SecuritySettings provider=”alladmin”>所有人都是管理员,打开浏览器访问,输入账号admin,密码admin:

技术分享

可以看到有2组实例,其中6380是slave,6379是master,从图表上可以清晰看到层架关系。

技术分享

实例列表:

技术分享

点击单个Redis实例进去看到。

技术分享

面板属性

面板展示的属性都是可以通过redis info命令获取到,opserver做了更清晰的展示。

Ops(/sec)  每秒处理量

memory(used)即used_memory_rss(used_memory)

used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps等命令的输出一致。

used_memory_peak : Redis 的内存消耗峰值(以字节为单位)

used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位

 

Summary是总体概览部分。

Memory是内存使用情况,重要。

persistence 是RDB和AOF的状态。
keyspace key存储的情况,analyze进去可以查看详细分布。
stats  客户端命令的key命中率和处理量
clients 查看有哪个ip(或机器名)过来的连接数多,很方便的定位到那台应用端机器长时间没有释放连接,重要。

slow command log 服务端接受的命令日志。

总结

Opserver 算是个比较轻量级的监控系统,部署修改都非常方便,比如增加连接数或者内存报警功能。

Opserver简单部署

一、下载opserver项目

地址:https://github.com/opserver/Opserver/

二、用vs2012及以上版本打卡opserver项目,如图

三、右击Opserver,点“设为启动项”

四、调试(F5)

调试之后,发现报错

停止调试进入Opserver项目下的Config目录找到SecuritySettings.config.example文件。清单如下:

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <SecuritySettings provider=“AD”>
  3.     <  Optional, these networks can see the overview dashboard without authentication  >
  4.     <InternalNetworks>
  5.         <Network name=“SE Internal” cidr=“10.0.0.0/8” />
  6.     </InternalNetworks>
  7. </SecuritySettings>
  8. <!–Example of global access for everyone:
  9. <SecuritySettings provider=“alladmin” >–>

 

修改cidr配置为你的本地地址如:192.168.0.0/24或者127.0.0.1【可选 这些网络无须身份验证就可以看到概览仪表板】

我这里直接将文件改为:

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <SecuritySettings provider=“alladmin” >
  3. </SecuritySettings>

 

保存为SecuritySettings.config(去掉example),再次调试:

因为安全设置是alladmin,所以直接点击Login in即可,进去之后你会看到:

这是因为还没有配置以下相关监控文件

这里以SQL为例:

找到Opserver下面的config/SQLSettings.json.example文件,双击编辑:

  1. {
  2.   “defaultConnectionString”“Data Source=$ServerName$;Initial Catalog=master;Integrated Security=SSPI;”,
  3.   “instances”: [
  4.       {
  5.           “name”“实例名”,
  6.         “connectionString”“Data Source=实例名;Initial Catalog=DB;UID=sa;pwd=****;”
  7.       },
  8.     { “name”“实例名” }
  9. }

再次调试,看到以下界面

点击Node可以看到实例详细。

完毕~微笑

跟蓝狐学MVC教程–MiniProfiler.EF6监控调试MVC5和EF6的性能

以前开发Webform的时候可以开启trace来跟踪页面事件,这对于诊断程序的性能是有很大的帮助的,起到事半功倍的作用,今天我就来谈用mvc开发项目的调试和性能监控。EF框架自动给我生成sql语句,当我们的程序遇到性能问题的时候我们可以用MiniProfiler.EF来监控调试MVC和EF的性能,查看生成的sql语句、运行了哪些sql,以及所花的时间。MiniProfiler.EF,一个轻量级开源的mvc性能调试、监控组件MiniProfiler专门为EF定制的版本。下面通过一个具体一例子的说明怎么在我们的项目中用MiniProfiler.EF6监控调试MVC和EF的性能。下面的项目是基于我上面的一篇文章的,MVC5与EF6 Code First 第一个入门完整实例教程

1、安装MiniProfiler.EF6

nuget搜索框中输入MiniProfiler,将出现下面结果:

点击安装将把MiniProfiler.EF6相关的dll加到项目中。

2、添加MiniProfiler.EF相关代码到项目里面

1、在Global.asax加入MiniProfiler相关的监控代码

修改之后完整内容为:

  1. using System.Web.Mvc;
  2. using System.Web.Optimization;
  3. using System.Web.Routing;
  4. using StackExchange.Profiling;
  5. using StackExchange.Profiling.EntityFramework6;
  6. namespace MiniProfilerDemo
  7. {
  8. public class MvcApplication : System.Web.HttpApplication
  9. {
  10. protected void Application_Start()
  11. {
  12. AreaRegistration.RegisterAllAreas();
  13. FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
  14. RouteConfig.RegisterRoutes(RouteTable.Routes);
  15. BundleConfig.RegisterBundles(BundleTable.Bundles);
  16. MiniProfilerEF6.Initialize();
  17. }
  18. protected void Application_BeginRequest()
  19. {
  20. MiniProfiler.Start();
  21. }
  22. protected void Application_EndRequest()
  23. {
  24. MiniProfiler.Stop();
  25. }
  26. }
  27. }

其中是在Application_Start加入了MiniProfilerEF6.Initialize()和添加了Application_BeginRequest、Application_BeginRequest两个Application的事件函数,这个的作用分别是初始化MiniProfilerEF6和开始、结束MiniProfiler监控。

2、修改_Layout.cshtml视图文件

在Views\Shared\_Layout.cshtml文件的body前面加上一段代码,让监控展示在页面上。

  1. @StackExchange.Profiling.MiniProfiler.RenderIncludes()

如下图:

3、在Web.config加入代码

  1. <system.webServer>
  2. <handlers>
  3. <add name=“MiniProfiler” path=“mini-profiler-resources/*” verb=“*” type=“System.Web.Routing.UrlRoutingModule” resourceType=“Unspecified” preCondition=“integratedMode” />
  4. </handlers>
  5. </system.webServer>

为了要在页面上显示MVC和EF的调试跟踪时间必须要加入上面的代码。如下图:

在system.webServer配置结点下的handlers结点,加入了一个名为MiniProfiler的handler。

3、查看运行结果

运行程序,查看页面如下图:

可以看到左角多了一个数字的区块,表示这个页面所花的毫秒数,点击上面的数字就可以弹出详细的时间跟踪信息,如下图:

可以看到这个页面运行了三个sql语句,sql所花时间为您673.4毫秒,占部时间为为12.5%。还可以点击这个sql的时间,将显示运行了哪些sql,如下图:

4、细微监控方法内部的时间

现在我们为ProductController加上监控,监控获取从数据库中获取Product记录所花的时间。我们把ProductController修改为:
  1. using MiniProfilerDemo.DAL;
  2. using System.linq;
  3. using System.Web.Mvc;
  4. using StackExchange.Profiling;
  5. using System.Collections.Generic;
  6. using MiniProfilerDemo.Models;
  7. namespace MiniProfilerDemo.Controllers
  8. {
  9. public class ProductController : Controller
  10. {
  11. public ActionResult Index()
  12. {
  13. using (EFDbContext db = new EFDbContext())
  14. {
  15. var profiler = MiniProfiler.Current;
  16. List<Product> m;
  17. using (profiler.Step(“获取Product列表”))
  18. {
  19. m = db.Products.ToList();
  20. }
  21. return View(m);
  22. }
  23. }
  24. }
  25. }

重新生成项目,再次运行查看页面,如下图:

可以看到上面多了我们刚才手动加的“获取Product列表”条记录。这样可以细微监控方法内部的时间,方便、快速地帮我们找出我们的程序的瓶颈所在。

本站文章除注明转载外,均为本站原创或翻译,欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,共创和谐网络环境。
转载请注明:文章转载自:蓝狐软件工作室 » 跟蓝狐学MVC教程–MiniProfiler.EF6监控调试MVC5和EF6的性能
本文标题:跟蓝狐学MVC教程–MiniProfiler.EF6监控调试MVC5和EF6的性能
本文地址:http://www.lanhusoft.com/Article/125.html

Google Protocol Buffer 的使用和原理

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

刘 明, 软件工程师, 上海交大电子与通信系

2010 年 11 月 18 日

  • +内容

简介

什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍:

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

或许您和我一样,在第一次看完这些介绍后还是不明白 Protobuf 究竟是什么,那么我想一个简单的例子应该比较有助于理解它。

一个简单的例子

安装 Google Protocol Buffer

在网站 http://code.google.com/p/protobuf/downloads/list上可以下载 Protobuf 的源代码。然后解压编译安装便可以使用它了。

安装步骤如下所示:

 tar -xzf protobuf-2.1.0.tar.gz 
 cd protobuf-2.1.0 
 ./configure --prefix=$INSTALL_DIR 
 make 
 make check 
 make install

关于简单例子的描述

我打算使用 Protobuf 和 C++ 开发一个十分简单的例子程序。

该程序由两部分组成。第一部分被称为 Writer,第二部分叫做 Reader。

Writer 负责将一些结构化的数据写入一个磁盘文件,Reader 则负责从该磁盘文件中读取结构化数据并打印到屏幕上。

准备用于演示的结构化数据是 HelloWorld,它包含两个基本数据:

  • ID,为一个整数类型的数据
  • Str,这是一个字符串

书写 .proto 文件

首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义。代码清单 1 显示了例子应用中的 proto 文件内容。

清单 1. proto 文件
 package lm; 
 message helloworld 
 { 
    required int32     id = 1;  // ID 
    required string    str = 2;  // str 
    optional int32     opt = 3;  //optional field 
 }

一个比较好的习惯是认真对待 proto 文件的文件名。比如将命名规则定于如下:

 packageName.MessageName.proto

在上例中,package 名字叫做 lm,定义了一个消息 helloworld,该消息有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 str。opt 是一个可选的成员,即消息中可以不包含该成员。

编译 .proto 文件

写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。

假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

 protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

命令将生成两个文件:

lm.helloworld.pb.h , 定义了 C++ 类的头文件

lm.helloworld.pb.cc , C++ 类的实现文件

在生成的头文件中,定义了一个 C++ 类 helloworld,后面的 Writer 和 Reader 将使用这个类来对消息进行操作。诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。

编写 writer 和 Reader

如前所述,Writer 将把一个结构化数据写入磁盘,以便其他人来读取。假如我们不使用 Protobuf,其实也有许多的选择。一个可能的方法是将数据转换为字符串,然后将字符串写入磁盘。转换为字符串的方法可以使用 sprintf(),这非常简单。数字 123 可以变成字符串”123”。

这样做似乎没有什么不妥,但是仔细考虑一下就会发现,这样的做法对写 Reader 的那个人的要求比较高,Reader 的作者必须了 Writer 的细节。比如”123”可以是单个数字 123,但也可以是三个数字 1,2 和 3,等等。这么说来,我们还必须让 Writer 定义一种分隔符一样的字符,以便 Reader 可以正确读取。但分隔符也许还会引起其他的什么问题。最后我们发现一个简单的 Helloworld 也需要写许多处理消息格式的代码。

如果使用 Protobuf,那么这些细节就可以不需要应用程序来考虑了。

使用 Protobuf,Writer 的工作很简单,需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个 C++ 的类,并定义在 lm.helloworld.pb.h 中。对于本例,类名为 lm::helloworld。

Writer 需要 include 该头文件,然后便可以使用这个类了。

现在,在 Writer 代码中,将要存入磁盘的结构化数据由一个 lm::helloworld 类的对象表示,它提供了一系列的 get/set 函数用来修改和读取结构化数据中的数据成员,或者叫 field。

当我们需要将该结构化数据保存到磁盘上时,类 lm::helloworld 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

对于想要读取这个数据的程序来说,也只需要使用类 lm::helloworld 的相应反序列化方法来将这个字节序列重新转换会结构化数据。这同我们开始时那个“123”的想法类似,不过 Protobuf 想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给 Protobuf 吧。

程序清单 2 演示了 Writer 的主要代码,您一定会觉得很简单吧?

清单 2. Writer 的主要代码
 #include "lm.helloworld.pb.h"
…

 int main(void) 
 { 
  
  lm::helloworld msg1; 
  msg1.set_id(101); 
  msg1.set_str(“hello”); 
    
  // Write the new address book back to disk. 
  fstream output("./log", ios::out | ios::trunc | ios::binary); 
        
  if (!msg1.SerializeToOstream(&output)) { 
      cerr << "Failed to write msg." << endl; 
      return -1; 
  }         
  return 0; 
 }

Msg1 是一个 helloworld 类的对象,set_id() 用来设置 id 的值。SerializeToOstream 将对象序列化后写入一个 fstream 流。

代码清单 3 列出了 reader 的主要代码。

清单 3. Reader
 #include "lm.helloworld.pb.h" 
…
 void ListMsg(const lm::helloworld & msg) { 
  cout << msg.id() << endl; 
  cout << msg.str() << endl; 
 } 
 
 int main(int argc, char* argv[]) { 

  lm::helloworld msg1; 
 
  { 
    fstream input("./log", ios::in | ios::binary); 
    if (!msg1.ParseFromIstream(&input)) { 
      cerr << "Failed to parse address book." << endl; 
      return -1; 
    } 
  } 
 
  ListMsg(msg1); 
  … 
 }

同样,Reader 声明类 helloworld 的对象 msg1,然后利用 ParseFromIstream 从一个 fstream 流中读取信息并反序列化。此后,ListMsg 中采用 get 方法读取消息的内部信息,并进行打印输出操作。

运行结果

运行 Writer 和 Reader 的结果如下:

 >writer 
 >reader 
 101 
 Hello

Reader 读取文件 log 中的序列化信息并打印到屏幕上。本文中所有的例子代码都可以在附件中下载。您可以亲身体验一下。

这个例子本身并无意义,但只要您稍加修改就可以将它变成更加有用的程序。比如将磁盘替换为网络 socket,那么就可以实现基于网络的数据交换任务。而存储和交换正是 Protobuf 最有效的应用领域。

和其他类似技术的比较

看完这个简单的例子之后,希望您已经能理解 Protobuf 能做什么了,那么您可能会说,世上还有很多其他的类似技术啊,比如 XML,JSON,Thrift 等等。和他们相比,Protobuf 有什么不同呢?

简单说来 Protobuf 的主要优点就是:简单,快。

这有测试为证,项目 thrift-protobuf-compare 比较了这些类似的技术,图 1 显示了该项目的一项测试结果,Total Time.

图 1. 性能测试结果

图 1. 性能测试结果Total Time 指一个对象操作的整个时间,包括创建对象,将对象序列化为内存中的字节序列,然后再反序列化的整个过程。从测试结果可以看到 Protobuf 的成绩很好,感兴趣的读者可以自行到网站 http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking上了解更详细的测试结果。

Protobuf 的优点

Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。

它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。

Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。

使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。

Protobuf 的不足

Protbuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。

XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。

由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容【 2 】。

高级应用话题

更复杂的 Message

到这里为止,我们只给出了一个简单的没有任何用处的例子。在实际应用中,人们往往需要定义更加复杂的 Message。我们用“复杂”这个词,不仅仅是指从个数上说有更多的 fields 或者更多类型的 fields,而是指更加复杂的数据结构:

嵌套 Message

嵌套是一个神奇的概念,一旦拥有嵌套能力,消息的表达能力就会非常强大。

代码清单 4 给出一个嵌套 Message 的例子。

清单 4. 嵌套 Message 的例子
 message Person { 
  required string name = 1; 
  required int32 id = 2;        // Unique ID number for this person. 
  optional string email = 3; 

  enum PhoneType { 
    MOBILE = 0; 
    HOME = 1; 
    WORK = 2; 
  } 

  message PhoneNumber { 
    required string number = 1; 
    optional PhoneType type = 2 [default = HOME]; 
  } 
  repeated PhoneNumber phone = 4; 
 }

在 Message Person 中,定义了嵌套消息 PhoneNumber,并用来定义 Person 消息中的 phone 域。这使得人们可以定义更加复杂的数据结构。

4.1.2 Import Message

在一个 .proto 文件中,还可以用 Import 关键字引入在其他 .proto 文件中定义的消息,这可以称做 Import Message,或者 Dependency Message。

比如下例:

清单 5. 代码
 import common.header; 

 message youMsg{ 
  required common.info_header header = 1; 
  required string youPrivateData = 2; 
 }

其中 ,common.info_header定义在common.header包内。

Import Message 的用处主要在于提供了方便的代码管理机制,类似 C 语言中的头文件。您可以将一些公用的 Message 定义在一个 package 中,然后在别的 .proto 文件中引入该 package,进而使用其中的消息定义。

Google Protocol Buffer 可以很好地支持嵌套 Message 和引入 Message,从而让定义复杂的数据结构的工作变得非常轻松愉快。

动态编译

一般情况下,使用 Protobuf 的人们都会先写好 .proto 文件,再用 Protobuf 编译器生成目标语言所需要的源代码文件。将这些生成的代码和应用程序一起编译。

可是在某且情况下,人们无法预先知道 .proto 文件,他们需要动态处理一些未知的 .proto 文件。比如一个通用的消息转发中间件,它不可能预知需要处理怎样的消息。这需要动态编译 .proto 文件,并使用其中的 Message。

Protobuf 提供了 google::protobuf::compiler 包来完成动态编译的功能。主要的类叫做 importer,定义在 importer.h 中。使用 Importer 非常简单,下图展示了与 Import 和其它几个重要的类的关系。

图 2. Importer 类

图 2. Importer 类Import 类对象中包含三个主要的对象,分别为处理错误的 MultiFileErrorCollector 类,定义 .proto 文件源目录的 SourceTree 类。

下面还是通过实例说明这些类的关系和使用吧。

对于给定的 proto 文件,比如 lm.helloworld.proto,在程序中动态编译它只需要很少的一些代码。如代码清单 6 所示。

清单 6. 代码
 google::protobuf::compiler::MultiFileErrorCollector errorCollector;
 google::protobuf::compiler::DiskSourceTree sourceTree; 

 google::protobuf::compiler::Importer importer(&sourceTree, &errorCollector); 
 sourceTree.MapPath("", protosrc); 

 importer.import(“lm.helloworld.proto”);

首先构造一个 importer 对象。构造函数需要两个入口参数,一个是 source Tree 对象,该对象指定了存放 .proto 文件的源目录。第二个参数是一个 error collector 对象,该对象有一个 AddError 方法,用来处理解析 .proto 文件时遇到的语法错误。

之后,需要动态编译一个 .proto 文件时,只需调用 importer 对象的 import 方法。非常简单。

那么我们如何使用动态编译后的 Message 呢?我们需要首先了解几个其他的类

Package google::protobuf::compiler 中提供了以下几个类,用来表示一个 .proto 文件中定义的 message,以及 Message 中的 field,如图所示。

图 3. 各个 Compiler 类之间的关系

图 3. 各个 Compiler 类之间的关系类 FileDescriptor 表示一个编译后的 .proto 文件;类 Descriptor 对应该文件中的一个 Message;类 FieldDescriptor 描述一个 Message 中的一个具体 Field。

比如编译完 lm.helloworld.proto 之后,可以通过如下代码得到 lm.helloworld.id 的定义:

清单 7. 得到 lm.helloworld.id 的定义的代码
 const protobuf::Descriptor *desc = 
    importer_.pool()->FindMessageTypeByName(“lm.helloworld”); 
 const protobuf::FieldDescriptor* field = 
    desc->pool()->FindFileByName (“id”);

通过 Descriptor,FieldDescriptor 的各种方法和属性,应用程序可以获得各种关于 Message 定义的信息。比如通过 field->name() 得到 field 的名字。这样,您就可以使用一个动态定义的消息了。

编写新的 proto 编译器

随 Google Protocol Buffer 源代码一起发布的编译器 protoc 支持 3 种编程语言:C++,java 和 Python。但使用 Google Protocol Buffer 的 Compiler 包,您可以开发出支持其他语言的新的编译器。

类 CommandLineInterface 封装了 protoc 编译器的前端,包括命令行参数的解析,proto 文件的编译等功能。您所需要做的是实现类 CodeGenerator 的派生类,实现诸如代码生成等后端工作:

程序的大体框架如图所示:

图 4. XML 编译器框图

图 4. XML 编译器框图在 main() 函数内,生成 CommandLineInterface 的对象 cli,调用其 RegisterGenerator() 方法将新语言的后端代码生成器 yourG 对象注册给 cli 对象。然后调用 cli 的 Run() 方法即可。

这样生成的编译器和 protoc 的使用方法相同,接受同样的命令行参数,cli 将对用户输入的 .proto 进行词法语法等分析工作,最终生成一个语法树。该树的结构如图所示。

图 5. 语法树

图 5. 语法树其根节点为一个 FileDescriptor 对象(请参考“动态编译”一节),并作为输入参数被传入 yourG 的 Generator() 方法。在这个方法内,您可以遍历语法树,然后生成对应的您所需要的代码。简单说来,要想实现一个新的 compiler,您只需要写一个 main 函数,和一个实现了方法 Generator() 的派生类即可。

在本文的下载附件中,有一个参考例子,将 .proto 文件编译生成 XML 的 compiler,可以作为参考。

Protobuf 的更多细节

人们一直在强调,同 XML 相比, Protobuf 的主要优点在于性能高。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。

对于这些 “小 3 到 10 倍”,“快 20 到 100 倍”的说法,严肃的程序员需要一个解释。因此在本文的最后,让我们稍微深入 Protobuf 的内部实现吧。

有两项技术保证了采用 Protobuf 的程序能获得相对于 XML 极大的性能提高。

第一点,我们可以考察 Protobuf 序列化后的信息内容。您可以看到 Protocol Buffer 信息的表示非常紧凑,这意味着消息的体积减少,自然需要更少的资源。比如网络上传输的字节数更少,需要的 IO 更少等,从而提高性能。

第二点我们需要理解 Protobuf 封解包的大致过程,从而理解为什么会比 XML 快很多。

Google Protocol Buffer 的 Encoding

Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用的非常巧妙的 Encoding 方法。

考察消息结构之前,让我首先要介绍一个叫做 Varint 的术语。

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。下面就详细介绍一下 Varint。

Varint 中的每个 byte 的最高位 bit 有特殊的含义,如果该位为 1,表示后续的 byte 也是该数字的一部分,如果该位为 0,则结束。其他的 7 个 bit 都用来表示数字。因此小于 128 的数字都可以用一个 byte 表示。大于 128 的数字,比如 300,会用两个字节来表示:1010 1100 0000 0010

下图演示了 Google Protocol Buffer 如何解析两个 bytes。注意到最终计算前将两个 byte 的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用 little-endian 的方式。

图 6. Varint 编码

图 6. Varint 编码消息经过序列化后会成为一个二进制数据流,该流中的数据为一系列的 Key-Value 对。如下图所示:

图 7. Message Buffer

图 7. Message Buffer采用这种 Key-Pair 结构无需使用分隔符来分割不同的 Field。对于可选的 Field,如果消息中不存在该 field,那么在最终的 Message Buffer 中就没有该 field,这些特性都有助于节约消息本身的大小。

以代码清单 1 中的消息为例。假设我们生成如下的一个消息 Test1:

 Test1.id = 10; 
 Test1.str = “hello”;

则最终的 Message Buffer 中有两个 Key-Value 对,一个对应消息中的 id;另一个对应 str。

Key 用来标识具体的 field,在解包的时候,Protocol Buffer 根据 Key 就可以知道相应的 Value 应该对应于消息中的哪一个 field。

Key 的定义如下:

 (field_number << 3) | wire_type

可以看到 Key 由两部分组成。第一部分是 field_number,比如消息 lm.helloworld 中 field id 的 field_number 为 1。第二部分为 wire_type。表示 Value 的传输类型。

Wire Type 可能的类型如下表所示:

表 1. Wire Type
Type Meaning Used For
0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 64-bit fixed64, sfixed64, double
2 Length-delimi string, bytes, embedded messages, packed repeated fields
3 Start group Groups (deprecated)
4 End group Groups (deprecated)
5 32-bit fixed32, sfixed32, float

在我们的例子当中,field id 所采用的数据类型为 int32,因此对应的 wire type 为 0。细心的读者或许会看到在 Type 0 所能表示的数据类型中有 int32 和 sint32 这两个非常类似的数据类型。Google Protocol Buffer 区别它们的主要意图也是为了减少 encoding 后的字节数。

在计算机内,一个负数一般会被表示为一个很大的整数,因为计算机定义负数的符号位为数字的最高位。如果采用 Varint 表示一个负数,那么一定需要 5 个 byte。为此 Google Protocol Buffer 定义了 sint32 这种类型,采用 zigzag 编码。

Zigzag 编码用无符号数来表示有符号数字,正数和负数交错,这就是 zigzag 这个词的含义了。

如图所示:

图 8. ZigZag 编码

图 8. ZigZag 编码使用 zigzag 编码,绝对值小的数字,无论正负都可以采用较少的 byte 来表示,充分利用了 Varint 这种技术。

其他的数据类型,比如字符串等则采用类似数据库中的 varchar 的表示方法,即用一个 varint 表示长度,然后将其余部分紧跟在这个长度部分之后即可。

通过以上对 protobuf Encoding 方法的介绍,想必您也已经发现 protobuf 消息的内容小,适于网络传输。假如您对那些有关技术细节的描述缺乏耐心和兴趣,那么下面这个简单而直观的比较应该能给您更加深刻的印象。

对于代码清单 1 中的消息,用 Protobuf 序列化后的字节序列为:

 08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML,则类似这样:

 31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65 
 6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C 
 6F 77 6F 72 6C 64 3E 

一共 55 个字节,这些奇怪的数字需要稍微解释一下,其含义用 ASCII 表示如下:
 <helloworld> 
    <id>101</id> 
    <name>hello</name> 
 </helloworld>

封解包的速度

首先我们来了解一下 XML 的封解包过程。XML 需要从文件中读取出字符串,再转换为 XML 文档对象结构模型。之后,再从 XML 文档对象结构模型中读取指定节点的字符串,最后再将这个字符串转换成指定类型的变量。这个过程非常复杂,其中将 XML 文件转换为文档对象结构模型的过程通常需要完成词法文法分析等大量消耗 CPU 的复杂计算。

反观 Protobuf,它只需要简单地将一个二进制序列,按照指定的格式读取到 C++ 对应的结构类型中就可以了。从上一节的描述可以看到消息的 decoding 过程也可以通过几个位移操作组成的表达式计算即可完成。速度非常快。

为了说明这并不是我拍脑袋随意想出来的说法,下面让我们简单分析一下 Protobuf 解包的代码流程吧。

以代码清单 3 中的 Reader 为例,该程序首先调用 msg1 的 ParseFromIstream 方法,这个方法解析从文件读入的二进制数据流,并将解析出来的数据赋予 helloworld 类的相应数据成员。

该过程可以用下图表示:

图 9. 解包流程图

图 9. 解包流程图整个解析过程需要 Protobuf 本身的框架代码和由 Protobuf 编译器生成的代码共同完成。Protobuf 提供了基类 Message 以及 Message_lite 作为通用的 Framework,,CodedInputStream 类,WireFormatLite 类等提供了对二进制数据的 decode 功能,从 5.1 节的分析来看,Protobuf 的解码可以通过几个简单的数学运算完成,无需复杂的词法语法分析,因此 ReadTag() 等方法都非常快。 在这个调用路径上的其他类和方法都非常简单,感兴趣的读者可以自行阅读。 相对于 XML 的解析过程,以上的流程图实在是非常简单吧?这也就是 Protobuf 效率高的第二个原因了。

结束语

往往了解越多,人们就会越觉得自己无知。我惶恐地发现自己竟然写了一篇关于序列化的文章,文中必然有许多想当然而自以为是的东西,还希望各位能够去伪存真,更希望真的高手能不吝赐教,给我来信。谢谢。

参考资料

学习

讨论

.NET技术+25台服务器怎样支撑世界第54大网站

英文原文:StackOverflow Update: 560M Pageviews A Month, 25 Servers, And It’s All About Performance

StackOverflow 是一个 IT 技术问答网站,用户可以在网站上提交和回答问题。当下的 StackOverflow 已拥有 400 万个用户,4000 万个回答,月 PV5.6 亿,世界排行第 54。然而值得关注的是,支撑他们网站的全部服务器只有 25 台,并且都保持着非常低的资源使用率,这是一场高有效性、负载均衡、缓存、数据库、搜索及高效代码上的较量。近日,High Scalability 创始人 Todd Hoff 根据 Marco Cecconi 的演讲视频“ The architecture of StackOverflow”以及 Nick Craver 的博文“What it takes to run Stack Overflow”总结了 StackOverflow 的成功原因。

意料之中,也是意料之外,Stack Overflow 仍然重度使用着微软的产品。他们认为既然微软的基础设施可以满足需求,又足够便宜,那么没有什么理由去做根本上的改变。而在需要的地方,他们同样使用了 Linux。究其根本,一切都是为了性能。

另一个值得关注的地方是,Stack Overflow 仍然使用着纵向扩展策略,没有使用云。他们使用了 384GB 的内存和 2TB 的 SSD 来支撑 SQL Servers,如果使用 AWS 的话,花费可想而知。没有使用云的另一个原因是 Stack Overflow 认为云会一定程度上的降低性能,同时也会给优化和排查系统问题增加难度。此外,他们的架构也并不需要横向扩展。峰值期间是横向扩展的杀手级应用场景,然而 他们有着丰富的系统调整经验去应对。该公司仍然坚持着 Jeff Atwood 的名言——硬件永远比程序员便宜。

Marco Ceccon 曾提到,在谈及系统时,有一件事情必须首先弄明白——需要解决问题的类型。首先,从简单方面着手,StackExchange 究竟是用来做什么的——首先是一些主题,然后围绕这些主题建立社区,最后就形成了这个令人敬佩的问答网站。

其次则是规模相关。StackExchange 在飞速增长,需要处理大量的数据传输,那么这些都是如何完成的,特别是只使用了 25 台服务器,下面一起追根揭底:

状态

  • StackExchange 拥有 110 个站点,以每个月 3 到 4 个的速度增长。
  • 400 万用户
  • 800 万问题
  • 4000 万答案
  • 世界排名 54 位
  • 每年增长 100%
  • 月 PV 5.6 亿万
  • 大多数工作日期间峰值为 2600 到 3000 请求每秒,作为一个编程相关网站,一般情况下工作日的请求都会高于周末
  • 25 台服务器
  • SSD 中储存了 2TB 的 SQL 数据
  • 每个 web server 都配置了 2 个 320G 的 SSD,使用 RAID 1
  • 每个 ElasticSearch 主机都配备了 300GB 的机械硬盘,同时也使用了 SSD
  • Stack Overflow 的读写比是 40:60
  • DB Server 的平均 CPU 利用率是 10%
  • 11 个 web server,使用 IIS
  • 2 个负载均衡器,1 个活跃,使用 HAProxy
  • 4 个活跃的数据库节点,使用 MS SQL
  • 3 台实现了 tag engine 的应用程序服务器,所有搜索都通过 tag
  • 3 台服务器通过 ElasticSearch 做搜索
  • 2 台使用了 Redis 的服务器支撑分布式缓存和消息
  • 2 台 Networks(Nexus 5596 + Fabric Extenders)
  • 2 Cisco 5525-X ASAs
  • 2 Cisco 3945 Routers
  • 主要服务 Stack Exchange API 的 2 个只读 SQL Servers
  • VM 用于部署、域控制器、监控、运维数据库等场合

平台

  • ElasticSearch
  • Redis
  • HAProxy
  • MS SQL
  • Opserver
  • TeamCity
  • Jil——Fast .NET JSON Serializer,建立在 Sigil 之上
  • Dapper——微型的 ORM

UI

  • UI 拥有一个信息收件箱,用于新徽章获得、用户发送信息、重大事件发生时的信息收取,使用 WebSockets 实现,并通过 Redis 支撑。
  • 搜索箱通过 ElasticSearch 实现,使用了一个 REST 接口。
  • 因为用户提出问题的频率很高,因此很难显示最新问题,每秒都会有新的问题产生,从而这里需要开发一个关注用户行为模式的算法,只给用户显示感兴趣的问题。它使用了基于 Tag 的复杂查询,这也是开发独立 Tag Engine 的原因。
  • 服务器端模板用于生成页面。

服务器

  • 25 台服务器并没有满载,CPU 使用率并不高,单计算 SO(Stack Overflow)只需要 5 台服务器。
  • 数据库服务器资源利用率在 10% 左右,除下执行备份时。
  • 为什么会这么低?因为数据库服务器足足拥有 384GB 内存,同时 web server 的 CPU 利用率也只有 10%-15%。
  • 纵向扩展还没有遇到瓶颈。通常情况下,如此流量使用横向扩展大约需要 100 到 300 台服务器。
  • 简单的系统。基于 .Net,只用了 9 个项目,其他系统可能需要 100 个。之所以使用这么少系统是为了追求极限的编译速度,这点需要从系统开始时就进行规划,每台服务器的编译时间大约是 10 秒。
  • 11 万行代码,对比流量来说非常少。
  • 使用这种极简的方式主要基于几个原因。首先,不需要太多测试,因为 Meta.stackoverflow 本来就是一个问题和 bug 讨论社区。其次,Meta.stackoverflow 还是一个软件的测试网站,如果用户发现问题的话,往往会提出并给予解决方案。
  • 纽约数据中心使用的是 Windows 2012,已经向 2012 R2 升级(Oregon 已经完成了升级),Linux 系统使用的是 Centos 6.4。

SSD

  • 默认使用的是 Intel 330(Web 层等)
  • Intel 520 用于中间层写入,比如 Elastic Search
  • 数据层使用 Intel 710 和 S3700
  • 系统同时使用了 RAID 1 和 RAID 10(任何4+ 以上的磁盘都使用 RAID 10)。不畏惧故障发生,即使生产环境中使用了上千块 2.5 英寸 SSD,还没碰到过一块失败的情景。每个模型都使用了 1 个以上的备件,多个磁盘发生故障的情景不在考虑之中。
  • ElasticSearch 在 SSD 上表现的异常出色,因为 SO writes/re-indexes 的操作非常频繁。
  • SSD 改变了搜索的使用方式。因为锁的问题,Luncene.net 并不能支撑 SO 的并发负载,因此他们转向了 ElasticSearch。在全 SSD 环境下,并不需要围绕 Binary Reader 建立锁。

高可用性

  • 异地备份——主数据中心位于纽约,备份数据中心在 Oregon。
  • Redis 有两个从节点,SQL 有 2 个备份,Tag Engine 有 3 个节点,elastic 有 3 个节点,冗余一切,并在两个数据中心同时存在。
  • Nginx 是用于 SSL,终止 SSL 时转换使用 HAProxy。
  • 并不是主从所有,一些临时的数据只会放到缓存中
  • 所有 HTTP 流量发送只占总流量的 77%,还存在 Oregon 数据中心的备份及一些其他的 VPN 流量。这些流量主要由 SQL 和 Redis 备份产生。

数据库

  • MS SQL Server
  • Stack Exchange 为每个网站都设置了数据库,因此 Stack Overflow 有一个、Server Fault 有一个,以此类推。
  • 在纽约的主数据中心,每个集群通常都使用 1 主和 1 只读备份的配置,同时还会在 Oregon 数据中心也设置一个备份。如果是运行的是 Oregon 集群,那么两个在纽约数据中心的备份都会是只读和同步的。
  • 为其他内容准备的数据库。这里还存在一个“网络范围”的数据库,用于储存登陆凭证和聚合数据(大部分是 stackexchange.com 用户文件或者 API)。
  • Careers Stack Overflow、stackexchange.com 和 Area 51 等都拥有自己独立的数据库模式。
  • 模式的变化需要同时提供给所有站点的数据库,它们需要向下兼容,举个例子,如果需要重命名一个列,那么将非常麻烦,这里需要进行多个操作:增加一个新列,添加作用在两个列上的代码,给新列写数据,改变代码让新列有效,移除旧列。
  • 并不需要分片,所有事情通过索引来解决,而且数据体积也没那么大。如果有 filtered indexes 需求,那么为什么不更高效的进行?常见模式只在 DeletionDate = Null 上做索引,其他则通过为枚举指定类型。每项 votes 都设置了 1 个表,比如一张表给 post votes,1 张表给 comment votes。大部分的页面都可以实时渲染,只为匿名用户缓存,因此,不存在缓存更新,只有重查询。
  • Scores 是非规范化的,因此需要经常查询。它只包含 IDs 和 dates,post votes 表格当下大约有 56454478 行,使用索引,大部分的查询都可以在数毫秒内完成。
  • Tag Engine 是完全独立的,这就意味着核心功能并不依赖任何外部应用程序。它是一个巨大的内存结构数组结构,专为 SO 用例优化,并为重负载组合进行预计算。Tag Engine 是个简单的 windows 服务,冗余的运行在多个主机上。CPU 使用率基本上保持在2-5%,3 个主机专门用于冗余,不负责任何负载。如果所有主机同时发生故障,网络服务器将把 Tag Engine 加载到内存中持续运行。
  • 关于 Dapper 无编译器校验查询与传统 ORM 的对比。使用编译器有很多好处,但在运行时仍然会存在 fundamental disconnect 问题。同时更重要的是,由于生成 nasty SQL,通常情况还需要去寻找原始代码,而 Query Hint 和 parameterization 控制等能力的缺乏更让查询优化变得复杂。

编码

  • 流程
  • 大部分程序员都是远程工作,自己选择编码地点
  • 编译非常快
  • 然后运行少量的测试
  • 一旦编译成功,代码即转移至开发交付准备服务器
  • 通过功能开关隐藏新功能
  • 在相同硬件上作为其他站点测试运行
  • 然后转移至 Meta.stackoverflow 测试,每天有上千个程序员在使用,一个很好的测试环境
  • 如果通过则上线,在更广大的社区进行测试
  • 大量使用静态类和方法,为了更简单及更好的性能
  • 编码过程非常简单,因为复杂的部分被打包到库里,这些库被开源和维护。.Net 项目数量很低,因为使用了社区共享的部分代码。
  • 开发者同时使用 2 到 3 个显示器,多个屏幕可以显著提高生产效率。

缓存

  • 缓存一切
  • 5 个等级的缓存
  • 1 级是网络级缓存,缓存在浏览器、CDN 以及代理服务器中。
  • 2 级由 .Net 框架 HttpRuntime.Cache 完成,在每台服务器的内存中。
  • 3 级 Redis,分布式内存键值存储,在多个支撑同一个站点的服务器上共享缓存项。
  • 4 级 SQL Server Cache,整个数据库,所有数据都被放到内存中。
  • 5 级 SSD。通常只在 SQL Server 预热后才生效。
  • 举个例子,每个帮助页面都进行了缓存,访问一个页面的代码非常简单:
  • 使用了静态的方法和类。从 OOP 角度来看确实很糟,但是非常快并有利于简洁编码。
  • 缓存由 Redis 和 Dapper 支撑,一个微型 ORM
  • 为了解决垃圾收集问题,模板中 1 个类只使用 1 个副本,被建立和保存在缓存中。监测一切,包括 GC 操。据统计显示,间接层增加 GC 压力达到了某个程度时会显著的降低性能。
  • CDN Hit 。鉴于查询字符串基于文件内容进行哈希,只在有新建立时才会被再次取出。每天 3000 万到 5000 万 Hit,带宽大约为 300GB 到 600GB。
  • CDN 不是用来应对 CPU 或I/O负载,而是帮助用户更快的获得答案

部署

  • 每天 5 次部署,不去建立过大的应用。主要因为
  • 可以直接的监视性能
  • 尽可能最小化建立,可以工作才是重点
  • 产品建立后再通过强大的脚本拷贝到各个网页层,每个服务器的步骤是:
  • 通过 POST 通知 HAProxy 下架某台服务器
  • 延迟 IIS 结束现有请求(大约 5 秒)
  • 停止网站(通过同一个 PSSession 结束所有下游)
  • Robocopy 文件
  • 开启网站
  • 通过另一个 POST 做 HAProxy Re-enable
  • 几乎所有部署都是通过 puppet 或 DSC,升级通常只是大幅度调整 RAID 阵列并通过 PXE boot 安装,这样做非常快速。

协作

  • 团队
  • SRE (System Reliability Engineering):5 人
  • Core Dev(Q&A site)6-7 人
  • Core Dev Mobile:6 人
  • Careers 团队专门负责 SO Careers 产品开发:7 人
  • Devops 和开发者结合的非常紧密
  • 团队间变化很大
  • 大部分员工远程工作
  • 办公室主要用于销售,Denver 和 London 除外
  • 一切平等,些许偏向纽约工作者,因为面对面有助于工作交流,但是在线工作影响也并不大
  • 对比可以在同一个办公室办公,他们更偏向热爱产品及有才华的工程师,他们可以很好的衡量利弊
  • 许多人因为家庭而选择远程工作,纽约是不错,但是生活并不宽松
  • 办公室设立在曼哈顿,那是个人才的诞生地。数据中心不能太偏,因为经常会涉及升级
  • 打造一个强大团队,偏爱极客。早期的微软就聚集了大量极客,因此他们征服了整个世界
  • Stack Overflow 社区也是个招聘的地点,他们在那寻找热爱编码、乐于助人及热爱交流的人才。

编制预算

  • 预算是项目的基础。钱只花在为新项目建立基础设施上,如此低利用率的 web server 还是 3 年前数据中心建立时购入。

测试

  • 快速迭代和遗弃
  • 许多测试都是发布队伍完成的。开发拥有一个同样的 SQL 服务器,并且运行在相同的 Web 层,因此性能测试并不会糟糕。
  • 非常少的测试。Stack Overflow 并没有进行太多的单元测试,因为他们使用了大量的静态代码,还有一个非常活跃的社区。
  • 基础设施改变。鉴于所有东西都有双份,所以每个旧配置都有备份,并使用了一个快速故障恢复机制。比如,keepalived 可以在负载均衡器中快速回退。
  • 对比定期维护,他们更愿意依赖冗余系统。SQL 备份用一个专门的服务器进行测试,只为了可以重存储。计划做每两个月一次的全数据中心故障恢复,或者使用完全只读的第二数据中心。
  • 每次新功能发布都做单元测试、集成测试盒 UI 测试,这就意味着可以预知输入的产品功能测试后就会推送到孵化网站,即 meta.stackexchange(原 meta.stackoverflow)。

监视/日志

  • 当下正在考虑使用 http://logstash.net/做日志管理,目前使用了一个专门的服务将 syslog UDP 传输到 SQL 数据库中。网页中为计时添加 header,这样就可以通过 HAProxy 来捕获并且融合到 syslog 传输中。
  • Opserver 和 Realog 用于显示测量结果。Realog 是一个日志展示系统,由 Kyle Brandt 和 Matt Jibson 使用 Go 建立。
  • 日志通过 HAProxy 负载均衡器借助 syslog 完成,而不是 IIS,因为其功能比 IIS 更丰富。

关于云

  • 还是老生常谈,硬件永远比开发者和有效率的代码便宜。基于木桶效应,速度肯定受限于某个短板,现有的云服务基本上都存在容量和性能限制。
  • 如果从开始就使用云来建设 SO 说不定也会达到现在的水准。但毫无疑问的是,如果达到同样的性能,使用云的成本将远远高于自建数据中心。

性能至上

  • StackOverflow 是个重度的性能控,主页加载的时间永远控制在 50 毫秒内,当下的响应时间是 28 毫秒。
  • 程序员热衷于降低页面加载时间以及提高用户体验。
  • 每个独立的网络提交都予以计时和记录,这种计量可以弄清楚提升性能需要修改的地方。
  • 如此低资源利用率的主要原因就是高效的代码。web server 的 CPU 平均利用率在5% 到 15% 之间,内存使用为 15.5 GB,网络传输在 20 Mb/s到 40 Mb/s。SQL 服务器的 CPU 使用率在5% 到 10% 之间,内存使用是 365GB,网络传输为 100 Mb/s到 200 Mb/s。这可以带来 3 个好处:给升级留下很大的空间;在严重错误发生时可以保持服务可用;在需要时可以快速回档。

学到的知识

1. 为什么使用 MS 产品的同时还使用 Redis?什么好用用什么,不要做无必要的系统之争,比如 C# 在 Windows 机器上运行最好,我们使用 IIS;Redis 在*nix 机器上可以得到充分发挥,我们使用*nix。

2. Overkill 即策略。平常的利用率并不能代表什么,当某些特定的事情发生时,比如备份、重建等完全可以将资源使用拉满。

3. 坚固的 SSD。所有数据库都建立在 SSD 之上,这样可以获得 0 延时。

4. 了解你的读写负载。

5. 高效的代码意味着更少的主机。只有新项目上线时才会因为特殊需求增加硬件,通常情况下是添加内存,但在此之外,高效的代码就意味着 0 硬件添加。所以经常只讨论两个问题:为存储增加新的 SSD;为新项目增加硬件。

6. 不要害怕定制化。SO 在 Tag 上使用复杂查询,因此专门开发了所需的 Tag Engine。

7. 只做必须做的事情。之所以不需要测试是因为有一个活跃的社区支撑,比如,开发者不用担心出现“Square Wheel”效应,如果开发者可以制作一个更更轻量级的组件,那就替代吧。

8. 注重硬件知识,比如 IL。一些代码使用 IL 而不是C#。聚焦 SQL 查询计划。使用 web server 的内存转储究竟做了些什么。探索,比如为什么一个 split 会产生 2GB 的垃圾。

9. 切勿官僚作风。总有一些新的工具是你需要的,比如,一个编辑器,新版本的 Visual Studio,降低提升过程中的一切阻力。

10. 垃圾回收驱动编程。SO 在减少垃圾回收成本上做了很多努力,跳过类似 TDD 的实践,避免抽象层,使用静态方法。虽然极端,但是确实打造出非常高效的代码。

11. 高效代码的价值远远超出你想象,它可以让硬件跑的更快,降低资源使用,切记让代码更容易被程序员理解。

使用MiniProfiler调试ASP.NET MVC网站性能

MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC的性能分析的小程序。可以对一个页面本身,及该页面通过直接引用、Ajax、Iframe形式访问的其它页面进行监控,监控内容包括数据库内容,并可以显示数据库访问的SQL(支持EF、EF CodeFirst等 )。并且以很友好的方式展现在页面上。

该Profiler的一个特别有用的功能是它与数据库框架的集成。除了.NET原生的 DbConnection类,profiler还内置了对实体框架(Entity Framework)以及LINQ to SQL的支持。任何执行的Step都会包括当时查询的次数和所花费的时间。为了检测常见的错误,如N+1反模式,profiler将检测仅有参数值存在差 异的多个查询。

MiniProfiler是以Apache License V2.0协议发布的,你可以在NuGet找到。配置及使用可以看这里:http://code.google.com/p/mvc-mini-profiler

为建立快速的网站黄金参考标准,雅虎2007年为网站提高速度的13个简易规则

mvcminiprofiler

Stack Overflow 用MVC Mini Profiler来促进开源,而在把每一页的右上角服务器渲染时间的简单行来迫使我们解决我们所有的性能衰退和遗漏。如果你在使用.NET开发应用,一定要使用上这个工具。

包括以下核心组件:

  • MiniProfiler
  • MiniProfiler.EntityFramework

如何安装?

一、环境准备
  • Visual Studio 2010
  • ASP.NET MVC项目
  • 如果需要调试EF,建议升级到Entity Framework 4.2
二、安装

推荐使用NuGet方式进行安装,参考文章《使用 NuGet 管理项目库

  • 第一步:在引用上右键选择“Manage NuGet Packages”
  • 第二步:Online搜索miniprofiler

image

MiniProfiler、MiniProfiler.EF、MiniProfiler.MVC3,同时会自动安装依赖组件:WebActivator, 同时也会自动在项目里面添加代码文件:MiniProfiler.cs

  • 第三步:修改代码使MiniProfiler生效

在global.cs的Application_Start事件里面增加代码: StackExchange.Profiling.MiniProfilerEF.Initialize();
修改View的layout文件,在head区域增加如下代码:@StackExchange.Profiling.MiniProfiler.RenderIncludes()

 

如果安装步骤一切顺利的话,打开站点的时候,就可以在左上角看到页面执行时间了,点开可以看到更详细的信息,如果有SQL的话,还会显示SQL语句信息,非常的方便。 页面上如果有ajax请求,也会同时显示到左上角。如果左上角显示红色提示,则表示可能存在性能问题需要处理:

image

点开SQL部分,可以看到详细的SQL语句

image

标记为duplicate的部分,代表在一次请求当中,重复执行了查询,可以优化。

问题:

1、在结合使用EF 4.3的时候发生如下错误:

Invalid object name ‘dbo.__MigrationHistory’.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at MvcMiniProfiler.Data.ProfiledDbCommand.ExecuteDbDataReader(CommandBehavior behavior) in \mvc-mini-profiler\MvcMiniProfiler\Data\ProfiledDbCommand.cs:line 155
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)

需要在EF 4.3上关闭数据库初始化策略:

public class SettingContext : DbContext
{
static SettingContext()
{
Database.SetInitializer<SettingContext>(null);
}

A simple but effective mini-profiler for .NET and Ruby.

A simple but effective mini-profiler for .NET, Ruby, Go and Node.js.

Where do I get support?

Bugs, Feature Requests, Discussion and KB live at community.miniprofiler.com or on Stack Overflow. View Releases and feel free to submit Pull Requests and Issues on GitHub.

Where is the current Ruby documentation?

See Github and Sams blog post.

What does it profile?

MiniProfiler does not attach itself to every single method call; that would be too invasive and wouldn’t focus on the biggest performance issues. Instead, it provides:

  • An ADO.NET profiler, capable of profiling calls on raw ADO.NET (SQL Server, Oracle, etc), LINQ-to-SQL, EF (including Code First), Lightspeed and a range of other data access scenarios
  • A pragmatic Step instrumentation that you can add to code you want to explicitly profile

Simple. Fast. Pragmatic. Useful.

Getting Started on .Net:

Hook up your application with at least one of the following packages are available on nuget:

PM> Install-Package MiniProfiler

Write out our css and javascript includes just before closing the body tag:

@using StackExchange.Profiling;
<head>
 ..
</head>
<body>
  ...
  @MiniProfiler.RenderIncludes()
</body>

For requests that aren’t being profiled, nothing will be written.

  • Decide when you want to profile; perhaps for local requests, perhaps for special users (developers, admins, test team, whatever).

The sample project starts profiling in Global.asax.cs Application_BeginRequest:

using StackExchange.Profiling;
...    
protected void Application_BeginRequest()
{
    if (Request.IsLocal)
    {
        MiniProfiler.Start();
    } 
}

For the code you want to profile, use (regardless of whether the profiler is null or not):

using StackExchange.Profiling;
...
var profiler = MiniProfiler.Current; // it's ok if this is null
using (profiler.Step("Set page title"))
{
	ViewBag.Title = "Home Page";
}
using (profiler.Step("Doing complex stuff"))
{
	using (profiler.Step("Step A"))
	{ // something more interesting here
		Thread.Sleep(100);
	}
	using (profiler.Step("Step B"))
	{ // and here
		Thread.Sleep(250);
	}
}

You can make this as granular or high-level as you like; passing a MiniProfiler in as an optional argument (defaulting to null) to downstream methods works well.

Stop the profiler; the sample project does this in its Global.asax.cs:

protected void Application_EndRequest()
{
    MiniProfiler.Stop();
}

If all goes well, you’ll see something like this:

demo

Profiler Security

Accessing last profiled request

Profiling information can be viewed for the last profiled request by navigating to

~/mini-profiler-resources/results

This resource is accessible to all by default, so you should assign a predicate delegate toMiniProfiler.Settings.Results_Authorize to restrict access. A good place to do this is inGlobal.asax.cs:

protected void Application_Start(object sender, EventArgs e) 
{
   MiniProfiler.Settings.Results_Authorize = IsUserAllowedToSeeMiniProfilerUI;
}
private bool IsUserAllowedToSeeMiniProfilerUI(HttpRequest httpRequest)
{
    // Implement your own logic for who 
    // should be able to access ~/mini-profiler-resources/results
    var principal = httpRequest.RequestContext.HttpContext.User;
    return principal.IsInRole("Developer");   
}

Accessing last 100 profiled requests

Similarly, profiling information for the last 100 profiled requests can be viewed at

~/mini-profiler-resources/results-index

Access to this resource is disabled by default but this can be changed by assigning a predicate delegate to MiniProfiler.Settings.Results_List_Authorize:

protected void Application_Start(object sender, EventArgs e) 
{
   MiniProfiler.Settings.Results_List_Authorize = IsUserAllowedToSeeMiniProfilerUI;
}
private bool IsUserAllowedToSeeMiniProfilerUI(HttpRequest httpRequest)
{
    // Implement your own logic for who 
    // should be able to access ~/mini-profiler-resources/results-index
    var principal = httpRequest.RequestContext.HttpContext.User;
    return principal.IsInRole("Developer");   
}

In using these profiling resources, it is possible to profile requests for any/all users but restrict access to viewing the results only to certain users.

Database profiling

The profiler includes powerful and comprehensive database profiling capabilities. To enable wrap your database connection with a profiling connection.

The built in database profiler supports any kind of DbConnection. It also supports Entity Framework and Linq-2-SQL.

Usage:

Use a factory to return your connection:

public static DbConnection GetOpenConnection()
{
    var cnn = CreateRealConnection(); // A SqlConnection, SqliteConnection ... or whatever

    // wrap the connection with a profiling connection that tracks timings 
    return new StackExchange.Profiling.Data.ProfiledDbConnection(cnn, MiniProfiler.Current);
}

Various features

(N+1) and duplicate query detection===

The profiler is able to detect and highlight areas where you are executing the same query multiple times with different parameters. This allows you to quickly find queries you may be able to batch.

N+1

What about AJAX?

The profiler is also able to log all ajax calls:

ajax calls

You will get one profile timing per ajax call.

Abandoning a profiler session

Often at the beginning of a session you may not know if a user should or should not be allowed to profile, a common pattern for dealing with such issues is:

protected void Application_BeginRequest()
{
   MvcMiniProfiler.MiniProfiler.Start();  
}
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
  if(!CurrentUserIsAllowedToSeeProfiler())
  {
	MvcMiniProfiler.MiniProfiler.Stop(discardResults: true);
  }
}

See the Global.asax.cs file in the Sample.MVC project for a demonstration of more config options that can be used.

Basic Troubleshooting

MiniProfiler isn’t showing

  • Run through the Getting Started steps again and double-check
  • If your web.config has the setting runAllManagedModulesForAllRequests=false then add the following to your web.config too:
<system.webServer>
  ...
  <handlers>
    <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
  </handlers>
</system.webServer>

Where is MiniProfiler used?

MiniProfiler was designed by the team at Stack Overflow. It is in production use there and on the Stack Exchange family of sites.

Also used at:

(if you are using this in production please let us know and we will add you to the list)

Frequently Asked Questions

If you have other questions, please ask them on Stack Overflow or community.miniprofiler.com.

Dapper使用笔记

基本操作

1,执行查询映射到一个强类型的列表

public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

2,映射到动态类型的列表

public static IEnumerable<dynamic> Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true)

3,返回受影响行数

public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null)

4,执行多次

connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)",

new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } }

)

5,public static IDataReader ExecuteReader

6,public static T ExecuteScalar

参数化

1,DynamicParameters 集合

创建集合对象,通过Add添加项,然后传递给调用方法

var p = new DynamicParameters();

p.Add("@PriceIds", "123", DbType.String);

输出参数和返回值

var p = new DynamicParameters();

p.Add("@a", 11);

p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);

p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);

cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); 

int b = p.Get<int>("@b");

int c = p.Get<int>("@c"); 

2,匿名类型

connection.Query<Dog>("select Age = @Age, Id = @Id", new { Age = 1, Id = 2 });

3,列表:

connection.Query<int>("select id from X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 });

高级

1,映射多个对象

public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(

this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null

)

还有其它更多对象的重载

示例:

var sql = 

@"select * from #Posts p 

left join #Users u on u.Id = p.OwnerId 

Order by p.Id";

var data = connection.Query<Post, User, Post>(sql, (post, user) => { post.Owner = user; return post;});

2,返回多个结果

public static GridReader QueryMultiple(

this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null

)

示例:

var sql = 

@"

select * from Customers where CustomerId = @id

select * from Orders where CustomerId = @id

select * from Returns where CustomerId = @id";

using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))

{

var customer = multi.Read<Customer>().Single();

var orders = multi.Read<Order>().ToList();

var returns = multi.Read<Return>().ToList();

} 

存储过程

cnn.Query<User>("spGetUser", new {Id = 1}, commandType: CommandType.StoredProcedure);

使用免费工具WCAT为Web应用程序进行压力测试

原文地址:http://blogs.msdn.com/alikl/archive/2008/03/09/stress-test-asp-net-web-application-with-free-wcat-tool.aspx

步骤:
1.安装WCAT
2.创建配置文件
3.运行测试
4.检查结果

安装WCAT
下载并安装Internet Information Services (IIS) 6.0 Resource Kit Tools,其中包含WCAT。

创建配置文件
有三个文本文件需要创建和配置:
1.script.txt
此文件定义了请求,换句话说就是请求哪些页面和如何请求。下面是一个简单的script.txt示例:

NEW TRANSACTION
classId = 1
NEW REQUEST HTTP
Verb = “GET”
URL = “http:// localhost / BankingShmanking / Default.aspx”

2.distribution.txt
定义了不同请求的权重。例如,我需要我需要在1.aspx上生成2倍于2.aspx的请求,我就会在 此文件中进行描述。只请求一个页面的时候,这个文件是没什么作用的。如下是一个distribution.txt文件的示例(1代表设置classId为 1的请求,50代表50%的请求被分配到了以此classId为标识的请求上):
1 50
3.config.txt
定义了测试间隔和生成多少客户端进行请求Web应用程序,下面是一个config.txt文件的示例:
Warmuptime 5s
Duration 30s
CooldownTime 5s
NumClientMachines 1
NumClientThreads 20

这些文件保存于”C:\Program Files\IIS Resources\WCAT Controller”文件夹。

运行测试

在命令行模式下,进入到”C:\Program Files\IIS Resources\WCAT Controller”文件夹,执行如下命令:
wcctl -c config.txt -d distribution.txt -s script.txt -a localhost
再打开令一个命令行窗口,到C:\Program Files\IIS Resources\WCAT Client文件夹下,执行命令:
wcclient.exe localhost

检查结果
结果会直接显示到屏幕上:


同时在C:\Program Files\IIS Resources\WCAT Controller目录下生成日志文件。

使用WCAT对你的Asp.net进行测试

WCAT – Simple Performance Test Tool for your .NET web app

By 16 Sep 2011

WCAT (Web Capacity Analysis Tool) is a tiny but excellent tool from Microsoft to perform load test your web application on IIS.  This tool enables you to do performance analysis on various scenarios of your web application.  All “perfmon” performance counters (like processor time, private bytes usage,  disk queue length,  total bytes sent or received in network) can be specified for the performance testing.  A performance test is for a business scenario, hence a test needs to include all servers (application server, database server) involved in the business scenario.  This is tool is as handy as possible to perform a load test even on single machine.

Once you specify the scenario in your application (i.e. appropriate URLs), a number of virtual clients from various client machines  will visit the URL with appropriate request data.  Like other performance tools, the following players are involved in a performance testing:

  • Server – machines on which your web application components are running
  • Client – A virtual client on a machine which acts as end-user to visit the URL
  • Controller – This coordinates a test among the virtual clients on various machines.  It also capture and collate the performance counters from appropriate servers.

I’ve created a small ASP.NET MVC3 application with following actions:
* http://localhost/myapp/load/create – to insert a simple “comment” field  value into the database (using POST data COMMENT =  “<value>”)
* http://localhost/myapp/load – to lists all comments

WCAT uses Windows Scripting and you can use JavaScript syntax to define the scenario for your test.  Below is a sample scenario in a file scenario.ubr:

scenario
 {
 name    = "IIS Home Page";

warmup      = 30;
 duration    = 50;

default
 {
 // send keep-alive header
 setheader
 {
 name    = "Connection";
 value   = "keep-alive";
 }

// set the host header
 setheader
 {
 name    = "Host";
 value   = server();
 }

// HTTP1.1 request
 version     = HTTP11;

// keep the connection alive after the request
 close       = ka;
 }

//
 // This script is made for IIS7
 //
 transaction
 {
 id = "My App Load Test";
 weight = 1;

request
 {
 url = "/myapp/load/create";
 verb = POST;
 postdata = "Comment=Tested";
 statuscode= 200;
 }

request
 {
 url         = "/MyBooks/load";
 statuscode  = 200;
 }

close
 {
 method      = reset;
 }
 }
 }

A scenario file normally contains warmup time (ramp up), test duration, cooldown time (ramp down).  The default section enables you to specify default HTTP headers for the test.  The transaction section is used to specify the actual business scenarios.  The weight property is used to set the priority of this transaction.  The request section is used to specify individual page request in the transaction.  A “request” contains

  • URL of the page
  • Optionally the HTTP verb (default GET).  In case of posting data, you have to specify the POST as verb.
  • POSTDATA for POST
  • Status code which is normally 200, but for some cases you may need to specify 300 or 302 for moving request

The other test details like servers and client machine names,  number of virtual clients, performance counters can be specified to WCAT through another script file called “setting file”.  Below is a sample setting file named “setting.ubr”:

settings
 {
 clientfile     = "scenario.ubr";
 server         = "localhost";
 clients        = 2;
 virtualclients = 10;

counters
 {
 interval = 10;

counter = "Processor(_Total)\\% Processor Time";
 counter = "Processor(_Total)\\% Privileged Time";
 counter = "Processor(_Total)\\% User Time";
 counter = "Processor(_Total)\\Interrupts/sec";

counter = "Memory\\Available KBytes";

counter = "Process(w3wp)\\Working Set";

counter = "System\\Context Switches/sec";
 counter = "System\\System Calls/sec";

counter = "Web Service(_Total)\\Bytes Received/sec" ;
 counter = "Web Service(_Total)\\Bytes Sent/sec" ;
 counter = "Web Service(_Total)\\Connection Attempts/sec" ;
 counter = "Web Service(_Total)\\Get Requests/sec" ;
 }

// other settings
 }

A setting file usually contains the following:

  • The scenario file the clients need to execute
  • Server machines
  • Number of physical client machines for this test
  • Number of virtual clients for this test
  • Performance counters
  • Registry settings

Note that you can use any file extension for scenario and setting files.  I have followed the convention”ubr” is used in WCAT sample.

Initial Setup

  1. Download the tool from http://snip.udooz.net/wcat63.   It has x64 version also.
  2. Add the installed folder path into system PATH.

This setup has following three exeutables and one Windows script file along with documentation and samples folders:

  • wcctl.exe – controller
  • wcclient.exe – client
  • wcutil.exe – small utility to view a brief report of a test
  • wcat.wsf – used to update, terminate and run wcclient on various client machines.

Initially, you need to update WCAT setup on all client machines by

 wcat.wsf –terminate –update –clients {comma separated list of WCAT client machines}

Steps for the Testing

To perform a test, you need a machine for “controller”, one or more machines for “clients” and the server machines.  You can do the testing even within a single machine by specifying “localhost”.

  1. Once you prepare the scenario and setting files, create a folder  in the controller machine for your test and copy these files on to it.
  2. Open the command prompt as Administrator.
  3. Run wcctl -t scenario.ubr -f settings.ubr -x on the controller machine.  The output will be like

4. Run wcclient.exe on all client machines.

After the test run, the output in the controller command prompt would be like

After all the test run completed, result will be stored in log.xml file in the controller’s  machine current directory.  WCAT has a XSLT “report.xsl” to transform this XML into readable in its installed folder.  Copy the file into log.xml.  Open the log.xml in the browser to see the output.  A part of log.xml file is

<?xml version='1.0'?>
 <?xml-stylesheet type='text/xsl' href='report.xsl'?>
 <report name="wcat" version="6.4.0" level="1" top="100">
 <section name="header" key="90000">
 ...

<item>
 <data name="counter" >\Processor(_Total)\% Processor Time</data>
 <data name="avg" >1.82</data>
 <data name="min" >1.82</data>
 <data name="max" >1.82</data>
 <data name="delta" >0.00</data>
 <item>
 <data name="time" >0.00</data>
 <data name="current" >1.82</data>
 </item>
 </item>
 <item>
 <data name="counter" >\Process(w3wp)\Private Bytes</data>
 <data name="avg" >98455552.00</data>
 <data name="min" >98455552.00</data>
 <data name="max" >98455552.00</data>
 <data name="delta" >0.00</data>
 <item>
 <data name="time" >0.00</data>
 <data name="current" >98455552.00</data>
 </item>
 </item>
 </table>

...
 </report>

Final Words

WCAT might not help to test an entire application which normally has so many user interaction which are not simply captured by single POST request.  However, it would help to perform load testing on every atomic part of your application or to perform technology evaluation as part of prototype engineering.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License