第3.4节 调用链路分析服务开发

3.4.1 什么是Code Call Graph(CCG)

Code Call Graph(CCG)即业务代码中的调用关系图,是通过静态分析手段分析并构建出的一种描述代码间关系的图。根据精度不同,一般分为类级别、方法级别、控制流级别,本文重点在方法级别上。
我们以一段代码进行举例:
class A {public void funA1() {funA2();C c = new C();c.funC1();}public void funA2() {B b = new B();b.funB1();}
}class B {public void funB1() {funB2();}public void funB2() {if (randN(10) < 5) {Logger.log("Hello B2");} else {funB2()}}
}class C {public void funC1() {B b = new B();b.funB2();}
}

如上代码所构建出的方法级别的CCG是这样的:

1,CCG的作用主要有两个

假设当我们出现一个需求改动到
1,funB1该方法时,我们可以从该图上进行逆向查找,找到所有直接调用或者间接调用该方法的所有方法A2,A1,这个代表对B2的改动,会影响到A2,A1,{B1, A2, A1}即方法B2的代码影响域。
2,在单元测试场景下,如果某个测试用例
testX是针对funC1的测试,那么我们可以从该方法上进行正向查找,找到所有它直接调用或者间接调用的方法B2,这个代表,我的测试用例testX的执行后可以测试到方法C1, B2,{B2, C1}即用例testX的关联代码。

2,CCG的应用场景

除了可以应用在精准测试场景下之外,还能在如下场景应用:
1,app启动或页面启动场景下的性能分析与性能优化:当我们要进行某个场景下的耗时优化时,我们可以从几个核心入口函数如android的下的application.onCreate(),application.onBaseContextAttached()的方法作为起点,查找后续调用方法,获知在整个启动流程里,哪些方法通过什么方式被执行了,帮助判断这种执行是否是启动场景下必须执行的任务。
2,组件化解耦:当我们需要判断两个组件间的耦合关系时,我们可以以其中一个组件中的方法作为起点,查找调用链上是否有另一个组件的方法,来寻找两个组件间的详细耦合关系,帮助后续进行解耦。相比传统静态分析方案,CCG可以更准确高效的查找出非直接依赖的隐性耦合。

3.4.2. CCG构建业界方案一览

目前业界有一部分相对完成度比较高的开源callgraph或者AST生成方案:

Android/Java

1,soot/wala等静态代码分析框架:GitHub - soot-oss/soot: Soot - A Java optimization framework,soot是比较完善的静态代码分析框架,从能力设计上都符合我们的需求,但是soot本身是一个通用性框架,没有专门为call graph场景去设计,比如匿名内部类,Runnable/Callable/Thread等线程类,lambda表达式,Stream调用,泛型处理等等,这些都需要我们去对soot做定制才能达到我们的需求。此外仅针对callgraph生成场景,soot设计是过复杂的,导致对于百万级方法节点的处理性能并不足够好。

2,java-all-call-graph: GitHub - Adrninistrator/java-all-call-graph: Generate all call graph for Java Code.,这个项目是一个比较简单的基于class字节码分析生成callgraph的方案,解决了soot的各种缺失能力,同时在处理性能上要优于soot。但是仍然存在一定缺陷,比如无法支持使用Redux框架进行开发的代码,反射,广播等场景。

iOS/Objc-c, swift

1,Drafter: GitHub - L-Zephyr/Drafter: 在iOS项目中自动生成类图和方法调用图 - Generate call graph in iOS project,Drafter是一个简单的语法+词法分析器,由于不带语义信息,只能支持单个类下的call graph生成,不符合我们的需求。
2,libTooling:官方工具,独立AST生成工具,libTooling可以生成一个完整的带语义分析的AST,我们可以基于该AST来生成所需的call graph,但是libTooling的性能非常差(需要为每个文件或者模块生成编译参数,并且无法应用各种编译优化),在全量情况下快手app的call graph生成耗时达到数小时,增量情况下一个500行文件的生成耗时达到几十秒,对于大型mr无法承受。
3,Clang Plugin:官方工具,集成进编译流程中的AST生成插件,clang plugin方案通过集成在编译流程里,目标产物为语义AST,由于可集成在编译流程中,我们可以复用包括gundum在内的各种编译优化手段,在增量情况下每个文件的生成耗时可以降到秒级,全量情况下为十几分钟。但是clang plugin只能支持oc代码,并且我们无法直接将打包集群的编译环境替换掉,因此我们需要在clang plugin基础上搞定swift/c++/c的支持,以及跨语言构建问题。同样的,我们还需要支持泛型、代理、redux、广播、KVO、oc runtime等特殊场景。

3.4.3 现在的CCG整体架构

CCG服务提供了Android,IOS调用钏的生成,序列化保存,查询等相关功能,以及对git diff获取diff函数的相关功能和接口。

3.4.4 . 流水线上CCG服务构建与更新流程

随着代码的改动,CCG需要同步更新,因此CCG服务需要与流水线深度关联。CCG服务主要分为3个阶段:

1,全量构建阶段

CCG全量构建基于定时触发,每隔固定时间(目前为24小时),CCG服务平台会触发一次双端全源码包构建请求,完成一次全量CCG构建,流程如下
如前文提到,CCG构建时需要使用特定jenkins脚本构建相关产物,获得产物后通过相关分析脚本得到完整版CCG:
生成出的完整版本CCG大概长上面这个样子,每个节点代表一个方法,我们需要存储该方法本身信息,其所属函数、参数列表,指向的前序与后序方法节点。对于一个超大型应用而言,我们可能有几百万个方法节点,这种存储方式最后得到的CCG产物十分庞大,内存占用达到几GB,显然这对内存、磁盘甚至CPU都是很大的负担(一个CCG服务器上需要同时维护多份CCG)。
因此我们在构建出全量CCG后,引入了CCG压缩流程,压缩后的CCG会被分成两部分:CCG-Node Map,CCG-Meta DB。压缩后的每个节点上只存储了该Node的hash值,并以此hash值作为key,构建meta DB,存储详细信息。在后续查询时,我们从Node Map中拿到对应的hash list后再从数据库中做一次sql查询,即可得到完整信息。这种方案也有利于获取扩展更多的节点信息,比如我们要增加线上用户热度图(见5.5)信息时,只需要在meta DB中插入即可。
全量构建完成的CCG我们称之为Base CCG,会以commitID作为版本号进行持久化。

2. 更新阶段

由于每个mr提交后都会改变局部CCG,因此我们需要引入实时CCG更新方案。我们选择引入git webhook监听所有mr merge操作,当一个mr合并入主分支后(dev分支),会触发CCG更新机制。整个更新机制分成两种平台,四种场景:
Android(复用产物分析)
场景1. 如果mr有新增/更新/删除单元测试case,一定会触发单元测试节点,此时我们根据mr diff与已经打好的单元测试包做一次增量分析,得到增量CCG
场景2. 如果mr没有相关单元测试case改动,我们根据mr diff与已经打好的编译检查包做一次增量分析,得到增量CCG
场景3. 如果因为各种原因没有匹配的编译检查包,我们需要触发一次jenkins debug包打包,再结合mr diff进行增量分析,得到增量CCG
iOS(无法使用产物结果,需要重新进行语义AST分析)
场景1. iOS场景下,在mr merge触发后,会直接触发jenkins打包服务,构建语义AST,与全量构建场景不同的是,这种场景下只会触发增量编译,因此语义AST构建只针对mr diff中的增量文件进行触发(目前主站增量编译是pod级缓存,AST构建也是pod级,当文件级缓存上线后,AST构建也将变成文件级)。
增量更新的CCG我们称之为Diff CCG,以mrId + 最新commitId作为索引值持久化,该CCG唯一绑定某个版本的Base CCG(取决于基于哪个base版本进行的diff),并存储指向对应版本的Base CCG的文件指针。
Diff CCG寻找绑定Base CCG的算法可以简化描述为:从提交mr对应的开发分支上向前寻找到最近的与dev分支的共同祖先节点,以这个节点commitId作为基准值,再向前寻找最邻近的关联有Base CCG的commitId,该Base CCG即目标CCG。
Question1. 为什么可以使用开发分支上的编译产物获取增量CCG并合入dev分支后的CCG?
事实上CCG的merge操作和git代码的merge操作是类似的,由于开发分支合并入dev分支时,代码层面一定不存在冲突,因此我们可以保证CCG merge时也不存在冲突。
另一方面对于代码层面的merge,最终可以归类为三种情况:add method,change method,delete method。这几种情况,反应到CCG上对应于添加一个方法节点,修改某个方法节点的出边,删除一个方法节点,可以实现一一对应。
因此我们在开发分支上获得增量CCG可以与当前mr的diff代码保证一一对应,merge进主干分支的CCG上时也等价于mr merge进主干分支。
Question2. 如果CCG更新太慢,后续mr所基于的dev分支代码已经领先于最新CCG会出现什么后果?
由于指向dev分支的merge操作是保证原子时序性的(不会出现两个merge操作并发执行),因此我们对于git merge的webhook也是时序性,在CCG更新操作上我们采用了同步非阻塞设计,即当出现一次merge操作后,我们触发更新操作,该更新操作会被push到执行队列中并立刻返回,执行队列是一个顺序的任务队列,保证前一个更新任务完全完成后,后一个才会执行。
当出现一个查询任务时,如果该查询任务所基于dev分支节点的CCG还未更新完成,为了避免阻塞等待,我们会使用最邻近CCG进行查询,这会带来一定程度的误差(事实上这种误差可以忽略,大多数情况下不会存在两个mr在很短的时间内去更新同一个功能模块)。

3,查询阶段

用户提交mr后,会通过流水线触发代码影响域查询服务,输入为mr diff文件,输出为受影响的方法list,以及相关权重信息。
  1. 查询可以分为两个阶段:
  2. mr diff分析找到所有改动方法
根据改动方法,在CCG上找到所有受影响的方法
MR Diff分析
MR分析阶段,我们会根据mr diff信息使用前置分析器找到变动方法,为了确保分析性能(MR分析阶段需要足够快,否则会影响整个流水线的执行速度),我们引入/自研了高性能的词法语法分析器作为我们的前置分析器,可以非常快的构建出一棵不带语义信息的AST。关于前置分析器的具体实现细节可见3.4、3.5节。
CCG查询阶段
拿到变动方法列表后,我们还无法直接进行查询,因为在CCG平台上存储的是一系列Base CCG和Diff CCG,我们需要找到并构建出我们的mr所匹配的CCG。
考虑如下的一个Git分支模型:
我们从Dev分支拉出Task分支后,当我们第一次提交mr到流水线上时(图中create MR),我们的CCG服务会基于该mr的最新commit(红色分支第二个节点)向前寻找最近一次checkout/merge/rebase Dev分支的节点(绿色第二个节点),找到该节点后,向CCG持久化服务中搜索对应的CCG图,此时我们找到了MR1-1 CCG,这是一个Diff CCG,该Diff CCG中存储了它依赖的Base CCG指针,然后对这两个CCG进行一次merge操作,即得到了我们想要的CCG(即CCG v1),在该图上即可进行后续的查询服务。当我们后续有新的commit提交到MR上时,重复上述操作即可获得新的CCG版本。
在具体查询上,我们根据变动类型将查询分为三类:
  1. 新增方法:新增方法不会影响其它方法,并且在没有匹配的新增case时,该新增方法也不会存在关联存量用例,而对于新增用例的场景,这些用例无论怎么样都会直接推荐,因此在CCG阶段直接忽略新增方法
  2. 删除方法:删除方法不会影响其它方法,也不会对测试产生影响,直接忽略
  3. 变更方法:变更方法是我们唯一需要进行查询的场景,我们以变更方法作为起始节点,向前追溯该方法的所有前序节点,即可获得对应变更方法的代码影响域,为了提供更多信息,我们会引入更多的权重信息来辅助后续的推荐策略,在第五章中会作详述。
至此整个CCG服务完成,作为一个单独的服务,为整个精准测试平台提供调用链路查询和分析相关功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/905477.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Liblib】基于LiblibAI自定义模型,总结一下Python开发步骤

一、前言 Liblib AI&#xff08;哩布哩布 AI&#xff09;是一个集成了先进人工智能技术和用户友好设计的 AI 图像创作绘画平台和模型分享社区。 强大的图像生成能力 &#xff1a;以 Stable Diffusion 技术为核心&#xff0c;提供文生图、图生图、图像后期处理等功能&#xff…

编程日志5.5

树的结构代码 #include<iostream> using namespace std; //由于树的每个结点可能有一些孩子结点,这些孩子结点的数量不确定,所以可以用一个链表来把所有的孩子结点给串起来 //链表结点定义 //这段代码定义了一个结构体ListNode,用于表示链表中的一个结点。这个结构…

【消息队列】RabbitMQ基本认识

目录 一、基本概念 1. 生产者&#xff08;Producer&#xff09; 2. 消费者&#xff08;Consumer&#xff09; 3. 队列&#xff08;Queue&#xff09; 4. 交换器&#xff08;Exchange&#xff09; 5. 绑定&#xff08;Binding&#xff09; 6. 路由键&#xff08;Routing …

uniapp -- 验证码倒计时按钮组件

jia-countdown-verify 验证码倒计时按钮组件 一个用于发送短信验证码的倒计时按钮组件&#xff0c;支持自定义样式、倒计时时间和文本内容。适用于各种需要验证码功能的表单场景。 代码已经 发布到插件市场 可以自行下载 下载地址 特性 支持自定义按钮样式&#xff08;颜色、…

知识图谱重构电商搜索:下一代AI搜索引擎的底层逻辑

1. 搜索引擎的进化论 从雅虎目录式搜索到Google的PageRank算法&#xff0c;搜索引擎经历了三次技术跃迁。而AI搜索引擎正在掀起第四次革命&#xff1a;在电商场景中&#xff0c;传统的「关键词匹配」已无法满足个性化购物需求&#xff0c;MOE搜索等新一代架构开始融合知识图谱…

深度学习 自然语言处理(RNN) day_02

1. 感知机与神经网络 1.1 感知机 生物神经元&#xff1a; 1.1.1 感知机的概念 感知机&#xff08;Perceptron&#xff09;&#xff0c;又称神经元&#xff08;Neuron&#xff0c;对生物神经元进行了模仿&#xff09;是神 经网络&#xff08;深度学习&#xff09;的起源算法&am…

PYTHON训练营DAY25

BUG与报错 一、try else try:# 可能会引发异常的代码 except ExceptionType: # 最好指定具体的异常类型&#xff0c;例如 ZeroDivisionError, FileNotFoundError# 当 try 块中发生 ExceptionType 类型的异常时执行的代码 except: # 不推荐&#xff1a;捕获所有类型的异常&…

LU分解求解线性方程组

L U LU LU分解 前言 L U LU LU分解 由以下定理得以保证&#xff1a; 设 A \boldsymbol{A} A为 n n n阶方阵&#xff0c;若其各界阶顺序主子式都不为 0 0 0&#xff0c;那么它可以 被唯一的上下三角矩阵积分解。 步骤 确定各矩阵形式 A L U \mathbf{A}\mathbf{LU} ALU ( a 1…

Linux——数据库备份与恢复

一&#xff0c;Mysql数据库备份概述 1&#xff0c;数据库备份的重要性 数据灾难恢复&#xff1a;数据库可能会因为各种原因出现故障&#xff0c;如硬件故障、软件错误、误操作、病毒攻击、自然灾害等。这些情况都可能导致数据丢失或损坏。如果有定期的备份&#xff0c;就可以…

SVM在医疗设备故障维修服务决策中的应用:策略、技术与实践

SVM在医疗设备故障维修服务决策中的应用&#xff1a;策略、技术与实践 医疗设备的高可靠性、安全性及严格合规性要求&#xff0c;使其故障维修决策具有显著的特殊性。支持向量机&#xff08;SVM&#xff09;凭借小样本学习、非线性建模及高精度分类能力&#xff0c;可有效解决…

WEB安全--Java安全--CC1利用链

一、梳理基本逻辑 WEB后端JVM通过readObject()的反序列化方式接收用户输入的数据 用户编写恶意代码并将其序列化为原始数据流 WEB后端JVM接收到序列化后恶意的原始数据并进行反序列化 当调用&#xff1a; ObjectInputStream.readObject() JVM 内部逻辑&#xff1a; → 反…

FlashInfer - 介绍 LLM服务加速库 地基的一块石头

FlashInfer - 介绍 LLM服务加速库 地基的一块石头 flyfish 大型语言模型服务中的注意力机制 大型语言模型服务&#xff08;LLM Serving&#xff09;迅速成为重要的工作负载。Transformer中的算子效率——尤其是矩阵乘法&#xff08;GEMM&#xff09;、自注意力&#xff08;S…

反向操作:如何用AI检测工具优化自己的论文“人味”?

大家好&#xff0c;这里是论文写手的一线自救指南&#x1f624; 在AIGC横行的今天&#xff0c;谁还没偷偷用过AI写几段论文内容&#xff1f;但问题来了&#xff1a;学校越来越会“识AI”了&#xff01; 有的学校甚至不看重复率&#xff0c;只盯AIGC率报告&#xff0c;一句“AI…

关于单片机的基础知识(一)

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于单片机基础知识的相关内容&#xf…

AWS技术助力企业满足GDPR合规要求

GDPR(通用数据保护条例)作为欧盟严格的数据保护法规,给许多企业带来了合规挑战。本文将探讨如何利用AWS(亚马逊云服务)的相关技术来满足GDPR的核心要求,帮助企业实现数据保护合规。 一、GDPR核心要求概览 GDPR的主要目标是保护欧盟公民的个人数据和隐私权。其核心要求包括: 数…

FFplay 音视频同步机制解析:以音频为基准的时间校准与动态帧调整策略

1.⾳视频同步基础 1.2 简介 看视频时&#xff0c;要是声音和画面不同步&#xff0c;体验会大打折扣。之所以会出现这种情况&#xff0c;和音视频数据的处理过程密切相关。音频和视频的输出不在同一个线程&#xff0c;就像两个工人在不同车间工作&#xff0c;而且不一定会同时…

车载网关--- 职责边界划分与功能解耦设计

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

最优化方法Python计算:有约束优化应用——近似线性可分问题支持向量机

二分问题的数据集 { ( x i , y i ) } \{(\boldsymbol{x}_i,y_i)\} {(xi​,yi​)}&#xff0c; i 1 , 2 , ⋯ , m i1,2,\cdots,m i1,2,⋯,m中&#xff0c;特征数据 { x i } \{\boldsymbol{x}_i\} {xi​}未必能被一块超平面按其标签值 y i ∈ { − 1 , 1 } y_i\in\{-1,1\} yi​∈…

aardio - 将文本生成CSS格式显示

import win.ui; /*DSG{{*/ var winform win.form(text"aardio form";right759;bottom469) winform.add( button{cls"button";text"Button";left340;top130;right430;bottom180;z3}; edit{cls"edit";text"我是一串文本";lef…

数字IC后端设计实现 | 如何自动删除Innovus 中冗余的hold buffer?

我们都知道在postCTS阶段做optDesign时序优化时需要进行hold violation的fixing。所以这个过程势必要通过插hold buffer来解决hold violation。这类hold buffer的名字带有"PHC"的关键词。 select_obj [dbGet top.insts.name PHC] llength [dbGet top.insts.name PH…