使用JMeter进行压力测试

JMeter是一款优秀的开源压力测试工具。网上有不少介绍的文章,比如DeveloperWorks上有一篇。不过脚本的制作比较麻烦,自己手动输入参数比较不实际。cnblog上有一测试达人在这里介绍了一个免费工具Badboy,可以录制在IE上的操作并导出JMeter脚本。Badboy本身是一个Web自动化测试工具。不足的是,一些Badboy录制的动作JMeter并不支持,比如多窗口操作。在导出jmx文件的时候Badboy会提示不能导出的内容。Badboy也有一些不够智能的地方,比如写死了服务器地址,而不是创建一个默认的HTTP访问器。
一开始使用JMeter进行压力测试完全是冲着JMeter的Cookie Manager功能去的。如果使用简单的工具,比如apache自带的ab工具,则要想方设法绕过应用程序的登录机制。使用Cookie Manager虽然不需要配置,但要注意的是,要把发来初始cookie的地址给包括进来。我测试的系统在/login.do这里发放cookie,但我直接访问/acegi_login登录,结果就丢掉了cookie,导致后续步骤失败。

你出生日的照片

在百合十大上看到的,挺有意思的。转载如下:

这个人——
  从1979到1997。18年。pola相机。每天一张照片。
  第一张是1979年3月31日,最后一张是1997年10月25日。后头的就没了。
  因为,1997年10月25日那天,拍照片那人死在病床上。
    他的朋友把他生前拍的照片整理了出来,放在网上。
  网址http://photooftheday.hughcrawford.com
    18年都在里面。

下面这张是我出生日的照片,居然是比萨斜塔:

国家地理的新节目:Megastructures/Megafactories

    虽然标题上说是新节目,不过估计也开播一两年了。主要都是从emule上拖的,地址在这里。主要介绍了一些大型工程产品的制作过程。对engineering有爱的同学可以看看。最近看了以下几集:

    M1A2埃布拉姆斯坦克。M1现在已经不生产了,纪录片里拍的是M1的翻新和改进过程,比如把M1A1翻新成M1A2。令人印象深刻的莫过于重量,特别是M1厚重的装甲。另一个让人感兴趣的东西是库存的零件,放在一个巨大的仓库里,所有的零件的存取都通过机器人操作。

    Apache直升机。感觉阿帕奇的生存设计就是冗余。两台发动机,一台1800马力。两个电子吊舱,两套配线,典型一个有重生技能的牛头人。出乎意料的是,阿帕奇的生产装配是全手工的,几乎没有引入机器人进行装配,还有就是邮箱居然是橡胶的。

    弗吉尼亚级攻击型核潜艇。美国海军的当家宝贝,藏着掖着没说啥具体的东西。主要就是一些训练的内容和一笔带过的制造工艺。

    C-5银河运输机。C-5的制造也是很早以前的事了,大概在上世纪80年代,所以纪录片讲述的主要是C-5的使用和保养。虽然C-5已经很大了,最大起飞重量有近400吨,可是它上面还有老毛子的近乎变态的An-124和An-225。这一集拍的是一架C-5搭载M1的强化装甲去伊拉克的故事,和M1坦克那集倒是有呼应。夜晚空中加油那段拍得很不错。

    卡特彼勒巨型卡车。大家经常看到在施工场所里的重型设备上的CAT,指的就是卡特彼勒毛毛虫(CATerpiller)。直到这次金融危机前,我还一直以为CAT是某个日本的重工集团,类似三菱或者川崎。卡特彼勒本来是生产拖拉机的,由于在田野里的样子像毛毛虫,这个名字就保留了下来。这集里说的巨型卡车,就像大家在电视上看到三峡大坝施工场地里跑的那种巨型装卸卡车,车轮子就有两人高,整个车大概有3层楼高。这么大型的车辆,由于无法进行整车运送,所以都是把装配好的主要大件,比如车架、轮子、驾驶室、装卸斗用火车分别运到施工场地(片子里是加拿大的油砂产地),再进行组装。全车150吨,马力惊人。制造过程中使用了大量的自动焊接,牛B的是,焊接处的结构强度比铸造部分的结构强度还要大。。。

    UPS高速递送。只介绍了UPS美国国内的业务。所有的邮件,除了目的地是本地附近地区的以外,全都空运送到美国中部Kentucky的一个镇上进行redistribution(不知道我有没有理解对,不过片子里似乎就是这样的)。邮件分为大中小三种类型进行分拣,大量的人工+更大量的自动化分拣。不知国内的那些快递公司,包括EMS,何时能达到如此业务水平。

    宝马Z4跑车。主要讲了Z4的组装过程。Z4的发动机居然是用铝和镁组成的,气缸内壁用铝,外面用镁,整个V6的发动机气缸只有30公斤。为了减重,MS进气道用的是塑料。

SHL

    今天去考了ACCA那个比赛的初试,考了一次SHL玩玩。算是我第三次正式考SHL了吧,第一次是去年的ACCA比赛,第二次是年初的UBS,都挂了。囧

    不过更囧的是今天去考试的路上。考试在南审,我差不多算好时间骑车出门,没想到到清凉门的时候前轮的挡泥板居然由于年久失修,掉下来了。要是全部掉下来就算了,我直接摔垃圾桶走人,只掉了一个螺丝,结果导致挡泥板直接卡在了前轮上,不能骑了。前面离南审还有1公里多的路,还有10多分钟就要进场了,又没公交车直达,而且我还不知道考试地点的具体位置(只知道教室门牌和楼宇的大概位置)。。。还好急中生智硬是把挡泥板拗弯,架在以前车篮的残骸上,居然又能骑了。

    仍然提前赶到考场(还好提前留了10分钟),入场,顺利答完题目。SHL这个东东的确是能够训练的考试,去年这个时候考这个的时候,我只做了大概18题不到;今天之前做了点准备,在考官喊停笔前半分钟正好做完。看到周围有很多人没做完,想起了去年这个时候的我,信心满满去考试却被打击得不行。

    下半年找工作,应该还会碰上一些SHL。不过IT的笔试更主要还是考专业上的知识,算法、数据结构还有语言吧。

   p.s. 要是过了这一轮,就可以免费考一次博思,嗯,主要是冲着这个去的。

    p.s. 最近写文档。。恨APIS九个洞。。。

雨乌线运转小记

乌江和安徽一江之隔。两地各辖一个乌江镇。乌江桥东为浦口,西为安徽和县。项羽当年在此自刎。

20090409155

起点,科苑宾馆,浦葛线

20090410160

要坐的雨乌

20090410158

612,来的时候坐的这部车,从江浦到乌江

20090410162

车内图,全车大概50个位子,包括加座(就像校车的加座,要翻下来坐过道上)

20090410(007)

路过桥林

20090410(008)

这镇上还有电影院

20090410(010)

京沪高铁,H型柱子,城际是Y型的柱子

 

20090410(011)

从这里离开宁乌路,上三桥

 

20090410(017)

大奔用来运货了,车牌还挺牛B,苏A ST001,三桥收费站

20090410163

终点,雨花区议会山

20090410164

雨花台纪念馆

20090410166

雨花台纪念碑

20090410167

雨花台群雕

 

一早鼓扬到开发区,3元

吃过午饭后

浦葛到浦六路,1元

汉江到江浦,1.5元

612到乌江,3元

雨乌到雨花台南门,5元

26到丹凤街,1.2元

全程约110公里,14.7元

家门口似乎要通BRT了

    虽然家就住在国道旁,可每次去岛内也算麻烦,因为家门口不设站,基本靠和司机打招呼或者招手停车。今天看到一则BRT新闻“BRT成功大道专线站点设置确定 岛内外共设19站”:

东南快报讯(记者 林晓琪)BRT成功大道专线从厦港出发,经过成功大道、厦门大桥,延伸至集美灌口,全长30.6公里,岛内和岛外各设9个和10个站,共19个站。

  昨日,记者从有关部门获悉,BRT成功大道线岛内9个站点分别为:厦港枢纽站、文曾路站、谊爱路站、吕岭路站、仙岳路站、金湖路站、安兜站、机场大道站、联检站;岛外10个站点为:航海学院站、园博苑站、杏林村站、厦门十中站、内茂立交站、杏北新城站、中亚城站、厦工机械站、灌口镇政府站、灌口枢纽站。

    看来附近房价有上升的空间。。。

投了GE的Summer

    学校用Bras很慢,直接上到远程服务器上网申。居然没有OQ,改了改大摩的cover letter,半个小时搞定。GE的网申系统的牛B之处在于,可以从你提交的CV里取出你的Work Experience(虽然他把我的Education Background给当成了Work Exp了)。其他问题也不多。

    上次的微软估计没戏了,过了一个半月了;大摩上次说还没有计划开始。

    P.S. 博客最近都是“阳春白雪”的stuff,搞点“下里巴人”的。嗯,突然想起了高中的生物老头郑家骥郑老师,课上突然冒出这一句把大伙儿雷得不行。

Selenium, Eclipse, Junit折腾记

    很早以前就想搞自动化Web功能测试,知道了Selenium,看了些文档,但当真正到开发项目的关头,测试总是草草而过,跑完一遍手工的拉倒,回归测试更是无从谈起。前几天终于痛下决心写起使用Selenium的Web自动测试代码。

    先扯扯Selenium(字面上是“硒”的意思)。当初出来的时候结结实实把我震撼了一回。原来搞Web自动化测试基本上走的是GUI的那条老路(当然可能也是我当年孤陋寡闻),而这种GUI自动测试工具往往是功能强大的私有软件(比如WinRunner),另外对Web这种多变的测试元素用起来也是很别扭。Selenium另辟蹊径,从JS入手调用浏览器,同时允许通过跨平台的代码调用。从API就可以看出来这个东西的直观易用:

   1:  selenium.windowMaximize();
   2:  selenium.click("link=信息系统");
   3:  selenium.waitForPageToLoad("30000");
   4:  selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
   5:  selenium.waitForPageToLoad("30000");

    另外,Selenium还提供了一个Firefox插件-Selenium IDE,用于录制用户的操作(虽然部分动作无法录制)。录制的动作可以直接导出成HTML/Java/Ruby/C#/PHP等格式的代码,配合提供的SeleneseTestCase,当作JUnit的TestCase使用。

    不过这折腾就折腾在这TestCase上。Selenium的开发提供的SeleneseTestCase是Junit3风格的,放在JUnit4底下跑,JUnit4的Annotation功能就用不起来了(这点经我浏览代码查证)。Selenium要启动浏览器,如果用不上@BeforeClass的话,每次启动都初始化一下Selenium,开个IE或者Firefox,这个测试的效率可吃不消(也有比较麻烦的Workaround,但总觉得不是很好)。而甩开SeleneseTestCase的话,又舍不得那个在测试没有通过的时候自动截屏的功能。于是开始Google,兼看Junit4的源代码。

    最后终于在这里找到了方案。但问题又来了:Eclipse自带的Junit 4.3还没有这个方案需要的类(JUnit4ClassRunner )!自己手动把原来的Library移除换上Junit 4.5,又发现这个类才用了一个版本就Deprecated了。于是换上了新类(BlockJUnit4ClassRunner)。

    接下去的工作就是用上Decorator模式,把原来SeleneseTestCase的代码给移到新的BaseTestCase上。期间还遇上了一些Override的问题。上代码:

SeleniumTestListner类,用于拦截异常的抛出

   1:  public class SeleniumTestListener extends RunListener {
   2:   private SeleneseTestCaseAdapter stca;
   3:      @Override
   4:   public void testFailure(Failure failure) throws Exception{
   5:          Selenium selenium = stca.getSeleniumTestBase().getSelenium();
   6:   if(!stca.isCaptureScreenShotOnFailure()){
   7:   return;
   8:          }
   9:   if (selenium != null) {
  10:              String filename = failure.getDescription().getDisplayName() + ".png";
  11:   try {
  12:                  selenium.captureScreenshot(filename);
  13:                  System.err.println("Saved screenshot " + filename);
  14:              } catch (Exception e) {
  15:                  System.err.println("Couldn't save screenshot " + filename + ": " + e.getMessage());
  16:                  e.printStackTrace();
  17:              }
  18:          }
  19:   
  20:      }
  21:   
  22:   public void setSeleneseTestCaseAdapter(SeleneseTestCaseAdapter stca){
  23:   this.stca = stca;
  24:      }
  25:  }
 
SeleniumTestRunner类:加入SeleniumTestListener监听器,得到Test实例并注入监听器
   1:  public class SeleniumTestRunner extends BlockJUnit4ClassRunner  {
   2:   private SeleniumTestListener stl;
   3:   public SeleniumTestRunner(Class<?> c) throws Exception{
   4:          super(c);
   5:          stl = new SeleniumTestListener();
   6:      }
   7:   
   8:      @Override
   9:   public void run(RunNotifier rn){
  10:          rn.addListener(stl);
  11:          super.run(rn);
  12:      }
  13:   
  14:   /**
  15:       * Copy from BlockJUnit4ClassRunner.methodBlock(FrameworkMethod method)
  16:       * to get tested instance
  17:       * @author Marshall
  18:       */
  19:      @Override
  20:   protected Statement methodBlock(FrameworkMethod method) {
  21:          Object test;
  22:   try {
  23:              test= new ReflectiveCallable() {
  24:                  @Override
  25:   protected Object runReflectiveCall() throws Throwable {
  26:   return createTest();
  27:                  }
  28:              }.run();
  29:          } catch (Throwable e) {
  30:   return new Fail(e);
  31:          }
  32:   
  33:   //Marshall added
  34:          stl.setSeleneseTestCaseAdapter((SeleneseTestCaseAdapter)test);
  35:   
  36:          Statement statement= methodInvoker(method, test);
  37:          statement= possiblyExpectingExceptions(method, test, statement);
  38:          statement= withPotentialTimeout(method, test, statement);
  39:          statement= withBefores(method, test, statement);
  40:          statement= withAfters(method, test, statement);
  41:   return statement;
  42:      }
  43:  }

SeleniumTestCaseAdapter, 打上了@RunWith。所有的TestCase都继承这个Adapter。但这个Adapter并不继承JUnit的TestCase类

   1:  /**
   2:   * Decorator pattern which makes this class have the same capability as the
   3:   * SeleneseTestCase class had provided. Copy a lot of source code from the 
   4:   * decorated class.
   5:   * @author Marshall
   6:   */
   7:  @RunWith(SeleniumTestRunner.class)
   8:  public class SeleneseTestCaseAdapter {
   9:   private static SeleniumTestBase stb = new SeleniumTestBase();
  10:   private boolean isCaptureScreenShotOnFailure = false;
  11:   
  12:   /** Use this object to run all of your selenium tests */
  13:   protected static Selenium selenium;
  14:   
  15:      @BeforeClass
  16:   public static void setUpSelenium() throws Exception{
  17:          stb.setUp("http://127.0.0.1:8080/", "*iexplore");
  18:          selenium = stb.getSelenium();
  19:      }
  20:   
  21:      @AfterClass
  22:   public static void tearDownSelenium() throws Exception{
  23:          stb.tearDown();
  24:      }
  25:   
  26:     ......
  27:  }

样例测试类:

   1:  public class TestRiskRepo extends SeleneseTestCaseAdapter {
   2:   public TestRiskRepo(){
   3:          setCaptureScreenShotOnFailure(true);
   4:      }
   5:      @Before
   6:   public void set() throws Exception {
   7:          selenium.open("/apis/login.do");
   8:          selenium.type("j_username", "marshall");
   9:          selenium.type("j_password", "xxxx");
  10:          selenium.click("//input[@value='登录']");
  11:          selenium.waitForPageToLoad("30000");
  12:      }
  13:   
  14:      @Test
  15:   public void repo() throws Exception {
  16:          selenium.windowMaximize();
  17:          selenium.click("link=信息系统");
  18:          selenium.waitForPageToLoad("30000");
  19:          selenium.click("//a[contains(@href, 'projects.do?method:view&project.id=1')]");
  20:          selenium.waitForPageToLoad("30000");
  21:          verifyTrue(selenium.isTextPresent("xxx"));
  22:          selenium.click("link=组织风险库");
  23:          selenium.waitForPageToLoad("30000");
  24:          verifyEquals("组织风险库 | APIS", selenium.getTitle());
  25:          verifyTrue(selenium.isTextPresent("可能性"));
  26:          verifyFalse(selenium.isVisible("//button[contains(text(), '搜索')]"));
  27:          selenium.click("css=.x-tool");
  28:          verifyTrue(selenium.isVisible("//button[contains(text(), '搜索')]"));
  29:      }
  30:   
  31:  }

多数据库SaaS尝试

    手上的APIS要上SaaS,其实就是ASP(Application Service Provider)。要求一份程序,一或多份数据库跑服务。设计的时候参考了阿里软件人写的《互联网时代的软件革命-SaaS架构设计》。这本书从广度上覆盖了做SaaS的很多内容,但深度却显不足,很多地方只是浅尝辄止,一笔带过。

    虽然如此,作为算是这个领域的第一本入门书,还是有一些参考价值的。比如,定义了SaaS四个成熟度模型:定制开发、可配置、高性能的多租户架构、可伸缩的多租户架构;APIS只有一个运行实例,但租户数量不会很多,短期内不会超过50,所以算入第3级,高性能的多租户架构。

    在数据库设计上,这本书对SaaS应用,提供了四个可选方案。独立数据库、共享数据库+隔离数据架构(Schema)、共享数据库+共享数据架构(Schema)。由于采用了MySQL,没有schema的概念,所以去掉了中间的选项。虽然技术人员有上高精尖技术的冲动,不过鉴于我们的用户数不会很多,用户和用户之间也完全隔离,采用了第一种方案。而这种方案,对开发其他功能完全没有任何影响,算是非常unobstructive(中文不好翻译,一般叫“无侵入”)的方案。只要在入口的Filter留个ThreadLocal标志,最底层的SessionFactory做个拦截,功能上算是搞定了。大概就是事先准备好几个SessionFactory,然后哪个用户上来了就给谁相应的SessionFactory。

    但革命的道路是曲折的,过程是漫长的。虽然前期通过阅读Spring代码做过一些技术调查,但Spring+Hibernate这对组合的复杂性还是折腾我老半天。Spring对Hibernate支持很好,几乎是全方位的,这也造成了我要到处扩展用到的Spring支持类。本来只打算对LocalSessionFactoryBean动手,却忘了spring大部分的Bean都是singleton,但又舍不得换成prototype后的性能下降,于是抡起袖子又扩展了BasicDataSource(DBCP)、HibernateTemplate、HibernateDaoSupport、HibernateTransactionManager。好不容易有点眉目,开始从配置的租户数据库载入数据,却屡屡抛session提前关闭的异常。而且诡异的是,这个异常出现的概率约为75%,而一旦我用Debug跟进去后,反而基本不抛异常了。这让我异常胸闷,要知道这种问题十有八九是并发的原因,这问题找起来很头大,往往陷入Spring+Hibernate的汪洋大海。

    接下来几天感冒发烧,好好休息了一阵子,思路却打开了。从网上重新搜出一堆资料。这个是Stack Overflow的一个问答,几个回答都比较靠谱;这个是通过HotSwappableTargetSource提供的DataSource动态替换;这个是Spring开发人员利用AbstractRoutingDataSource提供对DataSource的路由。权衡再三,决定使用AbstractRoutingDataSource。只扩展了两个类就成功实现功能,不抛异常。全局一个SessionFactory,后面几个DataSource轮流上。

    但问题又来了。二级缓存这个东西和SessionFactory绑定,多个DataSource要求要多个Cache,因为各个租户之间的数据主键很可能重复。我试图搜寻通过改造SessionFactory的CacheProvider来搞定,但未果,SessionFactory的Cache埋得有点深。

   最后,参考了这篇这篇,用一个简单的Decorator,退回了多SessionFactory,虽然代码不是很好看,但再无异常。今天收工。