文章总结: 本文档详细介绍了stressapptest压力测试工具的功能、参数及在Android系统中的集成与调用方法。首先阐述了工具原理及CPU压测实现,涉及多线程浮点计算与锁机制。其次提供了高通与RK平台的编译集成步骤,并给出了APK通过Root权限调用二进制文件的Java代码示例。文档兼具理论分析与实战代码,为开发者进行系统稳定性测试与工具移植提供了可操作的指导方案。 综合评分: 83 文章分类: 安全工具,移动安全,实战经验
stressapptest 压力测试 分析
原创
瓶子的跋涉 瓶子的跋涉
瓶子的跋涉
2025年10月13日 18:37 广东
stressapptest
Stressapptest 是Stressful Application Test(stressapptest)的简称,模拟高负载环境,进行大量计算,数据处理和内存操作,测试系统在多线程并发任务下的性能。
s: number of second to run the application ,测试时间m: number of memory copy threads to run ,复制线程数 memory copyi: number of memoy invert threads to run 反转线程数 Invert Copyc: CRC Check CRC校验 Data CheckC: number of memory CPU Stress threads to run CPU压力线程数M: Megabytes of ram to run 尽可能测试最大的可用存储空间(设置超过 memfree,就会被 kill)- 其它参数 查看源码的
sat.cc
./stressapptest -s 86400 -m 4 -i 4 -c 4 -C 4 -M xxx
官网示例 代码。
./stressapptest -s 20 -M 256 -m 8 -C 8 -W # Allocate 256MB of memory and run 8 "warm copy" threads, and 8 cpu load threads. Exit after 20 seconds.
./stressapptest -f /tmp/file1 -f /tmp/file2 # Run 2 file IO threads, and autodetect memory size and core count to select allocated memory and memory copy threads.
编译系统如何加入 stressapptest
高通:
device\qcom\common\base.mk添加
PRODUCT_PACKAGES += stressapptest
RK目录
device/rockchip/rk3562/rk3562_t/rk3562_t.mk
添加编译到系统当中
APK调用方法
加载拷贝stressapptest 文件
APK开发可以直接 嵌入 ,如何 单独 编译到 系统当中
private boolean copyBin(String name) {
File desFile = new File("/data/", name);
Log.i(TAG, "copyBin, desFile: " + desFile.toString());
// 从源码的assets的 bin目录下,复制到/data/目录下,可能需要系统app才可以执行
if (copyAssetFile("bin/" + name ,desFile )) {
// 授予 文件对应的权限
ShellUtils.CommandResult result2 = ShellUtils.execCmd(
"chmod 777 /data/" + name,
true);
Log.i(TAG, "copyBin, chmod result: " + result2.toString());
return result2.result >= 0 && TextUtils.isEmpty(result2.errorMsg);
}
returnfalse;
}
/**
* Execute the command.
*
* @param command The command.
* @param isRooted True to use root, false otherwise.
* @return the single {@link CommandResult} instance
*/
public static CommandResult execCmd(final String command, final boolean isRooted) {
return execCmd(new String[]{command}, isRooted, true);
}
/**
* Execute the command.
*
* @param commands The commands.
* @param isRooted True to use root, false otherwise.
* @param isNeedResultMsg True to return the message of result, false otherwise.
* @return the single {@link CommandResult} instance
*/
public static CommandResult execCmd(final String[] commands,
final boolean isRooted,
final boolean isNeedResultMsg) {
int result = -1;
if (commands == null || commands.length == 0) {
returnnew CommandResult(result, "", "");
}
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec(isRooted ? "su" : "sh");
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) continue;
os.write(command.getBytes());
os.writeBytes(LINE_SEP);
os.flush();
}
os.writeBytes("exit" + LINE_SEP);
os.flush();
result = process.waitFor();
if (isNeedResultMsg) {
successMsg = new StringBuilder();
errorMsg = new StringBuilder();
successResult = new BufferedReader(
new InputStreamReader(process.getInputStream(), "UTF-8")
);
errorResult = new BufferedReader(
new InputStreamReader(process.getErrorStream(), "UTF-8")
);
String line;
if ((line = successResult.readLine()) != null) {
successMsg.append(line);
while ((line = successResult.readLine()) != null) {
successMsg.append(LINE_SEP).append(line);
}
}
if ((line = errorResult.readLine()) != null) {
errorMsg.append(line);
while ((line = errorResult.readLine()) != null) {
errorMsg.append(LINE_SEP).append(line);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (successResult != null) {
successResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
returnnew CommandResult(
result,
successMsg == null ? "" : successMsg.toString(),
errorMsg == null ? "" : errorMsg.toString()
);
}
/**
* The result of command.
*/
publicstaticclass CommandResult {
publicint result;
public String successMsg;
public String errorMsg;
public CommandResult(final int result, final String successMsg, final String errorMsg) {
this.result = result;
this.successMsg = successMsg;
this.errorMsg = errorMsg;
}
@Override
public String toString() {
return"result: " + result + "\n" +
"successMsg: " + successMsg + "\n" +
"errorMsg: " + errorMsg;
}
}
调用执行
private Runnable mStressAppTestRunnable = new Runnable() {
@Override
public void run() {
Log.i(TAG, "stressapptest run...");
DataOutputStream dos = null;
try {
Process process = Runtime.getRuntime().exec("su");
dos = new DataOutputStream(process.getOutputStream());
String command = "/data/" + STRESS_APP_TEST + " -s " + (mTargetTime - 10)
+ " -i 4 -C 4 -W --stop_on_errors -M " + sMemory + "\n";
dos.write(command.getBytes(Charset.forName("utf-8")));
dos.flush();
String line;
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
while (isRunning() && (line = bufferedReader.readLine()) != null) {
Log.i(TAG, "stressapptest: " + line);
Message msg = new Message();
msg.what = COMMAND_UPDATE_LOG;
msg.obj = line;
mHandler.sendMessage(msg); // 通过handle将数据发送出去
}
dos.writeBytes("exit\n");
dos.flush();
Log.w(TAG, "stressapptest exit.");
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
} finally {
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
}
}
};
stressapptest源码分析
源码十分精简,可以学习了解一下。(补充) 不过代码量也挺高的。
源码结构
sat类
入口函数
int main(int argc, char **argv) {
Sat *sat = SatFactory(); // stressapptest 的核心类,通过SatFactory 返回创建的对象指针
if (sat == NULL) {
logprintf(0, "Process Error: failed to allocate Sat object\n");
return255;
}
// 在if语句中每个 运行正确会一直执行
if (!sat->ParseArgs(argc, argv)) { // 先解析命令函参数
logprintf(0, "Process Error: Sat::ParseArgs() failed\n");
sat->bad_status(); //错误状态++
} elseif (!sat->Initialize()) { // 接着进行相关的初始化
logprintf(0, "Process Error: Sat::Initialize() failed\n");
sat->bad_status();
} elseif (!sat->Run()) { // 初始化完成 进行压力测试
logprintf(0, "Process Error: Sat::Run() failed\n");
sat->bad_status();
}
// 测试完成 打印测试结果,根据执行中的 errorcount_ 以及错误状态来输出结果
sat->PrintResults();
// 释放结构体 以及线程 资源
if (!sat->Cleanup()) {
logprintf(0, "Process Error: Sat::Cleanup() failed\n");
sat->bad_status();
}
int retval;
// 查看Sat 状态,以便返回 给 终端方便用户了解状态
if (sat->status() != 0) {
logprintf(0, "Process Error: Fatal issue encountered. See above logs for "
"details.\n");
retval = 1;
} elseif (sat->errors() != 0) {
retval = 1;
} else {
retval = 0;
}
// 释放 Sat 类的内存
delete sat;
return retval;
}
Sat类
在main开始会初始化Sat类
Sat *sat = SatFactory();
// 通过调用
Sat *SatFactory() {
return new Sat();
}
-
解析参数,初始化,运行测试,打印结果,清理
-
包含初始化,线程管理,分析和报告函数
-
数据成员用来报错配置参数,控制标志,内存和测试配置,资源和结果
-
利用多线程使用pthreads,并包含特定方法不同类型的压力测试,如内存,文件IO,网络IO,磁盘IO,CPU压力,和缓存一致性测试。
-
使用多个队列结构进行页面管理,同时使用单锁和细锁队列实现
-
页面队列指的是系统中存储内存页一个队列,用于管理内存操作,确保多线程环境下操作的正确性和高效性。
-
单锁: 使用全局唯一锁保护整个队列,一个线程持有,其它线程不能访问。大量线程访问会导致锁竞争,成为性能瓶颈。
-
细锁:局部锁定或多锁模式,意味着对页面队列中每个元素或页面子结构单独加锁,确保多线程访问时,多个线程可以同时获取不同的锁,避免竞争,提高并发性能。
// Enum for page queue implementation switch. 用于页面队列的单锁和细锁模式切换,
enum PageQueueType { SAT_ONELOCK, SAT_FINELOCK };
CPU 压测原理
CPU 压测 主要通过 多线程 并发执行一些计算密集型的任务来实现,任务包括:计算密集型任务,多线程并发执行
- 线程创建 : stressapptest 通过pthread_create() 来创建多个线程,每个线程执行计算任务,线程的数量通常由用户通过-C参数指定。
// Generic CPU stress workload that would work on any CPU/Platform.
// Float-point array moving average calculation.
bool OsLayer::CpuStressWorkload() {
double float_arr[100];
double sum = 0;
#ifdef HAVE_RAND_R
unsignedint seed = 12345;
#endif
// Initialize array with random numbers.
for (int i = 0; i < 100; i++) {
#ifdef HAVE_RAND_R
float_arr[i] = rand_r(&seed);//生成随机数
if (rand_r(&seed) % 2)
float_arr[i] *= -1.0;
#else
srand(time(NULL));
float_arr[i] = rand(); // NOLINT
if (rand() % 2) // NOLINT
float_arr[i] *= -1.0;
#endif
}
// Calculate moving average. 计算滑动平均,频繁修改数组中的每个元素,加大计算量
for (int i = 0; i < 100000000; i++) {
float_arr[i % 100] =
(float_arr[i % 100] + float_arr[(i + 1) % 100] +
float_arr[(i + 99) % 100]) / 3;
sum += float_arr[i % 100];
}
// Artificial printf so the loops do not get optimized away.
if (sum == 0.0)
logprintf(12, "Log: I'm Feeling Lucky!\n");
returntrue;
}
DDR压测原理
DDR内存的压力测试主要 设计大量的内存分配,访问,通过高内存使用,频繁的内存读写操作,以及内存带宽的消耗来进行。
- 内存分配:通过 -M参数,来分配指定大小的内存
// Allocates memory to run the test on
bool Sat::AllocateMemory() {
// Allocate our test memory.
bool result = os_->AllocateTestMem(size_, paddr_base_);
if (!result) {
logprintf(0, "Process Error: failed to allocate memory\n");
bad_status();
returnfalse;
}
returntrue;
}
-
内存读写操作: 在分配的内存区域中进行大量读取和写入
-
顺序读写
-
随机读写
-
内存屏障和同步(在不同线程间进行内存屏障和同步操作,添加内存访问的复杂性
// 简单示例,具体实现 阅读源码,此部分较多
void stress_memory(void* ptr, size_t size) {
volatile char* data = (volatile char*)ptr;
for (size_t i = 0; i < size; i++) {
data[i] = (char)(i % 256); // 写入数据
char temp = data[i]; // 读取数据
}
}
开源地址
RK目录下: external/stressapptest 使用的版本为 1.0.9
最新版本:https://github.com/stressapptest/stressapptest
https://android.googlesource.com/platform/external/stressapptest/
CPU-BPU-DDR 压力测试[1]
参考资料
[1]
CPU-BPU-DDR 压力测试: https://developer.d-robotics.cc/rdk_doc/en/rdk_s/Advanced_development/linux_development/hardware_unit_test/bpu_cpu_ddr_stress
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:瓶子的跋涉 瓶子的跋涉 瓶子的跋涉《stressapptest 压力测试 分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论