应用软件系统级自动化性能测试

软件性能的系统级测试

对应用软件进行性能测试可以分为两个层面,一是测试应用软件的操作响应速度,通过从用户角度得到的应用软件操作响应的时间,来判断和分析软件在运行时带给用户在响应速度上的体验。这一点,在《使用 Rational Functional Tester 测试应用软件的操作响应速度》一文中进行了比较充分和详尽的阐述。而在另一方面,我们也需要精确的测量软件运行时一些重要的系统级性能数据,如软件运行占用的 CPU 系统时间,系统内存和虚存乃至如 GDI 对象,IO 等。通过这些量化的具体数据,我们才能够对应用软件进行更精确的分析和调优,从软件架构和代码的层面对程序的性能进行优化,进一步的提高软件的性能。

软件运行的 CPU 系统时间,占用的系统内存、虚存,GDI 对象等等这些数据我们可以通过操作系统自身提供的一些工具如 Windows 的任务管理器等来获得,但是这样的话,一方面我们很难将获得的数据与软件具体的某个操作精确的联系起来,为我们对程序代码的分析提供更可靠的分析依据,从另一方面来说,这样人工的记录也无疑耗费着我们宝贵的人力和时间。

开放的 RFT 自动测试平台

IBM 提供了一系列强大的软件自动测试产品,其中 IBM® Rational® Performance Tester (RPT) 能够通过配置,提供 CPU 系统时间,内存等实时的系统监测数据,但是作为测试 Web 应用软件的自动测试平台,RPT 并不能适用于普通的非 Web 应用软件的系统级自动性能测试。

而 IBM® Rational® Functional Tester (RFT) 则可以通过访问应用软件中的各个对象以及对象的方法和属性,控制软件的运行,完成对 Java,Web,.NET 各种应用软件的自动化测试。但是,RFT 本身却没有提供对软件运行时所占用的 CPU 系统时间,系统内存等计算机系统性能数据进行监测的工具或者相应的配置。

我们似乎陷入了一个互相矛盾的两难境地:RPT 可以监测 CPU 时间,系统内存等数据但是却不能对非 Web 应用软件进行测试,RFT 可以对普通应用软件进行自动测试,却不能对 CPU 时间,内存等系统性能数据进行监测。

不过峰回路转,在 RFT 基于 Java 的开放性自动测试架构的基础上,我们可以实现自定义的扩展 Java API,并使用这些 API 来获取软件对应进程的 CPU 时间和系统内存等系统性能数据,从而通过 RFT 来完成对应用软件的系统级自动化性能测试。

获取软件进程的系统性能信息

获取进程 CPU 时间的系统 API

一般说来,操作系统本身会提供相应的 API,对软件进程的 CPU 时间,占用内存等系统数据进行访问。在 Windows 下,可以通过

BOOL GetProcessTimes(HANDLE hProcess, 
 PFILETIME pftCreationTime, PFILETIME pftExitTime, 
 PFILETIME pftKernelTime, PFILETIME pftUserTime)

来获取对应进程的各种 CPU 时间,其中:

HANDLE hProcess: 被测软件进程的句柄 
 PFILETIME pftCreationTime: 进程创建时间 
 PFILETIME pftExitTime: 进程终止时间 
 PFILETIME pftKernelTime: 核心模式下进程占用的时间 
 PFILETIME pftUserTime: 用户模式下进程占用的时间 

GetProcessTimes API 被调用后,参数 pftCreationTime, pftExitTime, pftKernelTime, pftUserTime 就会被置上对应的数据。但是通过 GetProcessTimes 函数得到的是 FILETIME 类型,我们需要利用下面的函数进行转换:

static LONGLONG fileTimeToInt64 (const FILETIME * time){
 ULARGE_INTEGER _time;
 _time.LowPart = time->dwLowDateTime;
 _time.HighPart = time->dwHighDateTime;
 return _time.QuadPart;
}

得到的是以 100 纳秒为单位的数值。进程在核心模式下和用户模式下占用的时间之和,就是进程占用 CPU 的时间了。

获取软件进程句柄

那么,软件进程的句柄有该如何获取呢?我们可以通过另外一系列的 API 来枚举出当前系统中运行的所有进程信息,然后在这些系统进程中查找到被测软件对应的进程 ID,进而得到软件进程的句柄。

HANDLE WINAPI CreateToolhelp32Snapshot(
 DWORD dwFlags, DWORD th32ProcessID );

获取当前系统中运行所有进程的快照,其中 DWORD dwFlags 是对系统进程进行快照的类型,比如 TH32CS_SNAPPROCESS 表示获取当前系统范围内的所有进程,而 th32ProcessID 设置成 0 即可。

用一个 PROCESSENTRY32 结构的变量来保存我们获得的进程信息结构:

typedef struct
{ 
 DWORD dwSize; // 结构的长度
 DWORD cntUsage; // 进程的引用数
 DWORD th32ProcessID; // 进程 ID
 DWORD th32DefaultHeapID; // 进程默认堆 ID
 DWORD th32ModuleID; // 进程模块 ID
 DWORD cntThreads; // 进程创建的线程数
 DWORD th32ParentProcessID; // 进程的父线程 ID
 LONG pcPriClassBase; // 进程创建的线程基本优先级
 DWORD dwFlags; // 内部使用
 CHAR szExeFile[MAX_PATH]; // 进程名称
} PROCESSENTRY32;

其中 dwSize 需要在使用前设置:

PROCESSENTRY32 pe32;
pe32.dwSize=sizeof(pe32);

将我们定义的 pe32 传入 Process32First() 即可返回快照中第一个进程的信息结构。

BOOL WINAPI Process32First(
 HANDLE hSnapshot, //CreateToolhelp32Snapshot() 得到的快照句柄
 LPPROCESSENTRY32 lppe // 进程信息结构
);

而通过 Process32Next 可以得到快照中下一个进程:

BOOL WINAPI Process32Next(
 HANDLE hSnapshot, // CreateToolhelp32Snapshot() 得到的快照句柄
 LPPROCESSENTRY32 lppe // 进程信息结构
);

我们可以通过得到的进程信息结构中进程名称 pe32.szExeFile 变量与期望找到的进程名进行比较,找到对应的进程后,就可以得到此进程信息结构中的进程 ID:pe32.th32ProcessID。有了进程 ID 以后,我们才能通过 OpenProcess 函数的返回值得到进程的句柄:

HANDLE WINAPI OpenProcess(
 __in DWORD dwDesiredAccess, // 访问进程的权限
 __in BOOL bInheritHandle, // 句柄是否继承进程属性
 __in DWORD dwProcessId // 进程 ID
 );

整个枚举比较找到进程 ID 并最终得到进程句柄的过程如下:

HANDLE hProcessSnap=::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
BOOL hpMore=::Process32First(hProcessSnap,&pe32); 
while(hpMore) {
if((stricmp(pe32.szExeFile,DestProcessorName) == 0)){
printf("Find processor \n");
printf("%20s\t%10d\n",pe32.szExeFile,pe32.th32ProcessID);
 hdestPr=OpenProcess(PROCESS_ALL_ACCESS, false, pe32.th32ProcessID); 
 break;
}
hpMore=::Process32Next(hProcessSnap,&pe32);
}

获取软件进程的内存信息

得到句柄 hdestPr 后,我们就可以调用前面提到的 GetProcessTimes() API 来得到进程具体的进程占用的 CPU 时间了。而进程占用的内存,我们也可以通过 GetProcessMemoryInfo() API 来获取:

BOOL WINAPI GetProcessMemoryInfo(
 HANDLE Process, // 进程句柄
 PPROCESS_MEMORY_COUNTERS ppsmemCounters, // 内存信息结构
 DWORD cb // 内存信息结构的大小 
);

而内存信息结构的定义如下:

typedef struct _PROCESS_MEMORY_COUNTERS { 
DWORD cb; 
DWORD PageFaultCount; // 分页错误数目
SIZE_T PeakWorkingSetSize; // 工作集列 ( 物理内存 ) 的最大值
SIZE_T WorkingSetSize; // 工作集列 ( 物理内存 ) 的大小
SIZE_T QuotaPeakPagedPoolUsage; // 分页池的峰值的最大值
SIZE_T QuotaPagedPoolUsage; // 分页池的峰值大小
SIZE_T QuotaPeakNonPagedPoolUsage; // 非分页池的峰值的最大值 
SIZE_T QuotaNonPagedPoolUsage; // 非分页池的峰值大小
SIZE_T PagefileUsage; // 页文件页的大小(虚拟内存)
SIZE_T PeakPagefileUsage; // 页文件页的最大值
} 

其中 WorkingSetSizePagefileUsage 分别指的就是进程占用的物理内存和虚拟内存。

在得到进程句柄后,我们将定义好的内存信息结构变量传入 GetProcessMemoryInfo(),就可以得到对应的内存信息了:

PROCESS_MEMORY_COUNTERS pmc;
pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
GetProcessMemoryInfo(hsoffice, &pmc, sizeof(pmc));
physicalMem = pmc.WorkingSetSize;
virtualMem = pmc.PagefileUsage;

除了可以通过 GetProcessTimesGetProcessMemoryInfo API 来获取进程的 CPU 时间和占用内存的情况外,我们还可以通过类似的办法调用 GetProcessIoCountersGetThreadTimes 等 API 获得更进一步的 IO,线程等信息。

RFT Java Native API 的实现

通过上述的操作,我们得到了被测软件的进程句柄并最终得到了进程的 CPU 时间和内存信息,但是,这些 API 的操作都是在 C++ 的环境下完成的,我们没有办法在 RFT 的 Java Script. 中直接进行这些调用。因此我们需要把上述的操作封装成动态链接库,并通过 Java Native API 完成对动态链接库的调用,而最终在 RFT 的 Java Script. 中得到进程的系统性能信息。

Java Native Interface 允许 Java 程序调用本地用 C++ 等其它语言编写的程序或类库。我们将经过一系列的步骤来完成我们的 Java Native API。

首先在 RFT 的 Java Script. 代码 JavaNative_PerfmonWin.Java 中加上 JNI 的定义:

public native long getProcessorInfo(String processorName,String InfoType);

编译通过生成对应的 class 文件后,在 class 文件的目录下执行:

Javah –jni JavaNative_PerfmonWin.class

然后,会生成对应的 JavaNative_PerfmonWin.h 文件:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class JavaNative_PerfmonWin */
#ifndef _Included_JavaNative_PerfmonWin
#define _Included_JavaNative_PerfmonWin
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class: JavaNative_PerfmonWin
 * Method: getProcessorCPUTime
 * Signature: (LJava/lang/String;LJava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_JavaNative_PerfmonWin_getProcessorInfo
 (JNIEnv *, jclass, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif

这里声明的方法传递了一个 JNIEnv 指针、一个 jobject 指针和通过 Java 方法定义的 Java 参数(软件进程的名称和需要获取的信息)。

我们依据 JavaNative_PerfmonWin.h 中的声明,实现:

extern "C" JNIEXPORT jlong JNICALL 
Java_testcase_PVT_BenchMark_PVT_1Bench_1common_commTask_getProcessorInfo
(JNIEnv * env, jclass, jstring JprocessorName, jstring JInfoType){
……
……
}

在函数中按照之前的描述对进程进行枚举,找到进程的 ID 并进而得到句柄,然后调用相应 API 得到并返回获取的进程 CPU 时间和内存等信息。

其中,传入的 Java 参数需要进行转换:

const char* processorname = env->GetStringUTFChars(JprocessorName,0);
const char* Infotype = env->GetStringUTFChars(JInfoType,0);

并且在用完后需要释放:

env->ReleaseStringUTFChars(JprocessorName,processorname);
env->ReleaseStringUTFChars(JInfoType,Infotype);

整个程序的流程如下图 1 所示。


图 1. 进程 CPU,内存信息获取流程图
图 1. 进程 CPU,内存信息获取流程图

代码编译通过生成动态链接库之后,我们在 RFT 的 Java Script. 中加入:

static{
System.loadLibrary("JNIperfmon_dll");
}

并把动态库放到系统路径下,就可以完成调用了。

在克服重重困难之后,我们终于获得了自定义的在 RFT 下访问软件进程的 CPU 占用时间,内存等系统性能数据的 API,之后的使用就简单得多了。

在 RFT 的 Java Script. 中,我们可以直接使用:

Long proCPU_USERTime = getProcessorInfo(“ProcessorName”,"CPUUserTime");

来获取对应进程的 CPU,内存等性能信息。

自动化系统性能测试的流程及其扩展

此后我们对应用软件某个操作的自动性能测试流程也非常的简单,在控制软件进行动作之前,记录软件进程的 CPU 占用时间和占用内存,然后用 Script. 控制软件完成某个操作,如打开一个文档,保存修改过的文档或是其他一些复杂的操作,当检测到软件操作完成的某个判断标识发生改变或是等待足够长的时间以保证软件的操作完全完成后,再一次记录软件进程此时的 CPU 占用时间和占用内存,前后的数值相减,就可以得到软件这次操作所消耗的 CPU 时间和增加分配的内存了。如图 2 所示。


图 2. 自动化系统级性能测试流程图
图 2. 自动化系统级性能测试流程图

而更进一步的,我们甚至可以通过软件进程占用的 CPU 时间和系统 CPU 的主频,大致的换算出软件某次操作所执行的 CPU 指令数目:

指令数量 = CPU 单周期执行指令数 * ( CPU 时间 * ( 1 / CPU 主频 ))

假设我们的 CPU 主频是 1.4G HZ, 那么可以大致的认为 CPU 的一个时钟周期是 1S/1.4G=0.7μs,用软件进程某个操作所占的 CPU 时间假设是 230ms 除以这个值,便得到了软件进程在软件操作过程中占用的 CPU 周期数:328.6K 个,我们可以认为平均起来每个 CPU 时钟周期可以执行 2 条指令,那么软件在这个操作过程中所执行的 CPU 指令数大致就是占用的 CPU 周期数乘 2, 大约 657K 条指令了。

有时我们会需要比较不同软件或是软件的不同版本之间的某个操作的性能,进行软件性能的评测或是保证软件开发过程中性能没有出现下降。但是由于某些客观条件的限制,我们会在不同频率的 CPU 主频计算机系统中进行测试,并得到测试数据。由于测试平台的计算机 CPU 本身性能就存在差异,因此即使得到的测试数据之间存在不同,我们也很难衡量这些被测软件的性能孰优孰劣。而通过上述的一个简单的换算,我们可以得到不同被测软件在这个操作时所耗费的 CPU 指令数,就能够进行统一的比较了。

我们还可以计算软件进程的某段时刻 CPU 占用率 , 假设 t1 时刻进程的 CPU 时间为 pCPUTimet1,而 t2 时刻进程的 CPU 时间为 pCPUTimet2

pCPURate = (pCPUTimet1-pCPUTimet2)/(t2-t1)

这样,我们就可以用进程 CPU 占用率达到某个数值作为软件完成某个操作的标识来衡量软件的操作性能。还甚至可以画出软件在操作过程中 CPU 占用率的曲线,而通过采用各种数学的方法分析曲线的斜率等变化情况则可以让我们对软件操作性能进行更进一步的分析。而在某些关注软件 IO 性能的情况下,我们也可以在目前代码的框架下,修改调用 GetProcessIoCounters 等 IO 相关的 API 函数,来获得对应的性能信息,完成软件的自动化系统级性能测试。


 

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14780873/viewspace-582426/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/14780873/viewspace-582426/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值