Utilisez Process Explorer et Clumsy pour localiser les problèmes d'utilisation élevée du processeur par les logiciels

Table des matières

1. Description du problème

2. Utilisez Process Explorer pour rechercher initialement la cause de l'utilisation élevée du processeur

3. Utiliser l'outil Clumsy pour reproduire le problème dans l'intranet de l'entreprise

4. Selon la pile d'appels de fonction dans Process Explorer, analysez le code source et enfin découvrez le problème

5. Résumé


       Lors du dépannage du problème de scintillement de l'image vidéo du client du projet, j'ai accidentellement découvert un bogue profondément caché avec une utilisation élevée du processeur.Cet article décrira en détail l'ensemble du processus d'utilisation de l'outil de simulation d'environnement réseau maladroit et de Process Explorer pour localiser l'utilisation élevée du processeur problème.

1. Description du problème

       Le projet d'un certain client touche à sa fin et il est actuellement en phase d'essai client.S'il n'y a pas de problème majeur et que tout se passe bien, le client est prêt à acheter des produits. Le résultat a été un scintillement important de l'image vidéo sur l'un des ordinateurs portables du client, et le client a exigé que le problème soit résolu avant que tout achat de produit puisse être effectué.

       Ce scintillement vidéo est un problème très difficile. La bibliothèque de codecs vidéo actuellement utilisée a une faible probabilité d'incompatibilité avec différents fabricants et différents types de caméras USB. Il s'est produit dans l'ensemble du projet, donc un long processus de dépannage a commencé. .

       Un jour, j'ai utilisé Sunflower pour accéder à distance à l'ordinateur portable du client et j'ai constaté que l'utilisation du processeur du système était très élevée et que le système présentait un décalage évident. Vérifiez le gestionnaire de ressources du système, le logiciel de tournesol représente environ 15 % du processeur, le client a également démarré d'autres logiciels, ces logiciels représentaient également environ 30 %, notre logiciel représentait également environ 30 % ! Cela ne devrait pas être le cas, nous n'avons fait aucune affaire après la connexion au logiciel, et cela peut encore représenter 30%, ce qui doit être un problème !

2. Utilisez Process Explorer pour rechercher initialement la cause de l'utilisation élevée du processeur

       J'ai donc téléchargé Process Explorer sur la machine du client et utilisé cet outil pour examiner l'utilisation du processeur de chaque thread de notre processus logiciel et voir quel module occupait environ 30% du processeur lorsqu'il ne faisait rien.

Process Explorer est un outil que nous utilisons fréquemment lors du dépannage de problèmes logiciels Windows. Il utilise principalement les fonctions suivantes :
1) Vous pouvez afficher les informations de la bibliothèque dll chargées par le processus cible, y compris le chemin de la bibliothèque, la version de la bibliothèque et d'autres informations. Vous pouvez également vérifier si la bibliothèque démarrée dynamiquement par LoadLibrary est démarrée.
2) Vous pouvez afficher les informations de thread du processus cible, y compris le taux d'utilisation du processeur de chaque thread, la pile d'appels de fonction en temps réel du thread et d'autres informations.
3) Vous pouvez visualiser le matériel GPU utilisé par le processus cible (le module GPU est intégré au CPU). De nombreux logiciels utiliseront le GPU, tels que l'encodage et le décodage vidéo, vous pouvez utiliser le GPU pour implémenter l'encodage et le décodage durs (l'encodage et le décodage utilisant la puissance de calcul du CPU sont appelés encodage et décodage doux), réduisant ainsi efficacement l'utilisation du CPU.

Après avoir démarré Process Explorer, retrouvez notre processus logiciel dans la liste des processus :

 Double-cliquez sur notre processus logiciel, la fenêtre d'informations détaillées du processus apparaîtra, puis passez à l'onglet Threads :

Sur la figure, nous pouvons voir que le thread dont l'ID de thread est 12292 a un problème, et il représente en fait environ 25 % du CPU !

       Double-cliquez sur le thread pour afficher la pile d'appels de fonction actuelle du thread comme suit :

La pile d'appels de fonction est liée à l'appel d'interface de la bibliothèque open source libwebsockets. Nous avons déjà effectué une encapsulation simple de la bibliothèque open source libwebsockets. Se pourrait-il qu'il y ait quelque chose qui ne va pas avec notre encapsulation ?

       Le module d'entreprise A de notre logiciel communiquera avec le serveur A de la plate-forme via libwebsockts, et le serveur A de la plate-forme est responsable du traitement des transactions associées de l'entreprise A. Mais à l'heure actuelle, notre logiciel n'a rien fait, et n'a pas lancé le fonctionnement de l'entreprise A. Pourquoi les modules sous-jacents appellent-ils fréquemment libwebsockets ?

       Lorsque notre logiciel se connecte, il établit un lien avec le serveur A de la plateforme pour s'enregistrer auprès du serveur A, et ce lien est une longue connexion. Si le lien entre la couche inférieure du logiciel et le serveur A est déconnecté, la couche inférieure initiera une reconnexion automatique. Se pourrait-il que la couche inférieure soit actuellement incapable de se connecter au serveur A, et que la couche inférieure se reconnecte constamment à intervalles réguliers, et qu'il y ait un problème avec le code de synchronisation de la reconnexion, entraînant une utilisation élevée du processeur ?

       Donc, en vérifiant et en imprimant, j'ai trouvé que l'adresse du serveur A est 172.16.72.235, et j'ai envoyé un ping à cette adresse de serveur sur l'ordinateur portable du client, mais l'adresse du serveur n'a pas réussi à ping :

Nous pouvons donc estimer que c'est parce que le serveur ne peut pas être connecté et que la couche inférieure se reconnecte constamment au serveur, ce qui entraîne une utilisation élevée du processeur.

3. Utiliser l'outil Clumsy pour reproduire le problème dans l'intranet de l'entreprise

       Ce qui précède suppose probablement que la couche inférieure s'est reconnectée régulièrement au serveur A lorsqu'elle ne peut pas se connecter au serveur A, ce qui entraîne une utilisation élevée du processeur. Mais selon la pile d'appels de fonction affichée dans Process Explorer :

Par rapport au code source, nous n'avons pas trouvé le problème.

       Nous ne pouvons pas garder les ordinateurs des clients distants, et les clients ont encore beaucoup de choses à faire, nous essayons donc de reproduire ce problème dans notre environnement d'entreprise (notre entreprise a construit plusieurs ensembles d'environnements de plate-forme pour les tests, et ceux avec des environnements intranet , il existe également un environnement de réseau public). Alors comment le reproduire au sein de l'entreprise ? En fait, c'est très simple, utilisez des outils réseau pour intercepter toutes les données qui interagissent avec le serveur A, afin que le logiciel client ne puisse pas se connecter au serveur A, et la reconnexion automatique sous-jacente se déclenchera, ce qui devrait pouvoir reproduire le problème .

       J'ai donc pensé à un outil de simulation d'environnement réseau très utile et léger et maladroit, et j'ai directement utilisé cet outil pour intercepter tous les paquets de données envoyés par le client au serveur A.

Clumsy est un outil de simulation d'environnement réseau faible, qui peut directement intercepter les données du réseau, et peut également définir le taux de perte de paquets de l'adresse cible pour simuler un environnement réseau médiocre. C'est aussi un logiciel que nous utilisons davantage dans notre outil de travail quotidien.

       Après l'ouverture de clumsy, la condition de filtre par défaut est : sortant et ip.DstAddr >= 127.0.0.1 et ip.DstAddr <= 127.255.255.255, comme suit :

Il nous suffit de définir l'adresse cible sur l'adresse du serveur A dans la plate-forme 139.224.XXX.XXX (cette plate-forme est une plate-forme de test construite par notre société), c'est-à-dire ip.DstAddr == 139.224.XXX.XXX :

Cochez ensuite l'option Drop, cochez Inbound et Outbound, et réglez le taux de perte de paquets sur 100%, de sorte que tous les paquets de données envoyés par le terminal logiciel au serveur A soient rejetés, de sorte que le serveur A ne puisse pas être connecté. Notez que vous devrez peut-être exécuter l'outil avec des privilèges d'administrateur (en particulier dans les systèmes win10).
       Si le serveur A ne peut pas être connecté, la couche inférieure continuera à se reconnecter régulièrement, puis le problème réapparaîtra.Notre machine de travail occupe également un processeur élevé, il est donc plus certain que l'utilisation élevée du processeur causée par le code de reconnexion.

4. Selon la pile d'appels de fonction dans Process Explorer, analysez le code source et enfin découvrez le problème

       En fait, ce bogue d'utilisation élevée du processeur est très caché, il n'a pas ce problème lorsqu'il peut se connecter au serveur A. Il n'apparaîtra que lorsqu'il déclenchera une reconnexion lorsqu'il ne pourra pas se connecter au serveur A.       

       J'ai donc trouvé des collègues responsables de la maintenance des modules sous-jacents et examinons pourquoi leur code a provoqué une utilisation élevée du processeur. Parce que je suis responsable du dépannage des exceptions logicielles, j'aide souvent mes collègues des modules sous-jacents à dépanner diverses exceptions logicielles, telles que les modules de protocole, les modules réseau, les modules de codec audio et vidéo et les modules de composants.

       Par rapport à la pile d'appels de fonction affichée dans Process Explorer, j'ai trouvé l'emplacement du code source, mais après avoir examiné le code en détail, aucun défaut évident n'a été trouvé ! Pourquoi n'y a-t-il pas de problème d'exécution du code lorsqu'il peut être connecté, mais qu'il y aura des problèmes avec ce code lorsqu'il n'est pas connecté ? Le collègue responsable du module sous-jacent est très occupé et dit en plaisantant, puisque le problème est que le serveur ne peut pas être connecté, laissez le client vérifier pourquoi le serveur ne peut pas être connecté et laissez ce problème pour l'instant. Comment cela peut-il être fait! Il s'agit évidemment d'un grand danger caché, que l'environnement du client puisse être connecté ou non, il doit être résolu !

       J'ai donc repris leur code et l'ai étudié attentivement pour voir ce qui se passait. Le code de reconnexion au serveur A est traité dans un thread, et le code correspondant est le suivant : (Le problème réside dans le code qui appelle 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;
}

Habituellement, lors de l'utilisation d'un thread pour traiter des transactions, un Sleep doit être ajouté et le thread ne peut pas continuer à fonctionner, sinon le thread occupera toujours la tranche de temps CPU, ce qui entraînera une utilisation élevée du CPU, similaire à une boucle infinie. Pour les programmeurs, c'est du bon sens !

Il semble qu'il y ait aussi Sleep dans le code. Lors de l'appel de l'interface lws_service de libwebsockets, un paramètre timeout est passé. On estime que la fonction Sleep est réalisée à travers cette fonction.

       Pouvez-vous appeler directement l'interface Sleep sans appeler ce lws_service ? La réponse est non, allez à l'implémentation de l'interface lws_service et vérifiez les commentaires de l'interface 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);
}

La traduction de la note ci-dessus est la suivante :

Cette fonction traite tout trafic websocket en attente, pour trois types d'événements. Elle gère ces événements sur les types de connexion serveur et client de la même manière. 1) Accepter les nouvelles
connexions au serveur de notre contexte
2) Appeler le rappel de réception pour les données de trame entrantes reçues par les connexions serveur ou client.
Cette fonction gère tout trafic websocket (en attente) qui doit être traité, pour trois types d'événements. Il gère ces événements sur les types de connexion serveur et client de la même manière.
1) Recevoir une nouvelle connexion à notre serveur de contexte
2) Appeler la fonction de rappel pour rappeler les données reçues par le serveur ou la connexion client.

Vous devez appeler périodiquement cette fonction de service pour que toutes les fonctions ci-dessus se produisent ; si votre application est monothread, vous pouvez simplement l'appeler dans votre boucle d'événements principale. Vous devez appeler cette fonction de service périodiquement pour que toutes les fonctions ci-dessus se produisent
 ; Si votre application est monothread, vous pouvez l'appeler à partir de la boucle d'événement principale.

Comme on peut le voir dans les commentaires ci-dessus, pour s'assurer que la bibliothèque libwebsockets peut envoyer et recevoir des données normalement, l'interface lws_service doit être appelée.

       Alors pourquoi les codes ci-dessus ont-ils des problèmes d'exécution lorsqu'ils sont connectés au serveur, mais ont-ils des problèmes lorsqu'ils ne peuvent pas se connecter au serveur ? On estime que lorsque le serveur ne peut pas être connecté, il n'y a pas de connexion websockets valide dans libwebsockets.Lorsque l'interface lws_service est appelée, l'interface revient immédiatement, c'est-à-dire que l'interface ne joue pas le rôle de Sleep! Cela devrait être causé par cette raison.

       La solution finale est d'ajouter un Sleep à la fonction thread : (ajoutez une phrase de Sleep sous l'appel à lws_service)

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

Quelle que soit la situation, une certaine période de Sommeil doit être exécutée. Après avoir modifié le code, compilez la bibliothèque et écrasez-la dans le répertoire du logiciel. Après avoir réexécuté le logiciel, il n'y aura plus de problèmes.

5. Résumé

       En tant que développeur de logiciels, il est très nécessaire de maîtriser l'utilisation de certains outils courants. L'utilisation de ces outils pour aider à résoudre divers problèmes rencontrés dans le fonctionnement de nos produits logiciels peut améliorer efficacement l'efficacité du dépannage.

       Dans ce cas, le bloc de code problématique a été trouvé en examinant la pile d'appels de fonction dans l'outil Process Explorer, et le problème a été reproduit dans l'environnement de l'entreprise par l'outil Clumsy. C'est en s'appuyant sur ces outils que l'on peut progressivement localiser et résoudre les problèmes.

Je suppose que tu aimes

Origine blog.csdn.net/chenlycly/article/details/130038272
conseillé
Classement