Use Process Explorer to view the thread's function call stack to troubleshoot the program's high CPU usage.

Table of contents

1. Problem description

2. General ideas for using Process Explorer to troubleshoot high CPU usage of software

3. Use Process Explorer tool for analysis

3.1. Find threads with high CPU usage

3.2. Check the function call stack of the thread with high CPU usage to find the problematic code.

3.3. Description of the libwebsockets library export interface lws_service

3.4. Solution

4. You may need the pdb symbol file when using Process Explorer to view the function call stack.

5. Finally


Summary of the development of common functions of VC++ (list of column articles, welcome to subscribe, continuous updates...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585 C++ software anomaly troubleshooting series of tutorials from entry to proficiency (list of column articles) , welcome to subscribe and continue to update...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931 C++ software analysis tools from entry to mastery case collection (column article is being updated...) icon-default.png?t=N7T8https:/ /blog.csdn.net/chenlycly/article/details/131405795 C/C++ basics and advanced (column article, continuously updated...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html        A few days ago I used the Process Explorer tool to troubleshoot a software high CPU usage problem on a colleague's computer. Today I will use this example to fully describe how to use Process Explorer to troubleshoot high CPU usage problems.

1. Problem description

       A few days ago, when a colleague was using our software, the system experienced obvious lags. Looking at Windows Explorer, we found that our software process occupied nearly 50% of the CPU, which caused obvious lags in the system. Check the software interface and it shows that it is reconnecting to the server:

        A colleague was using a laptop. According to feedback from his colleague, he was previously connected to a mobile hotspot (connected to the Internet through the mobile data of his mobile phone). Later, he turned off the hotspot on his mobile phone, so the laptop could no longer connect to the Internet. The software is connected to multiple business servers. After the network is disconnected, the connections to the servers will be disconnected one after another. The software will automatically initiate a reconnection to each server. Because the notebook cannot connect to the external network, the bottom layer of the software has been continuously disconnected. The ground is reconnected. This high CPU usage may be triggered by the server's automatic reconnection . This high CPU problem is bound to occur in this problem scenario and can be reproduced with simple operations.

2. General ideas for using Process Explorer to troubleshoot high CPU usage of software

       When we encounter high CPU usage of software, we usually use the Process Explorer tool to troubleshoot. Use this tool to view all threads in the process and the proportion of CPU occupied by each thread:

Then double-click the thread with the highest CPU usage, view the thread's function call stack, and compare it with the source code to analyze the high CPU usage problem.

       High CPU usage is generally caused by the program executing code non-stop. The main scenarios of non-stop code execution include the following:

1) An infinite loop appears in the program, and the program has been executing the code in the loop body where the infinite loop occurred.
2) Sleep is not added to the loop body (usually the While loop) in the thread function, causing the loop body code to be executed continuously.

Therefore, when troubleshooting high CPU usage problems of programs, these two troubleshooting directions should be mainly considered.

3. Use Process Explorer tool for analysis

3.1. Find threads with high CPU usage

       On the computer with the problem, open Process Explorer, find the target software process in the process list, double-click the process entry to open the process's properties page, click the Threads tab, and you will see the thread list page.

       Generally, high CPU usage of a process is caused by a certain thread, and this thread will have obviously high CPU usage. You can click on the CPU usage column in the header of the thread list and sort according to the CPU usage ratio. You can see that a certain thread obviously occupies a higher CPU, as shown below:

​Double-click the thread entry with high CPU usage to view the function call stack of the thread, as follows:

​ Sometimes, we need to click the refresh button in the lower left corner multiple times to view multiple function call stacks to see a valid function call stack. Generally, when an infinite loop occurs in the code or the while loop in the thread function continuously executes the code, the function call stacks viewed multiple times using Process Explorer are similar or the same.

3.2. Check the function call stack of the thread with high CPU usage to find the problematic code.

       Through the function call stack shown, the wsswrapperlib library is calling the interface in the open source library libwebsockets. This wsswrapperlib library is a module of the underlying protocol stack. It is understood that the current client software communicates with a server on the platform side through libwebsockets. From the current stack, it is always connecting to the remote server. This is consistent with the interface layer display of reconnecting to the server. You can see the word WorkerThread in the wsswrapperlib module from the stack, which shows that the current function call stack is related to this WorkerThread thread.

       I have also encountered the problem of improperly calling lws_service of libwebsockets before, resulting in high CPU usage of the program. At that time, the problem also occurred in the scenario of automatic reconnection to the server. For corresponding questions, please refer to the article I wrote before:
Using Process Explorer and Clumsy Tools to Locate Software High CPU Usage Issues icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/130038272         So I found a protocol stack colleague who maintains the wsswrapperlib module. Let him search the lws_service interface directly in the code, and sure enough he found the thread function WorkerThread corresponding to a thread, and called the lws_service interface in the While loop in the thread function:

static void* WorkerThread(void* lpParam)
{
    int n = 0:
    CWSSContext* context = (CWSSContext*)1pParam;
    while (n >=0
#ifdef __ANDROID__
        && context->flag()
#endif
    )
    {
        n = lws_service( context->getContext,50 )
        context->po11();
    }

    return NULL;
}

3.3. Description of the libwebsockets library export interface lws_service

The description of the lws_service interface in the libwebsockets open source library is as follows:

/**
 * 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);
}

The translation of the above comment is as follows:

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.
This function handles any (pending) websocket traffic that needs to be processed, and works for three events. It handles these events in the same way on server and client connection types.
1) Receive a new connection from our context server
2) Call the callback function to call back the data received by the server or client connection.

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
. If your application is single-threaded, you can call it in the main event loop.

As can be seen from the above comments, to ensure that the libwebsockets library can send and receive data normally, the lws_service interface must be called.

3.4. Solution

       When the server cannot be connected, the second timeout parameter passed by calling the lws_service function cannot play the role of Sleep. We don't care what the lws_service function does internally, but the lws_service function will return quickly, which will cause the While to continue without stopping. Executed locally, it will occupy a large amount of CPU time slices, causing the thread to occupy a large proportion of the CPU. In order to prevent the thread code from running continuously, a Sleep must be artificially added here. The code is as follows:

static void* WorkerThread(void* lpParam)
{
    int n = 0:
    CWSSContext* context = (CWSSContext*)1pParam;
    while (n >=0
#ifdef __ANDROID__
        && context->flag()
#endif
    )
    {
        n = lws_service( context->getContext,50 )
        context->po11();
        
        Sleep(50);  // 人为地去Sleep一下
    }

    return NULL;
}

Generally, Sleep calls must be artificially added to business threads to prevent threads from running continuously and causing high CPU usage.

4. You may need the pdb symbol file when using Process Explorer to view the function call stack.

       The interface lws_service involved in this example is the exported interface in the open source library libwebsockets.dll. The exported interface is publicly visible to the outside, so the call to the lws_service interface can be directly seen in the function call stack. If the interface involved is a non-public interface within the library, and you want to see the detailed function name in the function call stack, you need the pdb symbol file of the corresponding module. The pdb symbol file can be placed in advance in the directory at the same level as the binary file (usually the installation directory of the program). When Process Explorer needs to load a pdb file, it will search for the pdb file in the directory where the exe main program is located, and automatically load it after searching.

       Of course, you can also place the pdb symbol files in a certain directory, and then set the directory to Process Explorer . The specific operation method is to click Options -> Configure Symbols... in the Process Explorer menu bar to open the following configuration window:

Just configure the path to the pdb file.

       There are other tools that may use pdb symbol files, please see the article I wrote before:

Which software analysis tools need to use pdb symbol files? icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131574418

5. Finally

       This article describes how to use Process Explorer to troubleshoot high CPU usage problems of programs through a specific example, which has certain reference or reference value. In this example, Process Explorer is used to view the function call stack of a thread in the process. The Process Explorer tool has other uses. The uses of Process Explorer can be summarized as follows:

1) Process Explorer can view the total virtual memory occupied by the entire process of the program , but it cannot be seen in the Windows Task Manager. It can be used when troubleshooting memory leaks.
2) Process Explorer can see which libraries are loaded by the target program and the paths of these libraries . You can also see whether the dynamically loaded dll library is loaded.
3) Process Explorer can see what command line parameters are passed to the program process when starting the program . For example, when the chrome browser is running, multiple processes will be started. Each process is responsible for handling different transactions. We can pass the command line parameters passed to the process. Know what tasks this process does, such as the render rendering process and the GPU acceleration thread.
4) Process Explorer can view the function call stack of each thread of the startup program to troubleshoot infinite loops, high CPU usage and multi-thread deadlock issues.

There are many cases of using Process Explorer to troubleshoot problems in my blog . You can refer to the related articles in the column "C++ Software Analysis Tool Case Collection":

Collection of C++ software analysis tools from entry to mastery (column article is being updated...) icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795

Guess you like

Origin blog.csdn.net/chenlycly/article/details/132830803