您的位置 首页 心理科普

apm测试【apm测试手速测试】

在JaCoCo出现之前,Java程序员已经有了一些代码覆盖率工具可供选择,如Cobertura、Emma等。但这些工具要么功能有限,要么配置复杂、效率低下。JaCoCo诞生于2009年,旨在提供一个可靠、高效且易于集成的代码覆盖率解决方案。

  • 高效:JaCoCo直接在运行时插桩字节码,无需进行额外的构建步骤,大大提高了效率
  • 易于集成:JaCoCo可以轻松集成到Maven、Ant等构建工具中,与CI/CD无缝衔接
  • 全面覆盖:支持多种代码覆盖类型,如指令、分支、循环、行、方法等
  • 丰富报告:生成HTML/XML/CSV格式报告,支持多种报告视角和过滤选项
  • 跨平台:工作于JVM之上,支持所有基于Java的应用程序

代码覆盖率分析日益成为高质量软件交付的标配,JaCoCo作为主流工具将持续获得支持和增强。未来或将完善对最新Java版本和框架的支持,提升可视化报告质量,简化工作流程集成等。

  1. 无侵入性插桩利用Java Agent机制,在目标JVM启动时注入JaCoCo的运行时代理代理分析类文件,找到需要插桩的位置,并修改字节码指令修改后的类将记录运行时的执行路径,用于覆盖率统计
  2. 支持多种覆盖类型通过ASM字节码分析和修改框架,精确分析Java字节码结构识别方法、分支、循环等代码结构,进行精准插桩运行时跟踪这些代码结构的执行情况,统计覆盖率数据
  3. 生成报告汇总运行时收集的原始执行数据根据报告格式需求,将原始数据与源代码进行映射处理使用模板生成HTML/XML/CSV格式报告,包括统计数据和源码高亮视图
  4. 轻量高效只在被测程序的运行时进行一次字节码改写,不影响编译过程插桩代码只添加少量指令,与原程序指令平行执行,开销很小无需频繁读写文件,执行性能接近不插桩场景
  5. 报告可视化使用JavaScript和CSS渲染HTML报告,生成代码高亮视图报告分为文件/包/类等多个层级,支持交互式展开折叠条形图将不同覆盖类型直观展示,辅助分析质量状况
  6. CI集成为Maven、Ant、Gradle等主流构建工具提供插件扩展将覆盖率报告作为质量关卡,阻止低覆盖率应用部署上线与CI服务器JenkinsBamboo等无缝集成,实现持续交付
  1. JaCoCo Agent加载通过Java命令行参数-javaagent启动JaCoCo代理或者在运行时,通过Instrumentation接口动态加载代理代码JaCoCo代理初始化,准备修改应用字节码
  2. 字节码扫描JaCoCo利用ASM框架,扫描应用所有的类文件和字节码识别需要插桩的目标代码,如分支、循环、方法调用等
  3. 插桩改写在目标代码处插入计数器,用于统计执行路径覆盖情况识别已插桩过的类,避免重复插桩浪费资源
  4. 执行跟踪代理启动执行数据服务器,用于接收每个计数器的数据应用执行时,相应计数器将执行路径数据发送给服务器
  5. 数据采集执行数据服务器实时汇总收集计数器执行数据应用退出前,JaCoCo代理保存最终的执行数据文件
  6. 报告生成读取执行数据文件,结合未插桩的类文件,生成汇总报告根据报告格式要求,渲染统计数据和源码视图
  • Java Agent:Java Agent是JVM启动时通过特殊选项指定的代码,可以对JVM进行监控、修改、增强等操作。JaCoCo以Java Agent形式注入代码,实现字节码插桩。
  • ASM字节码工具:JaCoCo利用了ASM框架对Java字节码进行分析和修改。ASM提供了访问者模式API,可精确遍历、操作字节码指令序列。
  • 执行数据服务器:JaCoCo的执行数据服务器充当数据收集器,用于接收插桩后的应用代码发送的覆盖率数据,汇总统计最终数据。
  1. 排除无需测试的代码通过配置规则排除第三方类、实用类等无需测试覆盖的代码避免这些代码拉低整体覆盖率,影响质量评估准确性
  2. 针对产品覆盖率设置目标根据产品特点和质量要求,设置合理的覆盖目标新产品起步阶段,可设置70%以上的目标循序渐进提升成熟产品应设置90%以上较高目标,确保质量长期可控
  3. 集成到CI/CD流程将JaCoCo覆盖率检查集成到持续集成和交付流程中构建验证通过后自动生成报告,低于阈值时阻止部署上线持续监控和反馈覆盖率,指导测试用例优化和补充
  4. 代码审计和评审定期审计JaCoCo反馈的未覆盖代码评估这些未覆盖代码的重要性及潜在风险针对高危区域补充测试用例,提高覆盖率
  5. 与测试框架集成JaCoCo可与主流单元测试框架如JUnit、TestNG等集成在运行测试用例时自动启动JaCoCo跟踪覆盖率简化了测试、质量管理的工作流,高效获取覆盖率反馈
  1. 插桩开销JaCoCo插桩后的应用会有一定程度的性能损耗实际生产环境不应保留插桩代码运行,仅开发测试时启用
  2. 报告一致性生成报告的源码版本要与被测试的班次保持一致避免类删除、移动等导致源码映射错位
  3. 多线程场景JaCoCo需要同步多线程并发执行数据开销会随线程数增加而线性增长
  4. 易遗漏的覆盖场景枚举类、内部类等容易被测试用例遗漏多线程、异常等场景也需注意测试覆盖
  5. 自动生成代码对于框架或反射自动生成的代码,JaCoCo无法进行插桩应通过其他手段如mock或集成测试补充这部分覆盖率
  1. 当 CI/CD 管道运行时,它首先会执行编译构建和单元测试的任务。这个阶段,我们通常会通过配置 Maven 或 Gradle 插件来启动 JaCoCo agent。例如,如果使用 Maven,我们可以在 pom.xml 中添加 JaCoCo 插件的配置。
  2. 在配置JaCoCo agent时,你需要为其指定一个输出目录,该目录用于存放生成的覆盖率报告。当单元测试运行时,JaCoCo agent 会对字节码进行增强,跟踪执行路径,然后收集覆盖率数据。
  3. 当单元测试运行结束后,JaCoCo agent 会生成覆盖率报告(比如 XML 或 HTML 格式),并将其保存到你之前指定的输出目录。
  4. 然后,可以使用一些 CI/CD 插件(比如 Jenkins 的 JaCoCo plugin)来解析这些覆盖率报告,展示覆盖率信息。
  5. 在测试任务完成,JVM 关闭时,JaCoCo agent 也会被自动卸载。因为 JaCoCo agent 是以 JVM 参数的形式加载的,其生命周期与 JVM实例一致。

也就是当 CI/CD 流程的构建任务开始时,它会启动一个新的 JVM 进程,并在这个进程中运行构建脚本(例如 Maven 或 Gradle 脚本),加载 JaCoCo agent,并执行单元测试。这所有的过程都是在这一个 JVM 进程中完成的。当构建任务结束后,这个 JVM 进程就会关闭,同时也会卸载 JaCoCo agent。但是这并不会影响你的应用的正常运行,因为你的应用运行在另一个独立的 JVM 进程中

  1. 增加测试用例:检查是否有未覆盖的代码段或路径,为这些部分编写对应的测试用例。
  2. 进行边界值和异常情况测试:确保测试中包含判断逻辑的边界情况,例如为空、为0、为极限值等情况。同时需要测试异常处理路径,例如处理无效输入的逻辑。
  3. 优化代码结构:如果代码中存在过于复杂的逻辑判断和多重嵌套,可以考虑重构代码,以简化控制流程。
  4. 使用白盒测试:白盒测试指的是直接测试代码的内部逻辑,这能帮助我们找到哪些路径未被覆盖。
  5. 导入正确的依赖:确保所有的测试库和代码库都正确导入,未被误引导至错误的库中。
  6. 使用Mock对象:某些情况下,为了测试一段代码,可能需要编写复杂的上下文环境。在这种情况下,可以使用Mock对象来创建这些上下文。

最后,覆盖率是一个参考指标,不能完全代表代码质量。重要的是要做到对产品功能和业务逻辑的全面覆盖。

  • 直接使用JaCoCo不支持增量测试覆盖率报告,它将报告所有测试的覆盖率,而不只是针对某次commit的增量修改部分代码。
  • 如果需要得到增量覆盖率报告,就需要整合其他的工具。例如,“SonarQube”就支持增量覆盖率管理,能够结合CI/CD工具,在每次代码修改后触发增量覆盖率分析。也就是说,结合SonarQube,可以看到针对某次commit修改部分的覆盖率数据。
  • 注意,SonarQube默认情况下会分析全量的代码。为了得到每次commit的修改的增量覆盖率报告,你需要在设置中启用增量模式(Leak Period模式),设置为previous_version,这样它将只分析自上次版本发布以来的新代码。

如何用 GPU硬件层加速优化Android系统的游戏流畅度

作为一款VR实时操作游戏App,我们需要根据重力感应系统,实时监控手机的角度,并渲染出相应位置的VR图像,因此在不同 Android 设备之间,由于使用的芯片组和不同架构的GPU,游戏性能会因此受到影响。举例来说:游戏在 Galaxy S20+ 上可能以 60fps 的速度渲染,但它在HUAWEI P50 Pro上的表现可能与前者大相径庭。 由于新版本的手机具有良好的配置,而游戏需要考虑基于底层硬件的运行情况。

如果玩家遇到帧速率下降或加载时间变慢,他们很快就会对游戏失去兴趣。如果游戏耗尽电池电量或设备过热,我们也会流失处于长途旅行中的游戏玩家。如果提前预渲染不必要的游戏素材,会大大增加游戏的启动时间,导致玩家失去耐心。如果帧率和手机不能适配,在运行时会由于手机自我保护机制造成闪退,带来极差的游戏体验。

基于此,我们需要对代码进行优化以适配市场上不同手机的不同帧率运行。

首先我们使用Streamline 获取在 Android 设备上运行的游戏的配置文件,在运行测试场景时将 CPU 和 GPU性能计数器活动可视化,以准确了解设备处理 CPU 和 GPU 工作负载,从而去定位帧速率下降的主要问题。

以下的帧率分析图表显示了应用程序如何随时间运行。

在下面的图中,我们可以看到执行引擎周期与 FPS 下降之间的相关性。显然GPU 正忙于算术运算,并且着色器可能过于复杂。

为了测试在不同设备中的帧率情况,使用友盟+U-APM测试不同机型上的卡顿状况,发现在onSurfaceCreated函数中进行渲染时出现卡顿, 应证了前文的分析,可以确定GPU是在算数运算过程中发生了卡顿:

因为不同设备有不同的性能预期,所以需要为每个设备设置自己的性能预算。例如,已知设备中 GPU 的最高频率,并且提供目标帧速率,则可以计算每帧 GPU 成本的绝对限制。

数学公式: $ 每帧 GPU 成本 = GPU 最高频率 / 目标帧率 $

CPU到 GPU 的调度存在一定的约束,由于调度上存在限制所以我们无法达到目标帧率。另外,由于 CPU-GPU 接口上的工作负载序列化,渲染过程是异步进行的。CPU 将新的渲染工作放入队列,稍后由 GPU 处理。

CPU控制渲染过程并且实时提供最新的数据,例如每一帧的变换和灯光位置。然而,GPU 处理是异步的。这意味着数据资源会被排队的命令引用,并在命令流中停留一段时间。而程序中的OpenGL ES 需要渲染以反映进行绘制调用时资源的状态,因此在引用它们的 GPU 工作负载完成之前无法修改资源。

我们曾做出尝试,对引用资源进行代码上的编辑优化,然而当我们尝试修改这部分内容时,会触发该部分的新副本的创建。这将能够一定程度上实现我们的目标,但是会产生大量的 CPU 开销。

于是我们使用Streamline查明高 CPU 负载的实例。在图形驱动程序内部libGLES_Mali.so路径函数, 视图中看到极高的占用时间。

由于我们希望在不同手机上适配不同帧率运行,所以需要查明libGLES_Mali.so是否在不同机型的设备上都产生了极高的占用时间,此处采用了友盟+U-APM来检测用户在不同机型上的函数占用比例。

经友盟+ U-APM自定义异常测试,下列机型会产生高libGLES_Mali.so占用的问题,因此我们需要基于底层硬件的运行情况来解决流畅性问题,同时由于存在问题的机型不止一种,我们需要从内存层面着手,考虑如何调用较少的内存缓存区并及时释放内存。

基于前文的分析,我们首先尝试从缓冲区入手进行优化。单缓冲区方案• 使用glMapBufferRange和GL_MAP_UNSYNCHRONIZED.然后使用单个缓冲区内的子区域构建旋转。这避免了对多个缓冲区的需求,但是这一方案仍然存在一些问题,我们仍需要处理管理子区域依赖项,这一部分的代码给我们带来了额外的工作量。多缓冲区方案• 我们尝试在系统中创建多个缓冲区,并以循环方式使用缓冲区。通过计算我们得到了适合的缓冲区的数目,在之后的帧中,代码可以去重新使用这些循环缓冲区。由于我们使用了大量的循环缓冲区,那么大量的日志记录和数据库写入是非常有必要的。但是有几个因素会导致此处的性能不佳:1. 产生了额外的内存使用和GC压力2. Android 操作系统实际上是将日志消息写入日志而并非文件,这需要额外的时间。3. 如果只有一次调用,那么这里的性能消耗微乎其微。但是由于使用了循环缓冲区,所以这里需要用到多次调用。我们会在基于c#中的 Mono 分析器中启用内存分配跟踪函数用于定位问题:

$ adb shell setprop debug.mono.profile log:calls,alloc

我们可以看到该方法在每次调用时都花费时间:

Method call summary Total(ms) Self(ms) Calls Method name 782 5 100 MyApp.MainActivity:Log (string,object[]) 775 3 100 Android.Util.Log:Debug (string,string,object[]) 634 10 100 Android.Util.Log:Debug (string,string)

在这里定位到我们的日志记录花费了大量时间,我们的下一步方向可能需要改进单个调用,或者寻求全新的解决方案。

log:alloc还让我们看到内存分配;日志调用直接导致了大量的不合理内存分配:

Allocation summary Bytes Count Average Type name 41784 839 49 System.String 4280 144 29 System.Object[]

最后尝试引入硬件加速,获得了一个新的绘图模型来将应用程序渲染到屏幕上。它引入了DisplayList 结构并且记录视图的绘图命令以加快渲染速度。

同时,可以将 View 渲染到屏幕外缓冲区并随心所欲地修改它而不用担心被引用的问题。此功能主要适用于动画,非常适合解决我们的帧率问题,可以更快地为复杂的视图设置动画。

如果没有图层,在更改动画属性后,动画视图将使其无效。对于复杂的视图,这种失效会传播到所有的子视图,它们反过来会重绘自己。

在使用由硬件支持的视图层后,GPU 会为视图创建纹理。因此我们可以在我们的屏幕上为复杂的视图设置动画,并且使动画更加流畅。

代码示例:

// Using the Object animator view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); objectAnimator.start(); // Using the Property animator view.animate().translationX(20f).withLayer().start();

另外还有几点在使用硬件层中仍需注意:

(1)在使用之后进行清理:

硬件层会占用GPU上的空间。在上面的 ObjectAnimator代码中,侦听器会在动画结束时移除图层。在 Property animator 示例中,withLayers() 方法会在开始时自动创建图层并在动画结束时将其删除。

(2)需要将硬件层更新可视化:

使用开发人员选项,可以启用“显示硬件层更新”。如果在应用硬件层后更改视图,它将使硬件层无效并将视图重新渲染到该屏幕外缓冲区。

但是由此带来了一个问题是,在不需要快速渲染的界面,比如滚动栏, 硬件层也会更快地渲染它们。当将 ViewPager 滚动到两侧时,它的页面在整个滚动阶段会以绿色突出显示。

因此当我滚动 ViewPager 时,我使用 DDMS 运行 TraceView,按名称对方法调用进行排序,搜索“android/view/View.setLayerType”,然后跟踪它的引用:

ViewPager#enableLayers(): private void enableLayers(boolean enable) { final int childCount = getChildCount(); for (int i = 0; i< childCount; i++) { final int layerType = enable ? ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; ViewCompat.setLayerType(getChildAt(i), layerType, null); } }

该方法负责为 ViewPager 的孩子启用/禁用硬件层。它从 ViewPaper#setScrollState() 调用一次:

private void setScrollState(int newState) { if (mScrollState == newState) { return; } mScrollState = newState; if (mPageTransformer != null) { enableLayers(newState != SCROLL_STATE_IDLE); } if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(newState); } }

正如代码中所示,当滚动状态为 IDLE 时硬件被禁用,否则在 DRAGGING 或 SETTLING 时启用。PageTransformer 旨在“使用动画属性将自定义转换应用于页面视图”(Source)。

基于我们的需求,只在渲染动画的时候启用硬件层,所以我想覆盖ViewPager 方法,但由于它们是私有的,我们无法修改这个方法。

所以我采取了另外的解决方案:在 ViewPage#setScrollState() 上,在调用enableLayers() 之后,我们还会调用OnPageChangeListener#onPageScrollStateChanged()。所以我设置了一个监听器,当 ViewPager 的滚动状态不同于 IDLE 时,它将所有 ViewPager 的孩子的图层类型重置为 NONE:

@Override public void onPageScrollStateChanged(int scrollState) { // A small hack to remove the HW layer that the viewpager add to each page when scrolling. if (scrollState != ViewPager.SCROLL_STATE_IDLE) { final int childCount =.getChildCount(); for (int i = 0; i< childCount; i++).getChildAt(i).setLayerType(View.LAYER_TYPE_NONE, null); } }

这样,在 ViewPager#setScrollState() 为页面设置了一个硬件层之后——我将它们重新设置为 NONE,这将禁用硬件层,因此而导致的帧率区别主要显示在 Nexus上。

作者:陈可心

原文链接:http://click.aliyun.com/m/1000306394/

用户评论


陌然淺笑

终于找到专业的APM测试平台了!之前自己折腾各种工具测个APM效率好费事,这个网站界面简洁直观,操作简单方便,手动的输入和自动测试都支持,很人性化!

    有7位网友表示赞同!


凝残月

APM测试虽然很科学,但结果是否能反应真实工作成效呢?我感觉这种测速更像是看一个人“敲代码”的手法,而不是真正编程水平的表现啊。比如写逻辑复杂的程序,单纯看手速或许没太大用。

    有13位网友表示赞同!


灵魂摆渡人

这个网站的APM测试功能很好用!之前在面试的时候,经常被问到APM效率,心里总有点压力啊,现在有了这个平台可以提前练习,很有帮助呢!

    有11位网友表示赞同!


大王派我来巡山!

我感觉自己写代码时比较专注,不追求速度,有时候反而效率更高。APM测试虽然能反应反应速度,但忽略了我们思考和解决问题的能力啊!

    有20位网友表示赞同!


夏日倾情

这款网站的测试非常全面,不仅涵盖了常见的按键操作,还包含一些特殊字符输入的测试,能更全面地评估我们的打字精度和准确率。很棒!

    有19位网友表示赞同!


北朽暖栀

做程序员最重要的可不是手速,而是代码质量和逻辑思维能力!这种APM测试只关注表面,没法准确反映一个开发者真正的技术水平。

    有6位网友表示赞同!


咆哮

我觉得这个网站的"手速测试"更像是一种趣味游戏,可以用来放松一下心情,锻炼反应速度。但是不能将结果太严肃化啊!

    有17位网友表示赞同!


窒息

对于一些需要快速输入代码的程序员来说,这个APM测试确实很有用处。比如做前端开发、手游开发等,手速快能提高工作效率和产出速度。

    有5位网友表示赞同!


柠栀

我经常参加ACM比赛,在编程挑战中,手疾眼快是相当重要的!所以一直在关注一些能提升手速的工具和平台,这个网站挺实用的!

    有19位网友表示赞同!


肆忌

最近面试了几家公司,都提到了APM测试,现在开始练习了这个网站上的功能。我觉得还是很有帮助的,至少能在面试中展示出我的操作水平。

    有20位网友表示赞同!


念旧情i

这个网站挺酷啊,可以挑战好友看看谁的手速更快,更有互动性! 虽然平时编程的时候我不追求速度,但是玩玩这个测试还是蛮有意思的!

    有5位网友表示赞同!


追忆思域。

对于新手来说,建议不要把APM测试看得太重。更重要的是掌握扎实的编程基础和解决问题的思维方式。手速只是辅助工具。

    有18位网友表示赞同!


拉扯

我感觉这个网站的"APM测试"功能挺有用的,特别是对于在竞赛环境中需要快速码代码的程序员来说。可以帮助他们进行能力训练和自我评估

    有16位网友表示赞同!


经典的对白

最近面试的时候被问到了APM效率,不知道该怎么准备? 这个网站上提供了蛮多测试用例,能让我练习一下手感,到时候再进面试也更有底气了!

    有17位网友表示赞同!


还未走i

这个网站的功能还设计的不完善,比如不能自定义测试内容和场景,测试流程比较单调。 希望能加入更多个性化定制功能,更好满足用户的需求。

    有8位网友表示赞同!


珠穆郎马疯@

想尝试一下别的APM测试平台,感觉这个网站的界面有点过于简洁,缺乏一些引导提示信息,对于新手不太友好!

    有20位网友表示赞同!


浮世繁华

手速只是一些硬件和习惯的问题,我更注重逻辑思维能力和代码设计。这种单纯测试手速的方式,其实并不能反映一个程序员真正的实力。

    有11位网友表示赞同!