使用Process Explorer和Clumsy定位软件高CPU占用问题

news/2024/5/6 15:35:27/文章来源:https://blog.csdn.net/chenlycly/article/details/130038272

目录

1、问题描述

2、使用Process Explorer初步找到CPU占用高的原因

3、使用Clumsy工具在公司内网环境复现了问题

4、根据Process Explorer中的函数调用堆栈,分析源码,最终找出了问题

5、总结


       在排查项目客户的视频图像闪烁问题时,无意中发现一个掩藏很深的高CPU占用的bug,本文将详细讲述使用网络环境模拟工具clumsy以及Process Explorer定位该CPU高占用问题的全过程。

1、问题描述

       对某客户的项目即将收尾,目前处于客户试用阶段,如果没什么大问题,一切进展顺利的话,客户就准备进行产品采购了。结果在客户的一台笔记本电脑上出现了严重的视频图像闪烁问题,客户要求必须解决这个问题后才会进行产品采购。

       这个视频闪烁是个很棘手的问题,目前使用的视频编解码库对不同厂商及不同类型的USB摄像头,会有小概率的不兼容问题,正好整个项目中碰到,于是开始了漫长的问题排查过程。

       某天使用向日葵远程到客户的笔记本上,发现系统的CPU占用很高,系统产生了明显的卡顿。查看系统的资源管理器,向日葵软件占了15%左右的CPU,客户还启动了其他的一些软件,这些软件也占了30%左右,我们的软件居然也占到了30%左右!不应该啊,我们软件登陆后什么业务都没做,居然也能占到30%,这肯定是有问题的!

2、使用Process Explorer初步找到CPU占用高的原因

       于是在客户的机器上下载了Process Explorer,使用该工具看一下我们软件进程的各线程的CPU占用情况,看看到底是哪个模块在没做什么操作时就占用了30%左右的CPU。

Process Explorer是我们排查Windows软件问题时使用频率很高的一个工具,主要使用到以下几个功能:
1)可以查看目标进程加载的dll库信息,包括库的路径、库的版本等信息。还可以查看通过LoadLibrary动态启动的库有没有启动起来。
2)可以查看目标进程的线程信息,包括各线程的CPU占用率、线程实时的函数调用堆栈等信息。
3)可以查看目标进程使用GPU硬件的情况(CPU上集成了GPU模块)。很多软件会使用GPU,比如进行视频编解码,可以使用GPU实现硬编硬解(使用CPU的计算能力进行的编解码叫做软编软解),从而有效地减少对CPU占用。

将Process Explorer启动起来后,在进程列表中找到我们的软件进程:

 双击我们的软件进程,弹出进程的详细信息窗口,然后切换到Threads标签页下:

从图中我们可以看出,是线程id为12292的那个线程有问题,居然占了25%左右的CPU!

       双击该线程,查看到该线程当前的函数调用堆栈如下:

函数调用堆栈是与libwebsockets开源库的接口调用有关,我们底层之前有对libwebsockets开源库做过简单的封装,难道是我们封装的有问题?

       我们软件的A业务模块会通过libwebsockts和平台的A服务器进行通信,平台的A服务器是负责A业务的相关事务处理的。但当前我们软件什么都没做,还没有发起A业务的操作,为啥底层的模块会频繁的调用到libwebsockets呢?

       我们软件在登陆时,会和平台的A服务器建立链接去向A服务器注册,并且这个链接是长连接。如果软件底层与A服务器链接断开后,底层会发起自动重连。难道是底层当前登陆不上A服务器,底层在不断的定时重连,而定时重连的代码有问题,导致了高CPU占用?

       于是通过查看打印找到A服务器的地址为172.16.72.235,在客户的笔记本上去ping这个服务器地址,结果服务器地址ping不通:

所以我们基本可以估计出,就是因为服务器连不上,底层在不断的重连该服务器导致高CPU占用的。

3、使用Clumsy工具在公司内网环境复现了问题

       上面大概猜测出是底层在连接不上A服务器的情况下,一直在定时地去重连A服务器导致的高CPU占用问题。但根据Process Explorer中查看到的函数调用堆栈:

对照着源代码,我们并没有找到出问题的点。

       我们不能一直远程客户的电脑,客户还有很多自己的事情要做,于是我们尝试在对我们公司环境中复现这个问题(我们公司内部搭建了多套用于测试的平台环境,有内网环境的,也有公网环境的)。那如何在公司内部复现呢?其实很简单,使用网络工具将与A服务器交互的数据都拦截掉,让客户端软件连不上A服务器就能触发底层的自动重连,应该就能将问题复现出来。

       于是想到一个很好用的、轻量级的网络环境模拟工具clumsy,直接用该工具将客户端发给A服务器的数据包全部拦截掉。

Clumsy是一种弱网络环境模拟工具,可以直接将网络数据拦截掉,也可以设置对目标地址的丢包率,模拟较差的网络环境,它也是我们日常工作中用的比较多的一种软件工具。

       打开clumsy后,默认的过滤条件是:outbound and ip.DstAddr >= 127.0.0.1 and ip.DstAddr <= 127.255.255.255,如下:

我们只需要设置目标地址为平台中A服务器的地址139.224.XXX.XXX(该平台是我们公司内部大搭建的测试平台)即可,即ip.DstAddr == 139.224.XXX.XXX:

然后勾选Drop选项,并将Inbound和Outbound都勾选上,将丢包率设置为100%,这样软件终端发给A服务器数据包都丢弃掉了,这样A服务器就连不上了。注意,可能需要以管理员权限运行该工具(特别是在win10系统中)。
       A服务器连不上,底层就会不断去定时重连,然后问题就复现了,在我们的工作机器上也是占用了较高的CPU,所以更加能确定是重连的代码导致的高CPU占用了。

4、根据Process Explorer中的函数调用堆栈,分析源码,最终找出了问题

       其实这个高CPU占用的bug是有很大的隐蔽性的,在能连上A服务器时没有这个问题,只有连不上A服务器时触发重连,才会出现。       

       于是找来负责维护底层模块的同事,一起来看看他们的代码到底为啥为导致高CPU占用。因为我是负责软件异常排查的,所以会经常协助底层模块的同事去排查各种软件异常问题,比如协议模块、网络模块,音视频编解码模块、组件模块等。

       对照这Process Explorer中显示的函数调用堆栈,找到了源代码的位置,但是详细看了一下这些代码并没有发现明显的破绽!为啥能连上代码执行的没问题,而连不上时这段代码会有问题呢?负责底层模块的同事很忙,开玩笑地说,这个问题既然是服务器连不上,那就让客户查为啥会出现服务器连不上的问题,这个问题先放着吧。这哪成啊!这明显是个很大的隐患,不管客户的环境能否连的上,都必须要解决!

       于是我将他们的代码拿过来,仔细研究了一下,看看到底是怎么回事。重连A服务器的代码放置在一个线程中处理的,相关代码如下:(问题就出在调用lws_service的那句代码上!)

static void* WSSocketProc( void* pParam )
{s_ptContext = CreateContext();if ( NULL == s_ptContext ){MLOG::MLogErr( ML_WEBSOCKET, "[%s] Create Context Failed!!!", __func__ );return NULL;}if ( FALSE ==OspSemBCreate( &s_hWsiCloseSem ) ){MLOG::MLogErr( ML_WEBSOCKET, "[%s] s_hWsiCloseSem Inited Failed!!!", __func__ );return NULL;}SemGive( g_hWSInitSem );while ( TRUE ){CheckSvrConnect();SemTake( s_hWsiCloseSem );std::vector<u64>::iterator itWsi = s_vecToBeClosedWsi.begin();for ( ; s_vecToBeClosedWsi.end() != itWsi; ++itWsi ){SemTake( g_hSessionIDSem );std::map<u64, std::string>::iterator itSessionID = g_mapSessionID.find( *itWsi );if ( g_mapSessionID.end() != itSessionID ){bClientForceClose = TRUE;lws_close_free_wsi( (lws *)( *itWsi ) , LWS_CLOSE_STATUS_NOSTATUS );}SemGive( g_hSessionIDSem );}s_vecToBeClosedWsi.clear();SemGive( s_hWsiCloseSem );// 问题就出在这句代码上,在没有websockets连接时,该接口没有起到sleep的作用lws_service( s_ptContext, LWS_SERVICE_TIMEOUT );if ( s_bExitSocketProc ){MLOG::MLogHint( ML_WEBSOCKET, "[%s] SocketProc Thread Exit!!!", __func__ );OspSemDelete( s_hWsiCloseSem );s_vecToBeClosedWsi.clear();break;}}lws_context_destroy( s_ptContext );return NULL;
}

按讲,在使用一个线程去处理事务时必须要加一个Sleep,不能让线程一直在运行,否则线程一直在占用CPU时间片,一直占用着CPU就会导致高CPU占用,类似于死循环。对于程序员来说,这是个常识!

代码中确实好像也有Sleep,通过调用libwebsockets的接口lws_service时传入了一个超时的参数,估计是通过该函数实现的Sleep的功能。

       能否不调用这个lws_service,直接调用Sleep接口呢?答案是不可以,go到lws_service接口实现处,查看lws_service接口的注释:

/*** lws_service() - Service any pending websocket activity* @context:    Websocket context* @timeout_ms:    Timeout for poll; 0 means return immediately if nothing needed*        service otherwise block and service immediately, returning*        after the timeout if nothing needed service.**    This function deals with any pending websocket traffic, for three*    kinds of event.  It handles these events on both server and client*    types of connection the same.**    1) Accept new connections to our context's server**    2) Call the receive callback for incoming frame data received by*        server or client connections.**    You need to call this service function periodically to all the above*    functions to happen; if your application is single-threaded you can*    just call it in your main event loop.**    Alternatively you can fork a new process that asynchronously handles*    calling this service in a loop.  In that case you are happy if this*    call blocks your thread until it needs to take care of something and*    would call it with a large nonzero timeout.  Your loop then takes no*    CPU while there is nothing happening.**    If you are calling it in a single-threaded app, you don't want it to*    wait around blocking other things in your loop from happening, so you*    would call it with a timeout_ms of 0, so it returns immediately if*    nothing is pending, or as soon as it services whatever was pending.*/LWS_VISIBLE int
lws_service(struct lws_context *context, int timeout_ms)
{return lws_plat_service(context, timeout_ms);
}

上述注释的翻译如下:

This function deals with any pending websocket traffic, for three kinds of event.  It handles these events on both server and client types of connection the same.
1) Accept new connections to our context's server
2) Call the receive callback for incoming frame data received by server or client connections.
此函数处理任何需要处理的(悬而未决的)websocket 流量,适用于三种事件。它以相同的方式处理服务器和客户端连接类型上的这些事件。
1) 接受到我们上下文服务器的新连接
2) 调用回调函数,将服务器或客户端连接接收到的数据,回调出去。

You need to call this service function periodically to all the above functions to happen; if your application is single-threaded you can just call it in your main event loop.
你需要周期性地调用这个服务函数来使上述所有函数发生; 如果您的应用程序是单线程的,您可以在主事件循环中调用它。

从上面的注释可以看出,要保证libwebsockets库中能正常的收发数据,必须要调用lws_service接口。

       那为什么连上服务器时上述代码运行有问题,而连不上服务器时就会有问题呢?估计是连不上服务器时,libwebsockets中就没有有效的websockets连接,当调用到lws_service接口时该接口立即返回了,即该接口没有起到Sleep的作用!应该就是这个原因引起的。

       最后的解决办法是,在线程函数中添加一个Sleep:(在调用lws_service的下方加上一句Sleep)

lws_service( s_ptContext, LWS_SERVICE_TIMEOUT );Sleep( LWS_SERVICE_TIMEOUT ); // 需要人为地添加sleep,以保证当前线程有睡眠时间

不管在什么情况都要执行一定时间的Sleep。修改代码后,将库编译出来,覆盖到软件的目录中,重新运行软件后就不再有问题了。

5、总结

       作为软件开发人员很有必要去掌握一些常用工具的使用,通过这些工具来辅助排查我们软件产品在运行中遇到的各种问题,可以有效地提高排查问题的效率。

       在本例中,正是通过查看Process Explorer工具的函数调用堆栈找到了出问题的代码块,通过Clumsy工具在公司环境中复现了问题。正是依靠这些工具,我们才逐步的定位和解决问题的。

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

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

相关文章

Centos7安装部署Jenkins

Jenkins简介&#xff1a; Jenkins只是一个平台&#xff0c;真正运作的都是插件。这就是jenkins流行的原因&#xff0c;因为jenkins什么插件都有 Hudson是Jenkins的前身&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控程序重复的工作&#xff0c;Hudson后来被…

JavaScript基础-02

常量&#xff08;字面量&#xff09;&#xff1a;数字和字符串 常量也称之为“字面量”&#xff0c;是固定值&#xff0c;不可改变。看见什么&#xff0c;它就是什么。 常量有下面这几种&#xff1a; 数字常量&#xff08;数值常量&#xff09;字符串常量布尔常量自定义常量…

【MATLAB数学建模编程实战】Kmeans算法编程及算法的简单原理

欢迎关注&#xff0c;本专栏主要更新MATLAB仿真、界面、基础编程、画图、算法、矩阵处理等操作&#xff0c;拥有丰富的实例练习代码&#xff0c;欢迎订阅该专栏&#xff01;&#xff08;等该专栏建设成熟后将开始收费&#xff0c;快快上车吧~~&#xff09; 【MATLAB数学建模编…

[LeetCode周赛复盘] 第 340 场周赛20230409

[LeetCode周赛复盘] 第 340 场周赛20230409 一、本周周赛总结二、 6361. 对角线上的质数1. 题目描述2. 思路分析3. 代码实现三、6360. 等值距离和1. 题目描述2. 思路分析3. 代码实现四、6359. 最小化数对的最大差值1. 题目描述2. 思路分析3. 代码实现五、 6353. 网格图中最少访…

ROS实践06 自定义消息类型

文章目录运行环境&#xff1a;思路&#xff1a;1.1 定义.msg文件1)功能包下新建 msg 目录&#xff0c;添加文件 Person.msg2)修改package.xml3)修改CMakeLists.txt2.1 自定义消息调用(C)1&#xff09;编译后修改includePath2&#xff09;发布方实现2.1修改CMakeLists.txt2.3运行…

【OpenCV-Python】cvui 之 trackbar

CVUI 之 trackbar cvui::trackbar() 渲染一个 trackbar&#xff0c; 可以左右拖动或点击对数字进行增加或减少的调整。 不使用离散间隔 使用离散间隔 Python import numpy as np import cv2 import cvuidef trackbar_test():WINDOW_NAME Trackbar-Test# 创建画布frame np.z…

【Python童年游戏】满满的回忆杀—那些年玩过的童年游戏你还记得吗?那个才是你的菜?看到第一个我就泪奔了(致我们逝去的青春)

导语 滴一一学生卡&#x1f64c; 结伴上车的学生仔子们 用笑声打破车厢的沉默 大人眼里的晚高峰 是给放学后快乐&#x1f600;时光的加时 下车的学生匆匆起身带起 一阵熟悉的栀子香于&#x1f493; 是关于校园的记忆 开始零零散散地闪现 放学后集合的秘密基地/跟着城…

LVGL v8学习笔记 |12 - 移植LVGL 8.3到ESP32C3开发板(ST7789)

一、移植前的准备 1. 基础工程 ESP32-IDF开发笔记 | 03 - 使用SPI外设驱动ST7789 SPILCD2. lvgl源码 https://github.com/lvgl/lvgl下载最新发布的 8.3.6 版本:https://github.com/lvgl/lvgl/releases/ 二、移植lvgl 1. 复制lvgl源码到工程中 将下载的 lvgl-8.3.6 文件夹直…

JSON数据遍历之for-in

JSON数据遍历之for-in object 本身就是无对象的集合&#xff0c;因此在用 for-in 语句遍历对象的属性时&#xff0c;遍历出的属性顺序与对象定义时不同。 W3C标准 根据 ECMA-262&#xff08;ECMAScript&#xff09;第三版中描述&#xff0c;for-in 语句的属性遍历的顺序是由对…

KlayGE-001-简介

KlayGE 引擎学习-001 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWlSlet9-1680688988724)(images/KlayGE_logo.png)] 一、KlayGE引擎介绍 软件简介 KlayGE中文译为&#xff1a;粘土游戏引擎&#xff0c;是一个开源、跨平台&#xff0c;基于插…

6-MATLAB APP Design-表格组件(uitable)

此博文通过MATLAB APP Design实现对学生成绩的处理,具体的功能包括读取表格数据、添加学生数据、计算总成绩、成绩排序+以及表格的保存。 一、APP 界面设计展示 1. 在画布中拖入面板、表格和四个按钮,布局如下。将面板的title写为“学生成绩计算器”并居中,将四个按钮的t…

游戏开发之Unity2021熟悉基本工具

接上一节通用渲染管线项目搭建 导入天空盒素材&#xff1a;在窗口中选择资源商店后会弹出下面的图片&#xff0c;在资源商店中找到我们想要的天空盒素材&#xff0c;将素材在unity中打开&#xff0c;如下面的第二幅图中就是我选择的天空盒素材&#xff0c;在这里可能会遇到一个…

Centos7搭建Ngrok内网穿透

一、安装gcc和git(用于下载ngrok源码) yum install gcc -y yum install git -y 二、安装go语言环境 yum install -y mercurial git bzr subversion golang golang-pkg-windows-amd64 golang-pkg-windows-386 三、检查环境安装 git --version //( > 1.7 ) go version 四…

通读《技术管理实战36讲》1、自我倾听篇

你好&#xff0c;我是小Z&#xff0c;一个工作在交付前线的程序员&#xff0c;我们正在通读《技术管理实战36讲》&#xff0c;作者刘建国。今天我们要梳理的章节是“自我倾听篇”。 在第1篇《多年前的那些工程师都去哪了&#xff1f;》中&#xff0c; 作者借助上周的“老知道人…

linux系统中cat命令的详细用法

在Linux中&#xff0c;cat命令是一个很常用的命令&#xff0c;它的作用是将文件内容输出到屏幕上&#xff0c;或者将多个文件合并成一个文件。下面是cat命令的一些常用用法&#xff1a; ​1. 显示文件内容 使用cat命令可以打印出文件的内容&#xff0c;如&#xff1a; cat fi…

[Qt 教程之Widgets模块] —— QFormLayout表单布局

Qt系列教程总目录 文章目录一、创建QFormLayout二、成员函数2.1. 对行操作2.2. 操作布局项2.3. 间距2.4. 设置布局规则2.5. 对齐方式表单布局 QFormLayout 以两列形式布局其子项。左列由标签组成&#xff0c;右列由小部件&#xff08;行编辑器、数字调整框等&#xff09;组成。…

FPGA实现图像去雾 基于暗通道先验算法 纯verilog代码加速 提供2套工程源码和技术支持

目录1、前言2、目前我这里已有的图像处理方案3、暗通道先验算法介绍4、本图像去雾模块的优缺点5、vivado工程详解vivado工程1详解vivado工程2详解6、上板调试验证7、福利&#xff1a;工程源码获取1、前言 本文详细描述了FPGA实现图像去雾的实现设计方案&#xff0c;采用暗通道…

C++文件加密篇(基于char数组进行可逆加密)

严格意义上的加密算法有对称加密算法和非对称加密算法&#xff0c;对称加密算法是指加密与解密的key相同&#xff0c;而非对称加密算法是指加密&#xff08;使用公钥&#xff0c;所有人都可以获取&#xff09;与解密&#xff08;使用私钥&#xff0c;只有指定方有私钥&#xff…

Robosense激光雷达Linux配置

文章目录1.1 速腾rs16连接&#xff1a;1.2 网络配置1&#xff09;官方说明2&#xff09;设置网络3&#xff09;检查是否连接成功2.1 激光雷达ROS包下载/编译1)下载ROS包2&#xff09;安装libpcap依赖3&#xff09;修改编译模式4&#xff09;config文件配置5&#xff09;编译并运…

【数据结构与算法】一、数据结构的基本概念

文章目录一、数据结构的基本概念1.1 数据结构的研究内容1.2 数据类型和抽象数据类型1.3 算法和算法分析1.3.1 算法的时间复杂度1.3.2 算法时间效率的比较1.4 知识回顾一、数据结构的基本概念 1.1 数据结构的研究内容 1.2 数据类型和抽象数据类型 抽象数据类型&#xff08;ADT…