跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

这是您第一次登陆到 developerWorks,已经自动为您创建了您的概要文件。 选择您概要文件中可以公开的信息的信息(如姓名、国家/地区,以及公司),这些信息同时也会与您所发布的内容相关联。 您可以随时更新您的 IBM 账号。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

基于 RPT 的云资源部署基准性能测试架构与实现

李 昱彤, 软件工程师, IBM
lyt_small.jpg
李昱彤是一名软件工程师,具有针对web相关产品的软件测试开发经验。主要涉及多平台测试需求的工具开发,基于互联网的自动化框架开发,开源类二次开发,云计算性能工程测试开发等。作为第一作者、第一发明人,发表论文 10 余篇,拥有专利 2 项。包括 IBM 软件质量亚太年度会议论文 3 篇,IBM developerWorks期刊论文 3 篇,其他国内外期刊和会议论文 5 篇,以及发明专利 2 项。

简介: IBM Smart Cloud Enterprise (以下均简称SCE)是IBM云计算战略基于企业级的一款庞大而复杂的公有云产品,通过可视化的云端,帮助客户部署高灵活度的云方案。由此可见,云资源部署的性能指标极为关键。获取和评估云端虚拟资源创建和释放的性能,包括不同操作系统,以及搭载了中间件或者存储介质等的各类虚机的创建和释放的性能,是核心要务。

发布日期: 2012 年 4 月 23 日
级别: 中级
访问情况 : 1107 次浏览
评论: 


RPT(Rational Performance Tester) 是 IBM 性能测试的企业级工具。本文将针对最典型的云资源操作—云端虚机创建,阐述如何基于 RPT 来架构和实现一个典型的基准性能测试。从技术细节来讲将全面深入地分享,

第一,如何通过 web 请求数据和返回数据,整理业务逻辑,编写代码,实施嵌套;

第二,如何设计和实现一个实时的可定制统一输出参数的日志引擎,解决并发用户的参数冲突。

本文旨在通过项目实践,分享上述两大方面的实现过程,并展现技术层面的通用性及其设计思路的可复用性。

针对上述两个层面,正文给出对应的章节。典型虚机创建的 RPT 脚本重构技术,适于并发任务的实时的可定制统一输出参数的日志引擎设计与实现,以及最后的功效验证。

典型虚机创建的 RPT 脚本重构技术

首先,有必要对虚机创建的业务逻辑做可视化说明。如下图所示,


图 1. 虚机创建业务逻辑(查看大图
图 1. 虚机创建业务逻辑

可以看到,用户登入系统后进行添加虚机操作,并按照菜单模板进行套餐配置,提交该配置到服务器,实时的反馈用户当前虚机创建的进度,直到用户成功获得一个 Active 的虚机。作为性能测试的需求,需要从用户体验的角度关注每个必要的环节(比如必要的错误检测和调试日志),以及尤其是虚机创建过程中两大阶段的响应时间。

其次,针对业务进行 RPT 脚本的创建。此部分作为常识性技术知识,本文不详述。请读者参考相关技术来学习如何使用 RPT,文中最后参考资源也附有相关帮助文档。

第三,假设 RPT 原始脚本已经创建完毕。接下来便是分析和编辑这个脚本,称作脚本重构。脚本重构的过程需要脚本开发人员仔细考虑目标实现的全部过程,这样方可有的放矢循序渐进地进行代码编写和调试。

我们给出针对典型虚机创建这个业务已经完成的 RPT 代码重构的示例截图,如下图所示。本文将结合此图进行进一步的解释,因为如何通过 web 请求数据和返回数据,整理业务逻辑,编写代码,实施嵌套等问题都体现在这一过程中。


图 2. 虚机创建的 RPT 代码重构总图
图 2. 虚机创建的 RPT 代码重构总图

该图是基于我们如下工作的汇总:对照业务逻辑,进行请求整理,分析和调试。为了比较清晰的解释这一过程,我们务必需给出架构图,这幅图将整体的业务逻辑从用户层面和技术层面进行结合,全面体现了设计架构思路。


图 3. 总体流程的架构解析(查看大图
图 3. 总体流程的架构解析

结合脚本重构图与设计架构图,可以看出我们是按照访问页面的顺序开展的,依次经历了 home 主页面,登陆页面,访问 control panel 页面,选择需创建虚机的镜像类型和相关参数序列,提交套餐到云后台,套餐请求的持续阶段,套餐虚拟化供给的持续阶段,虚机创建成功通知,用户登出。

可以体会到,除去首位两端的 custom 代码,从整个业务的技术操作来讲主要包括 5 个主要功能块。分别是 SelectImage,Submit,Transaction:Requesting,Transaction:Provisioning,GetInstance。下面本文将依次对这五个模块进行分析和讲解。

SelectImage

SelectImage 其实并非真正的反映用户执行虚机镜像的套餐选择,因为真正产生效应的是在套餐提交,即 Submit 阶段。这里将 SelectImage 作为一个重要模块,是因为它是 timestamp 时间戳的生发之处。Timestamp 极其重要,它贯穿了整个流程,架构图中也不少地方体现了这一点。因为我们用其来判定虚机创建的重要节点,并处理各种可能的结果。现在仍旧回到 SelectImage,可以看到我们重构的时候使得其下所辖一个特定 RPC。这里的 RPC 满足什么条件,又有何可用之处呢?展开来看,

请求段:

usaxprx0a1ccxra.ccmp.ibm.lab/cloud/enterprise/auth/cloud-rpc

Chunkdata: {"params":[-1],"method":"pullEvents","id":7}

返回值:

{"id":7,"result":{"timestamp":1278443355741,"javaClass":"com.ibm.cloud.rpc.bean.EventQueue","events":{"javaClass":"java.util.ArrayList","list":[]}}}

所以,满足以上请求段模型的才是我们要找的 RPC。它会返回一个 JSON 块,含有整个流程所需要的第一时间戳。因此,不言而喻接下来就是要获取这个时间戳。custom 代码段 test.GetTimeStamp 就是斯任。读者可参考下面清单,


清单 1. GetTimeStamp 代码示例
				
 public String exec(ITestExecutionServices tes, String[] args) { 
 IDataArea dataArea = tes.findDataArea(IDataArea.VIRTUALUSER); 

 String response = args[0]; 
 String timestamp = ""; 

        String leftBound = "timestamp\":"; 
    String rightBound = ","; 
    timestamp = Utility.extractValue(response, leftBound, rightBound); 
    
    // 如果 timestamp 为空,输出错误的请求返回
    if(timestamp=="") 
    { 
 // …
    } 
    // 记录合法的 timestamp 到 METADATA1_KEY 所指向的 hash 变量
    LogProcessor temp = ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY)); 
    temp.Timestamp = timestamp; 
    dataArea.put(TestInitial.METADATA1_KEY, temp); 
   
    // 在 dataArea 域中更新并返回
    dataArea.put(TIMESTAMP_KEY, timestamp);    
 return timestamp; 

 } 

Submit

此阶段是提交用户虚机创建套餐。细节看去,拥有和 Select 阶段相同的一个 RPC。但是其实是天壤之别,因为 chunkdata 不一样。如下,

 {"params":["10000350","ElaineTest",{"javaClass":"java.util.HashMap",
 "map":{"quantity":"1","size":"CMP-BRONZE-32.1/2048/175","oss.static.vlan.0":"0"}},
 "2012-07-11T05:00:00Z","brian0705","61"],"method":"createInstances","id":10} 

可见这是完成了一个套餐选项的提交请求。

其返回值自然也是不同。如下示例,

 {"id":10,"result":{"javaClass":"java.util.ArrayList","list":
 [{"size":"CMP-BRONZE-32.1/2048/175","imageName":null,"software":
 {"javaClass":"java.util.ArrayList","list":[{"javaClass":
 "com.ibm.cloud.rpc.bean.Application","version":"",
 "type":"OS","name":"RHEL"}]},"ip":" ","requestId":"2607",
 "createDate":"2010-07-07T02:19:48-05:00",
 "name":"ElaineTest","minExpireExtend":"2010-07-08T02:19:52-05:00",
 "expireDate":"2012-07-06T05:00:00-05:00","status":0,"hostname":"","owner":
 "idpxtestbrian10@cn.ibm.com","activeDate":"","imageId":
 "10000350","storageId":null,"javaClass":"com.ibm.cloud.rpc.bean.ServerInstance",
 "tags":{"javaClass":"java.util.ArrayList","list":[]},"id":"2307",
 "description":null,"maxExpireExtend":"2012-08-04T02:19:52-05:00",
 "ownerName":null,"dataCenter":"61"}]}} 

它给我们提供了一个套餐提交之后的反馈。返回值里字段 status 表征了提交是否成功。比如 0 表示成功,非 0 是异常。还有其他丰富的信息,比如 Objectid,Owner 等。你可以根据需要进行获取。在这里,我们一共有三个 custom 代码来完成架构需求,test.GetObjectid,test.GetOwner,test.GetStatus,其编写规范读者可以参考代码清单一。基于其本质功能的一致性,此处不赘述。

Transaction:Requesting

我们将其做成 Transaction 是想获得其响应时间。这一阶段有一个父类的 IF 来控制。假设 custom 代码 : test.GetStatus 严格等于 0,执行后续操作否则退出该 IF 控制段。那么对于一个成功进入到 Requesting 阶段的用户,我们就可以进行相关管理了。此例中,Requesting 的模块包含一个 loop 和一 custom 代码段。该 loop 中,首先要实现自检测时间戳的功能,因为上文也提到过它很重要可以帮助定义阶段节点;其次要实现判定,这就是 loop 中 CheckStatusForProvision 以及代码 test.GetTimeStampForProvision 所实现的;loop 之外的 custom 代码是用来判定超时现象的。比如我们可以定义 20 分钟为 requesting 阶段的超时临界,如果抵达临界值该阶段尚未结束,就会控制输出超时信息并结束后续脚本执行了。

这里 GetTimeStampForProvision 很重要,它有两个输入参数,这在 RPT 的 argument 可以设置。分别是父 RPC 请求的返回值以及 Objectid,其配置如下所示,


图 4.GetTimeStampForProvision 参数配置图
图 4.GetTimeStampForProvision 参数配置图

方便读者参考,GetTimeStampForProvision 代码清单如下,


清单 2. GetTimeStampForProvision 代码示例
				
 public String exec(ITestExecutionServices tes, String[] args) { 
 IDataArea dataArea = tes.findDataArea(IDataArea.VIRTUALUSER); 
 String response = args[0]; 
 String origin_timestamp =  (String) dataArea.get(GetTimeStamp.TIMESTAMP_KEY); 

 // 从当前 rpc 请求的返回中得到时间戳
 String leftBound = "timestamp\":"; 
 String rightBound = ","; 
 String timestamp = Utility.extractValue(response, leftBound, rightBound); 

 // 时间戳变化标志着云处理后台的响应的更新
 if (!timestamp.equals(origin_timestamp)&&!timestamp.equals("")) 
 { 
  String status = Utility.extractValue(response, "status\":", ","); 
  String id = Utility.extractValue(response, "objectId\":\"", "\","); 
  java.util.Date a = new java.util.Date (Long.parseLong(timestamp)); 
  java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  sdf.setTimeZone(TimeZone.getTimeZone("Etc/GMT")); 
  String st=sdf.format(a); 
 
  //objecid 一致,且 status 为 1,表明 requesting 成功
  if (args[1].equals(id)&& "1".equals(status) ) 
  {    
      //status 1 表示 requesting 成功结束,provisioning 成功开启,对 provisioning 的元类变量实时推换操作。
  LogProcessor temp2 = ((LogProcessor) dataArea.get(TestInitial.METADATA2_KEY)); 
  temp2.WorkingStageCode = "1";  temp2.Timestamp = timestamp; 
  temp2.StartTime = st+"(GMT)"; 
  temp2.MilliStartTime = Long.parseLong(timestamp); 
  dataArea.put(TestInitial.METADATA2_KEY, temp2); 
  
  // 同时,对 requesting 的元变量实施推换操作。
  LogProcessor temp1 = ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY)); 
  temp1.StageStatusCode = "0"; 
  temp1.EndTime = st+"(GMT)"; 
  temp1.MilliEndTime = Long.parseLong(timestamp); 
  if(0!=temp1.MilliStartTime&&0!=temp1.MilliEndTime) \
  temp1.StageDuration = (float)((temp1.MilliEndTime-temp1.MilliStartTime)/1000.0/60); 
  dataArea.put(TestInitial.METADATA1_KEY, temp1); 
  //requeting 元变量输出
  Utility.logInfo((File) dataArea.get(TestInitial.LOGINDIVIDUAL_KEY),
  ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY)).dataSerial()); 
  // 更新时间戳到全局,停止 loop,返回新时间戳
    dataArea.put(GetTimeStamp.TIMESTAMP_KEY, timestamp); 
  tes.getLoopControl().breakLoop(); 
  return timestamp;   
  } 
 //objecid 一致,且 status 不为 1,表明 requesting 失败
  else if(args[1].equals(id)&&(!"1".equals(status))) 
  { 
      dataArea.put(GetTimeStamp.TIMESTAMP_KEY, timestamp); 
      
      // 同时,对 requesting 的元变量实施推换操作
  LogProcessor temp = ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY)); 
  temp.EndTime =st+"(GMT)"; 
  temp.MilliEndTime = Long.parseLong(timestamp); 
  if(0!=temp.MilliStartTime&&0!=temp.MilliEndTime) \
  temp.StageDuration = (float)((temp.MilliEndTime-temp.MilliStartTime)/1000.0/60); 
  temp.StageStatusCode = "1"; 
   temp.failureResponseCode+="[failure requesting response]: "+response+" | "; 
  dataArea.put(TestInitial.METADATA1_KEY, temp); 
  //requeting 元变量输出
  Utility.logInfo((File) dataArea.get(TestInitial.LOGINDIVIDUAL_KEY),
  ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY)).dataSerial()); 
  
  tes.getLoopControl().breakLoop(); 
  return "0"; 
  } 
  //objecit 不一致,更新时间戳,不考虑判定,继续 loop 
  else 
  { 
     dataArea.put(GetTimeStamp.TIMESTAMP_KEY, timestamp); 
     return "0"; 
  } 
  } 
    // 时间戳未变,继续 loop 
 return "0"; 

 } 

Transaction:Provisioning

这个阶段的结构和处理与 Requesting 非常类似,不再赘述,其唯一的不同之处在于原变量的推送和输出,读者可以研究整体架构图来设计这一段的 Transaction。

GetInstance

这一阶段其实是为了满足输出的需求,因为到目前为止整个虚机的创建已经结束了,如果提前失败,则在此阶段之前已经得以合适的操控和输出,测试停止;如果成功,则通过该阶段进行操控输出。注意所辖 RPC 的特点,其请求段中 2307 是 Objectid,method 必须是 getInstance。其返回值中要获取的字段是成功创建的虚机的 hostname,这也是所辖 custom 代码 test.GetInstanceIP 的需求所在。

请求段:

usaxprx0a1ccxra.ccmp.ibm.lab/cloud/enterprise/auth/cloud-rpc

Chunkdata: {"params":["2307"],"method":"getInstance","id":49}

GetInstanceIP 关于 IP 推送的清单不再给出,读者可以参考上文相关代码示例编写。

到目前为止,我们完成了重构中的所有业务要件,向读者展示了如何通过 web 请求数据和返回数据,整理业务逻辑,编写代码,实施嵌套。可以说我们已经成功地提供了一个基于 RPT 的虚机创建框架,集成了基本完备的日志输出模型。但是一个问题出现了,如果实行并发用户策略,那么日志的实时管理和秩序输出就变得极其重要。这就引出了第二章节。


实时的可定制统一输出参数的日志引擎设计与实现

我们需要设计和实现一个统一的日志框架,能够执行格式化的输出,避免并发的读写冲突,进行业务上的容错反馈和控制,有良好的框架复用性和代码推广价值。而这不仅仅是为了解决并发支持的问题。

我们设计了一个元类,细心的读者可能已经注意到文章前面代码清单中不止一次出现过 Metadata。是的,这就是和我们的这个元类相关,该类具备如下属性,


图 5.Metadata 元类变量属性设计图(查看大图
图 5.Metadata 元类变量属性设计图

可以看到,Timestamp 是虚机创建生命周期的时间戳,Group-User 是虚拟用户索引,Owner 是创建虚机的用户账户,Working Stage Code 用来表征虚机的两个业务阶段,0 为 Requesting,1 为 Provisioning,Start Time 是表示该阶段的起始时间,End Time 是结束时间,均精确到毫秒,Object Id 是标志虚机创建请求的对象 ID,IP 是创建好的虚机的 IP 地址,Stage Status Code 是标志位,用来表征虚机业务阶段是否成功进行完毕,Duration 是业务阶段的持续时间,也就是性能指标,ErrResponse 是一旦某阶段没有成功,出现异常所对应的请求返回字符串值,FailureResponse 是一旦某阶段失败,但没有任何异常所对应的请求返回字符串值。下面是这个类的具体实现代码,


清单 3. Metadata 元类实现
				
 public String exec(ITestExecutionServices tes, String[] args) { 
 public String Timestamp=""; 
 public String User_Group=""; 
 public String WorkingStageCode=""; 
 public String StartTime=""; 
 public String EndTime=""; 
 public String Owner=""; 
 public String ObjectId=""; 
 public String IP=""; 
 public String StageStatusCode=""; 
 public float StageDuration = 0; 
 public String errResponseCode= ""; 
 public String failureResponseCode=""; 
 public long MilliStartTime = 0; 
 public long MilliEndTime = 0; 

 public String dataSerial() 
 { 
 return "TS_"+Timestamp+","+User_Group+","+Owner+","+WorkingStageCode+",
 "+StartTime+","+EndTime+","+ObjectId+","+IP+","+StageStatusCode+",
 "+StageDuration+","+CSVCellTranslator(errResponseCode)+",
 "+CSVCellTranslator(failureResponseCode); 
 } 
 public static String CSVCellTranslator(String input) 
 { 

 if(input.contains("\"")) 
 { 
 input=input.replaceAll("\"", "\"\""); 
 } 
 input="\""+input+"\""; 
 return input; 
 } 

其中,有两个成员函数。dataSerial()是将类的各元进行串接,形成 CSV 导出格式。CSVCellTranslator()是考虑到如果某元含有各种字符,尤其是引号本身,将其进行处理使得该字符串能够位于一个 CSV 单元格。

接着介绍元变量在虚机创建生命周期中是如何实时操控的。如下示例,

LogProcessor temp = ((LogProcessor) dataArea.get(TestInitial.METADATA1_KEY));

temp.Owner = owner;

dataArea.put(TestInitial.METADATA1_KEY, temp);

就这样,十分有效的,可以完成元类的替换和推送过程,而且适用于所有的元类。上面的例子是对元类变量 Owner 的推送和置换。

理解了上述内容,再次结合整体架构图,相信读者有了更多的体会。其实架构图中最下两行正是两个元类,分别对应我们提到的两个业务阶段。通过利用这样的类结构在虚机创建的生命周期中进行合理部署进而达到实时日志的秩序输出和并发支持。

对于整个日志框架,除了 Metadata 特定的元类实现,我们在开篇中略微提过的位于结构首尾的 custom 代码,尤其是位于首部的 test.TestInitial 也是十分关键的,充当着整体策略部署的启动因子。其主要功能是实现初始化,包括对于无论单个还是并发模式下的用户以实时的时间戳命名的文件路径分配,对于具备全局作用域的用户类和两个元类的初始化等。其代码清单如下,


清单 4. TestInitial 类的实现
				
 public String exec(ITestExecutionServices tes, String[] args) { 

 IDataArea dataArea = tes.findDataArea(IDataArea.VIRTUALUSER); 

 Date currentDate = Calendar.getInstance().getTime(); 
 SimpleDateFormat sm = new SimpleDateFormat("yyyy-MM-dd"); 

 // 只建立一个唯一的基于时间戳的文件夹路径,用来存放结果集合
 String workingDirStr = "D:/dcdump/" + "log_provision_" + sm.format(currentDate); 
 File workingDir = new File(workingDirStr); 
 if(!workingDir.exists()) 
 { 
 workingDir.mkdir(); 
 } 

 // 依据虚拟用户分类原则自动创立文本文件,与特定的虚拟用户关联,作为该用户所有测试结果的日志输出所在地
 IVirtualUserInfo virtualUserInfo= (IVirtualUserInfo) dataArea.get(IVirtualUserInfo.KEY); 
 String testLogFileStr = virtualUserInfo.getUserGroupName()+
 "_"+virtualUserInfo.getUserName()+"_"+virtualUserInfo.getUserGroupIndex(); 
 File testLogFile = new File(workingDirStr +"/"+ testLogFileStr + ".txt"); 


 // 初始化两个元类,表征两个业务段。并做相关赋值。将两个元类推入全局域。
 LogProcessor metaData_1 = new LogProcessor(); 
 LogProcessor metaData_2 = new LogProcessor(); 
 metaData_1.User_Group = virtualUserInfo.getUserName()+
 "_"+virtualUserInfo.getUserGroupIndex(); 
 dataArea.put(METADATA1_KEY, metaData_1); 
 dataArea.put(METADATA2_KEY, metaData_2); 

 try { 
 testLogFile.createNewFile(); 
 } catch (IOException e) { 
 // TODO Auto-generated catch block 
 e.printStackTrace(); 
 } 
 // 将用户特定关联的文件推入全局域
 dataArea.put(LOGINDIVIDUAL_KEY, testLogFile); 
 return testLogFileStr; 
 } 
 // 三个全局域变量,贯穿该 test 的执行始末。分别是用户特定文件路径,业务阶段 requesting 和业务阶段 provisioning。
 final static String LOGINDIVIDUAL_KEY = "LOGINDIVIDUAL_KEY"; 
 final static String METADATA1_KEY = "METADATA_1"; 
 final static String METADATA2_KEY = "METADATA_2"; 

以上部分就是实时分布式日志框架的设计实现和应用。


功效验证

不难看出,上述两章节所对应的技术要素是相辅相成,嵌合在一起的,日志的框架是服务于虚机的创建,虚机的创建融合了日志的管理。让我们看一看测试被触发之后,这套框架提供给我们的最终结果。

一旦测试启动,不论任何用户加载模式,则自动生成如下文件结构,以天为计时单元形成文件夹,具备如下格式,

‘ \log_provision_yyyy-mm-dd ’,例如 ‘ D:\dcdump\log_provision_2010-08-13 ’,如下图所示左侧导航栏红色方框标识的文件夹。

而关联着每个特定虚拟用户的日志结果则在此路径下自动生成文本文件,具备 CSV 格式。同样以下图为例,可以看到这是一个加载了 20 个并发用户的压力测试例,自动生成 20 个日志文件,每个文件以所在的虚拟组名和用户检索序列号组成,如下图所示。


图 6. 分布式文件结构(查看大图
图 6. 分布式文件结构

每个日志文件里的记录符合统一的格式,将所有日志进行自动串联的结果,如下图所示


图 7. 自动生成的日志示例(查看大图
图 7. 自动生成的日志示例

小结

  • 云计算是 IBM 基于服务理念的核心战略,RPT 是 IBM 企业级的性能测试软件,云的性能是热点话题。我们以 RPT 为实现平台,云为解决对象,以性能测试为目标,给读者展现了一个云资源部署基准性能测试的架构和实现范例。
  • 基于 web 层面业务逻辑的分析和代码编写与嵌套是十分有意义的,技术人员可以以此为模板,启迪云资源部署性能测试架构和分析的灵感,触类旁通,架构和实现其他类型虚拟资源的性能测试解决方案,比如虚拟存储资源,虚机镜像等等。
  • 实时的可定制的统一化日志引擎的意义在于不仅解决了并发用户的冲突问题,而且提供了通用的代码案例,资深的程序员可以高效地将这套思想移植到自己项目的需求上来。

参考资料

学习

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

关于作者

lyt_small.jpg

李昱彤是一名软件工程师,具有针对web相关产品的软件测试开发经验。主要涉及多平台测试需求的工具开发,基于互联网的自动化框架开发,开源类二次开发,云计算性能工程测试开发等。作为第一作者、第一发明人,发表论文 10 余篇,拥有专利 2 项。包括 IBM 软件质量亚太年度会议论文 3 篇,IBM developerWorks期刊论文 3 篇,其他国内外期刊和会议论文 5 篇,以及发明专利 2 项。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Cloud computing, Rational
ArticleID=811371
ArticleTitle=基于 RPT 的云资源部署基准性能测试架构与实现
publish-date=04232012