stressapptest压力测试分析

admin 2026-03-09 01:46:48 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档详细介绍了stressapptest压力测试工具的功能、参数及在Android系统中的集成与调用方法。首先阐述了工具原理及CPU压测实现,涉及多线程浮点计算与锁机制。其次提供了高通与RK平台的编译集成步骤,并给出了APK通过Root权限调用二进制文件的Java代码示例。文档兼具理论分析与实战代码,为开发者进行系统稳定性测试与工具移植提供了可操作的指导方案。 综合评分: 83 文章分类: 安全工具,移动安全,实战经验


cover_image

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 copy
  • i: number of memoy invert threads to run 反转线程数 Invert Copy
  • c : CRC Check CRC校验 Data Check
  • C: 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&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;100; i++) {
#ifdef&nbsp;HAVE_RAND_R
&nbsp; &nbsp; float_arr[i] = rand_r(&seed);//生成随机数
&nbsp; &nbsp;&nbsp;if&nbsp;(rand_r(&seed) %&nbsp;2)
&nbsp; &nbsp; &nbsp; float_arr[i] *=&nbsp;-1.0;
#else
&nbsp; &nbsp; srand(time(NULL));
&nbsp; &nbsp; float_arr[i] = rand(); &nbsp;// NOLINT
&nbsp; &nbsp;&nbsp;if&nbsp;(rand() %&nbsp;2) &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// NOLINT
&nbsp; &nbsp; &nbsp; float_arr[i] *=&nbsp;-1.0;
#endif
&nbsp; }

// Calculate moving average. 计算滑动平均,频繁修改数组中的每个元素,加大计算量
for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;100000000; i++) {
&nbsp; &nbsp; float_arr[i %&nbsp;100] =
&nbsp; &nbsp; &nbsp; (float_arr[i %&nbsp;100] + float_arr[(i +&nbsp;1) %&nbsp;100] +
&nbsp; &nbsp; &nbsp; &nbsp;float_arr[(i +&nbsp;99) %&nbsp;100]) /&nbsp;3;
&nbsp; &nbsp; sum += float_arr[i %&nbsp;100];
&nbsp; }

// Artificial printf so the loops do not get optimized away.
if&nbsp;(sum ==&nbsp;0.0)
&nbsp; &nbsp; logprintf(12,&nbsp;"Log: I'm Feeling Lucky!\n");
returntrue;
}

DDR压测原理

DDR内存的压力测试主要 设计大量的内存分配,访问,通过高内存使用,频繁的内存读写操作,以及内存带宽的消耗来进行。

  • 内存分配:通过 -M参数,来分配指定大小的内存
// Allocates memory to run the test on
bool&nbsp;Sat::AllocateMemory()&nbsp;{
// Allocate our test memory.
bool&nbsp;result = os_->AllocateTestMem(size_, paddr_base_);
if&nbsp;(!result) {
&nbsp; &nbsp; logprintf(0,&nbsp;"Process Error: failed to allocate memory\n");
&nbsp; &nbsp; bad_status();
&nbsp; &nbsp;&nbsp;returnfalse;
&nbsp; }
returntrue;
}
  • 内存读写操作: 在分配的内存区域中进行大量读取和写入

  • 顺序读写

  • 随机读写

  • 内存屏障和同步(在不同线程间进行内存屏障和同步操作,添加内存访问的复杂性

// 简单示例,具体实现 阅读源码,此部分较多
void&nbsp;stress_memory(void* ptr,&nbsp;size_t&nbsp;size)&nbsp;{
&nbsp;volatile&nbsp;char* data = (volatile&nbsp;char*)ptr;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(size_t&nbsp;i =&nbsp;0; i < size; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data[i] = (char)(i %&nbsp;256); &nbsp;// 写入数据
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;temp = data[i]; &nbsp;// 读取数据
&nbsp; &nbsp; &nbsp; &nbsp; }
}

开源地址

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 压力测试 分析》

评论:0   参与:  0