各位做SAP性能分析的朋友,尤其是常年跟ABAP慢事务、CPU时间过高的报表打交道的同仁,应该都有过这种经历:用户甩过来一个事务代码,说“这个超级慢”,你去ST05开SQL跟踪、去SAT做运行时分析,折腾半天,最后发现瓶颈不在数据库,不在外部接口,就在应用服务器本身的ABAP CPU时间上。这时候问题就来了——请求进来之后,到底哪一段ABAP代码在疯狂烧CPU?你有没有一套能快速把问题钉在具体代码行的手段?
这篇文章围绕的核心就是Request Entry Point: ABAP CPU Time这个指标,以及我一直在用的一套工作进程采样方法。先说结论:理解了请求入口点的含义,再把工作进程的CPU采样数据跟ABAP代码对应起来,大多数所谓的“神秘CPU瓶颈”都能在半小时内揪出来。文章内容适合SAP技术顾问、ABAP开发、以及做系统性能优化的运维同学,即使你对ABAP不太熟,只要会看SM50、会查STAD,也能跟着实操。
1. 先把两个关键指标彻底弄明白
1.1 Request Entry Point到底代表什么
在SAP系统里,一次用户请求从进入到结束,会经过完整的处理链条。比如用户在前台执行一个报表,这个动作会生成一个“请求”,由调度器分配给某个空闲的工作进程(Work Process)去处理。Request Entry Point,简单说就是这趟请求进入应用服务器时的那个入口位置——它记录了这个请求最初是从哪个会话、哪个事务代码、哪个程序启动的。
很多朋友看STAD(事务代码STAD,静态统计记录)时,忽略了这个字段。其实它是性能分析的起点坐标,决定了你该往哪个方向查:如果入口点是某个报表程序,那问题范围就锁定在报表的数据获取、内部处理、输出环节;如果入口点是某个RFC调用,那要同时考虑被调用系统的耗时。我见过太多排查案例,一开始没看清入口点,把时间浪费在无关模块上,绕了一大圈才回来。所以做任何性能分析,第一件事永远是确认入口点。
1.2 ABAP CPU Time:丢了它,谈何性能优化
ABAP CPU Time指的是请求在ABAP层的处理过程中,工作进程实际消耗的CPU时间。注意两个关键点:一是它只算ABAP层的执行时间,不含数据库执行和等待时间;二是它是“CPU时间”,不是响应时间。响应时间可能因为排队、等待、锁等待变得很长,但CPU时间高,则说明代码本身在进行大量计算、循环、字符串拼接等消耗CPU的运算。
拿日常生活类比,一个人交材料排队等了3个小时,这叫等待时间长;但真正办手续只花了5分钟,这是处理时间。如果发现用户提交之后系统一直在“转圈”,而ABAP CPU Time占了其中很大一块,那基本可以断定:代码层有问题,别去查数据库和网络了。反之,等待时间占总响应时间大头,那么优化重点应该是锁、调度、RFC等待这类阻塞点。所以看到ABAP CPU Time这个指标时,第一反应应该是去审视ABAP代码的执行效率。
2. 为什么常规分析工具不够用,需要采样
2.1 ST05、SAT和STAD各自的边界
很多团队提性能优化,上来就是ST05跟踪SQL,或者SAT做ABAP运行时分析。这俩工具有用,但要清楚它们各自的盲区。
ST05抓的是数据库操作,能让你看清SQL语句、执行计划、数据库锁。但问题在于,如果瓶颈压根不在数据库,ST05给出的数据只能证明“数据库挺快”,对CPU烧在哪这个问题毫无帮助。SAT是ABAP运行时分析器,它的优点是能跟踪到每个语句的执行时间和内存消耗,非常适合代码级微观分析。但SAT有一个很现实的问题:开启分析器本身会产生额外开销,而且对于偶发性的性能问题(比如某种特殊数据条件下才出现的慢),你在SAT里未必能复现出同样的数据路径。更重要的是,SAT更适合开发环境复现问题,生产系统上让用户开着SAT跑一遍,一方面有性能风险,另一方面数据和操作的随机性太强,复现率不高。
STAD是事后统计,能看整体响应时间和CPU时间,但它到不了“代码行”这个粒度,最多告诉你某个报表、某个事务总共花了多长时间。至于性能问题到底出在这段程序的哪一行,STAD说不出来。这时候就需要工作进程采样出手了。
2.2 采样的核心思想:低开销、贴近真实
所谓工作进程采样,原理上很像“拍快照”。你在一个时间窗口内,以固定的时间间隔(比如每2秒、每5秒)去查看各个工作进程当前正在执行的ABAP代码位置。采样的次数足够多之后,把每次“抓到”的代码位置统计出来,占比最高的那几处,基本就是CPU时间被消耗最集中的地方。
这个想法的妙处在于:不需要开启任何运行时分析器,不影响业务请求执行,因为采样动作只是“看一眼”工作进程正在干什么,成本极低。而且采样反映的是生产环境真实负载下代码的实际执行热点,不存在SAT那种复现偏差。就好比要想知道一条街上哪家餐厅生意最好,不用去每家店里查账,只要在高峰期站在路边,每隔几分钟数一数哪家排队的人多,几次统计下来,结论八九不离十。
结合SAP工作进程的架构,每个工作进程都会定期更新自己正在执行的ABAP对象的程序名、源码关键字等信息。系统里的事务SM50可以实时显示每个工作进程在跑什么程序、执行哪个事务、持续了多长时间。那采样的思路就是:以极短的时间间隔不断刷新SM50式的进程视图,记录下每个进程此刻在哪段代码里“烧CPU”,形成统计样本集合。
3. 实操全过程:把CPU时间钉到代码行上
3.1 第一步:拿到性能异常的请求入口
采样之前,必须先锁定目标。做法是看STAD,输入时间范围、用户或者事务代码,筛选出响应时间异常、ABAP CPU Time占比偏高的请求。重点盯着一个字段:请求入口点(Request Entry Point),它一般会显示成类似REPORT某个程序名,或者RFC/某FM的名字。
如果数据量大,建议导出到本地Excel,按ABAP CPU Time降序排。那个CPU时间最高的记录,就是今天采样的主要目标。很多时候你会发现,正是这个入口程序频繁被用户手动执行,响应时间可能不极端,但累积的CPU消耗巨大。这类“大户”才是采样最应该服务的方向。
3.2 第二步:盯住SM50,录制工作进程快照
传统的手动采样方法是人肉盯SM50。把SM50打开,选一个能覆盖到该程序运行的时间窗口,然后每隔几秒刷新一次(SM50有自动刷新功能,可以设置刷新周期),并且用手机或者截图工具记录下每次刷新的画面。
这种方法手工痕迹重,但确实有效。我早期做优化时,靠的就是这个土办法:让用户在测试机上跑慢事务,我这边掐着秒表每3秒截一张SM50图,连续截10分钟,然后把截图里出现最多的程序名统计出来。有次截了快200张图,最后发现某个函数模块出现频率超过60%,一查代码,里面是个双重循环在嵌套调用一个数据库表的行读取,数据量大时CPU全费在循环里了。优化掉这层循环之后,程序响应时间从10秒以上降到了2秒以内。
不过人肉截图的问题在于:样本量受限于刷新速度和人的反应速度,而且统计工作量大。到了生产环境,手工方式就不太合适了。
3.3 第三步:脚本化采样,让样本量说得起话
要想让采样数据具备统计学意义,最佳方式是写一个小工具脚本,自动读取工作进程信息并落盘记录。SAP提供了多种途径获取这类信息,比如远程方式读系统内部表或者使用外部监控接口。作为一个ABAP开发者,你完全可以用ABAP程序直接拉取SM50对应数据源的信息。
核心逻辑大致如下:
REPORT z_cpu_sampler. DATA: lt_processes TYPE TABLE OF tskh, lv_counter TYPE i VALUE 0. DATA: lv_start_time TYPE timestampl. GET TIME STAMP FIELD lv_start_time. DO 60 TIMES. CLEAR lt_processes. CALL FUNCTION 'TH_SERVER_LIST' IMPORTING list = lt_processes. " 这里根据系统版本选合适的数据源 LOOP AT lt_processes INTO DATA(ls_proc) WHERE pstate = 'Running' OR pstate = 'Finished'. " 记录进程号、会话、程序名、当前执行的动作 WRITE: / ls_proc-pid, ls_proc-pname, ls_proc-curr_report. ENDLOOP. WAIT UP TO 3 SECONDS. ENDDO.当然具体取数的方式在不同版本上有差异:较新的系统可以通过读类CL_ALV_SERVICES或者RFC模块来拉取进程信息,也可以直接读取内部表比如TASKH(前提是有权限)。重点不是哪个函数,而是思路:定时抓取进程运行状态,把PID、会话号、当前程序名、当前事务代码存下来。跑完之后,把输出结果导出来,按程序名分组计数,排序。
我实际用的是定时器加内表累积,最后用类CL_GUI_FRONTEND_SERVICES把结果写到服务器本地路径,再用SE38自带工具下载。连续采样10分钟到30分钟,样本量足够做统计了。如果追求更细致的代码行级定位,可以进一步把Working Process当前执行的Statement相关信息也拉进样本。不过要注意,系统版本越高,公开可读的明细字段可能越少,这时候可以折中用截图加OCR的思路,但效率不如直接读表。
3.4 第四步:从进程热点定位到代码行
采样程序跑完之后,你手上会有一张统计表:程序名A出现了多少次、程序名B出现了多少次。但程序名级别的热点还不够,需要继续定位到函数模块、子例程或者方法层面。
方法有两个。第一个是基于命中多的程序名,去SE80里直接看它的局部架构,重点排查主程序内的PERFORM和FUNCTION定义。第二个是借助ABAP运行时分析器(SE30或者SAT),只针对你采样出来的“嫌疑程序”做一次运行时分析。这样一来,SAT的开销不是全程跑,而是聚焦在已经被采样证明“大概率是热点”的那一小块范围,复现率非常高,效率也高。
我个人的做法是:先根据采样统计结果,把前3名嫌疑程序名的全局搜索文本在源代码库里扫一遍,找出这些程序里被循环调用最多、数据处理量最大的代码段,很多时候光看代码结构就能判断可疑点。然后再用SAT对选定的单个程序做细粒度分析,拿到精确到语句的行号级数据,整理成优化清单。
3.5 实战案例复盘:一次典型的磁盘式慢报表
拿一个我处理过的模拟项目来完整复盘。某公司有一套销售订单汇总报表,用户反映月底跑得特别慢,有时候要等一两分钟。STAD里显示总响应时间50秒,其中ABAP CPU时间38秒,数据库时间不到5秒,入口点是这个报表程序ZMM_ORDER_SUM。
一看到CPU时间是大头,基本就锁定了代码层。我先用采样脚本在测试机上让用户复跑了一次,连续采样20分钟(3秒一次,共400条样本)。统计结果非常直观:程序内部的一个子程序PERFORM_CALC_TOTAL被命中了320次,占全部样本的80%。顺着这个子程序打开代码,问题马上暴露:代码里套了一个循环遍历内表,循环体内对每条记录都做了一次字符串拼接和一次日期格式转换,而这两个操作都调用了一些效率偏低的老后兼容写法。数据量接近8万条时,光是嵌套的字符串处理就占了大几十秒。
定位到这一段之后,把循环内的字符串操作挪到循环外统一处理,去掉了无效的日期类型转换调用,改用了字符串变量直接拼接。改完再跑,ABAP CPU时间从38秒降到4秒,总响应时间从50秒降到7秒。整个过程中,采样脚本只跑了一轮,SAT只对目标子程序开了一次,没有影响系统里其他用户的使用。这就是采样这套打法的价值:精准定位、开销极小、整个排查链路直来直去。
4. 采样与定位过程中的常见坑
4.1 采样命中率低,怀疑脚本有问题?
不是每次采样都能在第一个时间窗口内抓到热点。原因往往在于业务请求本身是间歇式的——比如用户只在每个整点跑一次报表,或者同一时间并发量很低,工作进程大部分时间在等任务。这时候要先看进程状态:如果采到的大部分记录都是Waiting或者Idle,那说明采样窗口和业务执行窗口没重叠。建议先通过STAD找到业务执行的高峰时段,再调整采样窗口。
还有一种可能:热点请求根本不在当前指定的应用服务器实例上。生产系统通常是多个实例的集群,请求被负载均衡器分走了,你在实例A采样自然是抓不到命中的。解决方法是先把STAD确认的请求处理实例找出来,再针对那个实例单独做采样。我踩过这个坑一次,浪费了半天,从那以后采样前必看实例号。
4.2 进程数量不够,样本可能失真
假设系统的工作进程数很少(比如只有两三个对话进程),你又开着无数别的报表在跑,那么采样会被其他不相关的进程干扰。统计出来的热点可能不是你要分析的报表,而是另一个正在跑大程序的进程。
对策是锁定PID。更靠谱的做法:启动采样前,先用SM50查一下目标用户的会话对应的工作进程号,记录下这个PID,采样时只统计这个PID对应的执行状态。这样其他进程跑什么都没关系,样本干净得很。我用的脚本里也专门加了这个过滤参数。
4.3 特定场景采不到或不准:RFC、后台任务和并行处理
这里说三个容易踩的盲区。
第一个,RFC调用。如果性能瓶颈发生在对方系统,你采样的系统只是发出调用并等待,那么采样看到的是“等待”状态,CPU时间集中在被调用系统。所以面对RFC场景,应该把采样工作放到被调用系统上。
第二个,后台作业。后台作业不会出现在SM50的对话进程列表里,而是在后台作业进程里。采样脚本如果只抓对话进程,可能直接漏掉。好在大多数后台作业的运行逻辑相对固定,你可以在STAD里看后台作业的CPU时间,再用同样的思路去抓调度进程运行时的状态。
第三个,并行进程(俗称RFC并行)。一个报表可以用并行方式拆成多个子任务,它们各自占用多个工作进程。这时采样命中会分散到多个PID上,单看一个PID看不出热度。处理方式是要求开发人员关闭并行模式复现一次,或者做聚合统计时把所有PID放到一个组里合并计数。
4.4 区分CPU时间与响应时间,避免张冠李戴
采样定格的是“正在执行”的程序,但有种情况要小心:一个进程看起来在程序A里挂很久,但有可能它不是在烧CPU,而是在等某张表的锁释放,或者在等待RFC同步返回。SM50看到的程序位置是“最近一次更新到的位置”,并不等同于“正在全速计算”。
判断方法很简单:请开发人员打开SM50的“CPU时间”列,对比结束时刻的进程CPU时间增量。如果程序A挂着20秒,但CPU时间只涨了0.2秒,那瓶颈是等待;只有CPU时间跟挂起时长同步增长,才是真正的代码CPU热点。这类细节在汇报给领导或者跟业务沟通时特别重要,避免给出错误的优化方向。
5. 采样持续集成与团队协作的那些事
5.1 把采样工具沉淀成团队资产
单次手动采样有效果,但持续性更值钱。我现在习惯的节奏是:写一个可靠的采样报表程序,参数化地支持实例号、PID过滤、采样间隔、采样时长、输出方式,然后定期(比如每周五下午或者月底系统高峰)跑一次,把结果沉淀到一张表里,周复一周地观察热点分布变化。
这样做最大的价值是发现慢SQL之外的“代码层慢性病”。数据库问题通常有迹可循,慢的地方会自动上TOP SQL榜单;但ABAP CPU热点如果没有采样,一线运维根本感知不到。等到用户投诉“系统越来越慢”时,常常已经是多个程序叠加的CPU消耗爆炸了。常态化的采样相当于主动体检,能提前发现那些平时不温不火、但长期累积占用CPU的程序。
建议的统计维度包括:程序名、事务代码、功能菜单路径、运行频次、平均每次CPU时间、采样命中占比。一个月后再汇总,你基本就能画出这张表的“CPU消耗热力图”,哪些程序该优化、哪些程序该停用,一目了然。
5.2 如何跟开发团队沟通采样结果
做性能分析的人往往会遇到一个尴尬:你把采样报告递过去了,开发半天不接单,因为“程序名对不上”“这个事务代码现在没人用”。我的经验是——先转化语言,再传递结果。
采样命中热点以后,不要只给程序名,要再用SAT跑一遍拿到具体语句或函数,把问题描述成业务行为。例如:“ZRP001报表执行时,在日期格式转换这一步耗时占比最高,建议检查转换逻辑中是否存在不必要的循环调用。”这样开发一打开代码就能对上号,不会觉得你在臆测。
还有一些热点是历史遗留代码,没人愿意动,这种时候给出“不动代码也能用的优化方案”会更受欢迎。比如,通过系统参数调整工作进程数量,或者调整程序的运行频率、改为后台调度,把高CPU开销错峰掉。性能优化不是只能改代码这一条路,充分利用采样得出的运行规律,有时候一个调度策略调整带来的改善不亚于代码重构。
5.3 配套指标与工具组合使用
单靠工作进程采样不足以涵盖所有性能问题,还需要一套组合拳。我日常的监控工具箱构成大致如下:
- STAD:总览全局,找出响应时间和CPU高的请求入口点
- 工作进程采样:定位到具体程序、子程序,识别CPU热点
- SAT/SE30:针对嫌疑代码做行级耗时分析
这三者是递进关系,配合得很顺。再加ST05看SQL、看锁统计、看RFC队列,基本可以覆盖90%以上的性能瓶颈场景。每次排查性能问题,我不急着开一堆工具,而是顺着“入口->热点->代码”这条链,逐步收窄范围。工具齐上阵不等于高效,关键是在正确的时候用正确的那一把。
6. 个人实操心得与更进一步的方向
6.1 采样这招的边界,以及怎么补
坦白说,工作进程采样并不是万能的,它的核心优势是低开销、快速定位热点,但分析精度天然不如运行时分析器。它适合回答“到底哪块在烧CPU”,不太适合回答“为什么这一块烧得这么重”——后者还得靠SAT和代码逻辑审查推进。所以在团队里我会把采样当成第一棒,跑完采样再交棒给开发做细化分析。
另外,对于有BP(业务伙伴)版本的系统,或者采用接口化较多的新架构,进程采样有时会因为缓存和异步调用导致命中信息不完整,这个时候要配合网关层日志和接口日志一起看。别神话任何单一技术手段,组合拳始终最稳。
6.2 一次性能治理的关键心得:从小步快跑开始
最后说说我个人的体会。做系统性能治理,最忌讳的是想一口气把所有慢点全给处理掉。我一开始也犯过这毛病,把采样出来的TOP 10热点全部推给开发优化,结果排期打架、业务配合困难,最后推进不下去。后来改成小步快跑的模式:每次只选一个采样命中率最高、代码改动最安全的点做优化,上线后对比前后CPU时间和响应时间,出一个简单明了的收益对比,再推下一个。
这么做的好处是,业务和开发能直观看到性能优化带来的实际效果,积极性自然高。连续优化三四个点之后,整张采样热度表都会凉下来一大截,系统的整体体验提升也特别明显。如果你也想用这套方法治理自己负责的系统,我的建议是从一个“ABAP CPU Time占响应时间比重高、业务抱怨强、代码改动小”的事务开始,跑一轮采样试试看。实际操作过一轮后,你自己就能判断这套打法值不值得长期用下去了。