IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Web & réseau Delphi Discussion :

[D10.2] Utilisation CPU excessive sur un service Windows


Sujet :

Web & réseau Delphi

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Inscrit en
    Avril 2002
    Messages
    28
    Détails du profil
    Informations forums :
    Inscription : Avril 2002
    Messages : 28
    Par défaut [D10.2] Utilisation CPU excessive sur un service Windows
    Bonjour à tous.

    Je sèche sur un problème de CPU...
    J'ai un service Windows créé avec Delphi qui fonctionne correctement pendant quelques jours (3 à 4 jours), et soudain pour une raison inconnue, l'utilisation du CPU devient assez importante.
    Ce phénomène n’est pas généralisé sur tous les postes sur lesquels le service est installé.
    Voici une capture qui montre l’état de mon service avant :
    Nom : IMG_Without_CPU.png
Affichages : 360
Taille : 29,6 Ko
    Voici une capture de son état après quelques jours de fonctionnement :
    Nom : IMG_With_CPU.png
Affichages : 357
Taille : 30,0 Ko

    But de mon service :
    Le service est destiné à enregistrer les connexions entre un programme et un serveur SQL.
    Le programme envoie une demande au service et reçoit en retour une réponse du service.
    De plus le service gère le contenu d'un petit fichier local.

    Technique utilisée :
    Pour recevoir et répondre à la demande, j’utilise le composant « TIdTCPServer », l’événement OnExecute se charge de traiter la demande ainsi :
    La demande vient sous un format xml crypté, elle est stockée dans un objet dédié « TXMLCmd ».
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
     
    procedure TdmServiceServer.IdTCPServerExecute(AContext: TIdContext);
    var
      CommandData : string;
      Command : TXMLCmd;
      Response : TXMLCmd;
    begin
      try
        CommandData := AContext.Connection.Socket.ReadLn;
      except
        on E : Exception do
        begin
          if (E is EIdSilentException) then
            CommandData := EmptyStr
          else
            raise;
        end;
      end;
      if (CommandData <> EmptyStr) then
      begin
        // Chaque demande est exécutée dans un thread séparé, il faut initialiser
        // COM pour pouvoir utiliser XML
        CoInitializeEx(nil, COINIT_MULTITHREADED);
        try
          Command := TXMLCmd.Create;
          Command.XMLText := Uncrypt(CommandData);
     
          Response := TXMLCmd.Create;
          try
            Response.CMDId := Command.CMDId;
            Response.CMDName := Command.CMDName;
            try
              case Command.CMDId of
                CMD_CONNECT_ID :
                begin
                  ExecConnect(Command, Response);
                end;
                CMD_COMP_SUB_CONNECT_ID :
                begin
                  ExecCompSubConnect(Command, Response);
                end;
                CMD_DISCONNECT_ID :
                begin
                  ExecDisconnect(Command, Response);
                end;
                CMD_GET_SQL_CONNECTION_DATA_ID :
                begin
                  ExecGetSQLConnectionData(Command, Response);
                end;
                .
                .
                .
              else
                // Commande invalide
                Response.CMDId := CMD_ERROR_ID;
                Response.CMDName := CMD_ERROR_NAME;
                Response.ParamAsString['Message'] := Format(rsInvalidCommand, [Command.CMDName]);
              end;
              AContext.Connection.Socket.WriteLn(Crypt(Response.XMLText));
            finally
              Response.Free;
            end;
          finally
            Command.Free;
          end;
        finally
          CoUnInitialize;
        end;
      end;
    end;
    Rien de bien méchant...
    Mis à part quelques boucles "for", rien de spécial, rien qui bloque son fonctionnement, mais une consommation CPU étrange.

    Après toutes une série d'analyses, de log, je tombe sur un élément suspect, les « threads » utilisés.
    Au démarrage du service, les threads sont au nombre de 8. Ensuite, ce nombre monte et redescend de 8 à 9 à 8. Mais à un certain moment, ce nombre de threads monte et ne redescend pas…
    Par exemple, dans ma deuxième image, le nombre de Threads est à 11 et le CPU à 33.

    J'ai commencé à m'intéresser à ces Threads, mais c'est un sujet que je ne maîtrise pas vraiment.
    Dans Delphi, après avoir attaché le service depuis Delphi, j'ai une liste de Threads qui ressemble à ça :
    Nom : IMG_Delphi_Threads.png
Affichages : 362
Taille : 16,4 Ko

    Du coup, je voulais essayer d'identifier ce que font ces Threads.
    Mais comment ?
    Il y a la colonne "Location", comment je peux savoir quelle est le code attaché derrière cette info ?
    Ensuite, vu que ces "Threads" semblent augmenter, y a t-il un moyen depuis Delphi, à l'intérieure de ma programmation du service, de lister ces threads et de connaître le code exécuté ?

    Merci.

  2. #2
    Membre Expert
    Avatar de pprem
    Homme Profil pro
    MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Inscrit en
    Juin 2013
    Messages
    1 876
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Loiret (Centre)

    Informations professionnelles :
    Activité : MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2013
    Messages : 1 876
    Par défaut
    Bonjour

    Les threads sont générés à chaque connexion par TIdTCPServer. Si ça s'emballe c'est qu'ils ne se terminent pas correctement ou entrent en conflit entre eux.

    Quelles sont les versions de Delphi, Indy et de windows concernées ?

    Si ça ne le fait pas partout, y a-t-il plus ou moins d'utilisateurs sur ceux qui plantent ? Des connexions réseaux instables ?

  3. #3
    Membre averti
    Inscrit en
    Avril 2002
    Messages
    28
    Détails du profil
    Informations forums :
    Inscription : Avril 2002
    Messages : 28
    Par défaut
    Bonjour pprem

    Le service Windows est actuellement compilé avec Delphi 10.2 SP 3
    La version des composants Indy : 10.6.2.5366
    Sur une machine concernée, c'est Windows Serveur 2016

    Sur ce serveur, ce n'est pas une grosse installation, j'ai des serveurs où beaucoup plus d'accès sont effectués sans aucun souci.
    Je n'ai pas connaissances de soucis réseaux sur le serveur concernés. A priori pas de souci.

  4. #4
    Membre Expert
    Avatar de pprem
    Homme Profil pro
    MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Inscrit en
    Juin 2013
    Messages
    1 876
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Loiret (Centre)

    Informations professionnelles :
    Activité : MVP Embarcadero - formateur&développeur Delphi, PHP et JS
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juin 2013
    Messages : 1 876
    Par défaut
    ce serait bien d'avoir une trace des exceptions qui peuvent se produire sur la lecture

    de ce que j'ai vu en faisant une rapide recherche il semble qu'un sleep() de quelques millisecondes serait conseillé dans le cas où la lecture ne trouve rien, à faire dans le else du if global, faudrait voir le code de Indy pour savoir pourquoi cette suggestion revient en solution à chaque fois qu'un CPU est surchargé par un onExecute qui ne fait rien, peut-être que ça a été corrigé dans cette version. En tout cas, à tester.

  5. #5
    Expert éminent
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 56
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Par défaut
    sleep c'est utilisé dans une boucle trop rapide pour permettre au processeur de souffler un peu...mais ça veux dire que la boucle n'a rien à faire...exemple dans la boucle principale d'un jeu vidéo où on veux un maximum de frames, on peut laisser une respiration à la CPU par un sleep.

    mais sinon il y a des tas d'autres solutions, comme select() pour les sockets.

    pour le cas de ce serveur, je pense qu'il va falloir passer par des logs pour savoir ce qu'il se passe car il y a probablement un des threads (on peut faire maintenant des thread nommés) qui est dans un état non prévu et qui consomme de la CPU pour rien.

    Indy est plutôt stable, je ne pense pas qu'il soit en cause directement....mais par exemple si dans un boucle tu cherches à lire sur un socket fermé en ignorant les erreurs tu peux mettre la CPU à plat.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  6. #6
    Membre averti
    Inscrit en
    Avril 2002
    Messages
    28
    Détails du profil
    Informations forums :
    Inscription : Avril 2002
    Messages : 28
    Par défaut
    Merci pprem
    Je vais analyser ceci.
    J'ai fait une petite adaptation de mon code pour loguer des infos du OnExecute :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
     
    procedure TdmServiceServer.IdTCPServerExecute(AContext: TIdContext);
    var
      CommandData : string;
      Command : TXMLCmd;
      Response : TXMLCmd;
      CommandIsGood : Boolean; // <-- Ajout de cette variable
    begin
      try
        CommandData := AContext.Connection.Socket.ReadLn;
      except
        on E : Exception do
        begin
          if (E is EIdSilentException) then
          begin
            CommandData := EmptyStr;
            ABLogFileManager.LogLowDebugInfos2('EIdSilentException is run'); // <-- Je log une erreur silencieuse
          end
          else
          begin
            ABLogFileManager.LogLowDebugInfos2(E.ClassName + ' is run (Other Exception)'); // <-- Je log les autres erreurs non traitées
            raise;
          end;
        end;
      end;
      if (CommandData <> EmptyStr) then
      begin
        CommandIsGood := True;
        // Chaque demande est exécutée dans un thread séparé, il faut initialiser
        // COM pour pouvoir utiliser XML
        CoInitializeEx(nil, COINIT_MULTITHREADED);
        try
          Command := TXMLCmd.Create;
          Command.XMLText := Uncrypt(CommandData);
     
          Response := TXMLCmd.Create;
          try
            Response.CMDId := Command.CMDId;
            Response.CMDName := Command.CMDName;
            try
              case Command.CMDId of
                CMD_CONNECT_ID :
                begin
                  ExecConnect(Command, Response);
                end;
                CMD_COMP_SUB_CONNECT_ID :
                begin
                  ExecCompSubConnect(Command, Response);
                end;
                CMD_DISCONNECT_ID :
                begin
                  ExecDisconnect(Command, Response);
                end;
                CMD_GET_SQL_CONNECTION_DATA_ID :
                begin
                  ExecGetSQLConnectionData(Command, Response);
                end;
                .
                .
                .
              else
                // Commande invalide
                Response.CMDId := CMD_ERROR_ID;
                Response.CMDName := CMD_ERROR_NAME;
                Response.ParamAsString['Message'] := Format(rsInvalidCommand, [Command.CMDName]);
                CommandIsGood := False;
                ABLogFileManager.LogLowDebugInfos2(rsInvalidCommand); // <-- Je log une commande invalide (Ne devrait jamais arriver)
              end;
              AContext.Connection.Socket.WriteLn(Crypt(Response.XMLText));
            finally
              Response.Free;
            end;
          finally
            Command.Free;
          end;
        finally
          CoUnInitialize;
        end;
        if CommandIsGood then
          ABLogFileManager.LogLowDebugInfos2('CommandData is treated !'); // <-- Je log que mon process est correctement traité
      end
      else
      begin
        ABLogFileManager.LogLowDebugInfos2('Empty CommandData !'); // <-- Je log une commande vide
        Sleep(10); // <-- Je fais le Sleep 10 pour faire respirer.
      end;
    J'ai déjà un résultat de log avec des différences :
    - Sur ma machine, quelques EIdSilentException sont interceptés, mais pas énormément.
    - Sur un serveur fonctionnel, il y en a beaucoup, mais n'ont aucune incidence sur le bon fonctionnement de mon service.
    - Sur le serveur qui provoque une surcharge CPU, il y a quelques EIdSilentException, par contre l'erreur EIdSocketError est intercepté assez régulièrement (Ligne 21). Je vais voir si je trouve des infos. Mais je suis preneur de tous conseils…

    Pour l'instant pas de surcharge, je vais surveiller cela ces prochains jours. Peut-être que dans le cas d'une erreur non traitées, je peux enlever le "raise", j'explorerai cette voie si nécessaire aussi.

    Salut Paul TOTH
    Mon service n'a pas de boucle particulière, que quelques-unes et n’ont pas de souci.
    Je ne crée pas manuellement de Threads dans mon code, j’ai simplement repéré (capture no 2) que mon processus avait énormément de threads lorsque le CPU est surchargé.
    Je pose la question suivante : Depuis Delphi, est-il possible de lister tous les threads de l’application ? Et d’en connaitre la source ?

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Utiliser une SoapExtension sur certains services et pas les autres
    Par kheironn dans le forum Développement Web avec .NET
    Réponses: 0
    Dernier message: 19/01/2018, 13h10
  2. Recherche de tutoriaux sur les services Windows
    Par talrashha dans le forum Services Windows
    Réponses: 2
    Dernier message: 04/10/2010, 12h04
  3. Réponses: 5
    Dernier message: 22/08/2008, 11h59
  4. Début sur un service Windows
    Par zooffy dans le forum Windows Forms
    Réponses: 0
    Dernier message: 24/08/2007, 17h16
  5. Question sur les services windows
    Par bilb0t dans le forum Windows
    Réponses: 8
    Dernier message: 09/11/2005, 16h31

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo