44 votes

Utilisation élevée du processeur mais faible charge moyenne

Nous rencontrons un comportement étrange où nous constatons une haute utilisation du CPU mais un load average assez faible.

Le comportement est mieux illustré par les graphiques suivants de notre système de surveillance.

Utilisation du CPU et charge

À environ 11h57, l'utilisation du CPU passe de 25% à 75%. Le load average n'a pas changé de manière significative.

Nous utilisons des serveurs avec 12 coeurs avec 2 hyper-threads chacun. Le système d'exploitation voit cela comme 24 CPUs.

Les données d'utilisation du CPU sont collectées en exécutant /usr/bin/mpstat 60 1 chaque minute. Les données pour la ligne all et la colonne %usr sont affichées dans le graphique ci-dessus. Je suis certain que cela montre la moyenne par CPU, pas l'utilisation "empilée". Alors que nous voyons une utilisation de 75% dans le graphique, nous voyons un processus montrant utiliser environ 2000% de CPU "empilé" dans top.

La figure du load average est prise à partir de /proc/loadavg chaque minute.

uname -a donne :

Linux ab04 2.6.32-279.el6.x86_64 #1 SMP Wed Jun 13 18:24:36 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux

La distribution Linux est Red Hat Enterprise Linux Server release 6.3 (Santiago)

Nous exécutons quelques applications web Java sous une charge assez lourde sur les machines, environ 100 requêtes/s par machine.

Si j'interprète correctement les données d'utilisation du CPU, lorsque nous avons une utilisation du CPU de 75%, cela signifie que nos CPUs exécutent un processus 75% du temps, en moyenne. Cependant, si nos CPUs sont occupés 75% du temps, ne devrions-nous pas voir un load average plus élevé? Comment les CPUs pourraient-ils être occupés à 75% alors que nous n'avons que 2-4 tâches en file d'attente d'exécution?

Interprétons-nous correctement nos données? Qu'est-ce qui peut causer ce comportement?

85voto

Ketan Khairnar Points 570

Sous Linux au moins, la moyenne de charge et l'utilisation du CPU sont en fait deux choses différentes. La moyenne de charge est une mesure du nombre de tâches en attente dans une file d'exécution du noyau (non seulement du temps CPU mais aussi de l'activité disque) sur une période de temps. L'utilisation du CPU est une mesure de la charge actuelle du CPU. Le maximum de charge qu'un seul thread CPU à 100% pendant une minute peut "contribuer" à la moyenne de charge d'une minute est de 1. Un CPU quadricœur avec hyperthreading (8 cœurs virtuels) tous à 100% pendant 1 minute contribuerait 8 à la moyenne de charge d'une minute.

Très souvent, ces deux nombres présentent des corrélations, mais on ne peut pas les considérer comme identiques. Vous pouvez avoir une charge élevée avec une utilisation du CPU presque à 0% (comme lorsqu'il y a beaucoup de données d'E/S bloquées dans un état d'attente) et vous pouvez avoir une charge de 1 et un CPU à 100% lorsque vous avez un processus monofilaire fonctionnant à pleine puissance. De plus, pendant de courtes périodes, vous pouvez voir le CPU presque à 100% mais la charge est toujours inférieure à 1 car les métriques moyennes n'ont pas encore "rattrapé".

J'ai vu un serveur avoir une charge de plus de 15 000 (oui, vraiment ce n'est pas une faute de frappe) et un pourcentage de CPU proche de 0%. C'est arrivé parce qu'un partage Samba posait problème et que de nombreux clients se retrouvaient bloqués dans un état d'attente d'E/S. Si vous voyez un nombre de charge élevé régulièrement sans activité CPU correspondante, il est probable que vous rencontriez un problème de stockage de quelque sorte. Sur les machines virtuelles, cela peut également signifier que d'autres VMs luttent intensément pour les ressources de stockage sur le même hôte VM.

Une charge élevée n'est pas forcément une mauvaise chose, la plupart du temps cela signifie simplement que le système est utilisé à pleine capacité ou peut-être même au-delà de sa capacité à suivre (si le nombre de charge est supérieur au nombre de cœurs de processeur). Dans un endroit où j'étais administrateur système, quelqu'un surveillait de plus près la moyenne de charge sur leur système principal que Nagios ne le faisait. Quand la charge était élevée, ils m'appelaient 24/7 plus rapidement que vous ne pourriez dire SMTP. La plupart du temps, rien n'allait vraiment mal, mais ils associaient le nombre de charge à quelque chose qui ne tournait pas rond et le surveillaient comme un faucon. Après vérification, ma réponse était généralement que le système faisait simplement son travail. Bien sûr, c'était aussi le même endroit où la charge a dépassé 15 000 (mais pas le même serveur) donc parfois cela signifie que quelque chose ne va pas. Vous devez tenir compte de l'objectif de votre système. S'il s'agit d'un cheval de bataille, attendez-vous à ce que la charge soit naturellement élevée.

28voto

Matthew Ife Points 22370

La charge est un nombre très trompeur. Prenez-le avec un grain de sel.

Si vous générez de nombreuses tâches très rapidement qui se terminent très rapidement, le nombre de processus dans la file d'exécution est trop petit pour enregistrer la charge pour eux (le noyau compte la charge une fois toutes les cinq secondes).

Considérez cet exemple, sur mon hôte qui a 8 cœurs logiques, ce script python enregistrera une utilisation élevée du CPU dans top (environ 85%), mais presque aucune charge.

import os, sys

while True:
  for j in range(8):
    parent = os.fork()
    if not parent:
      n = 0
      for i in range(10000):
        n += 1
      sys.exit(0)
  for j in range(8):
    os.wait()

Une autre implémentation, celle-ci évite wait par groupes de 8 (ce qui fausserait le test). Ici, le parent tente toujours de maintenir le nombre d'enfants au nombre de processeurs actifs, ce qui le rendra beaucoup plus occupé que la première méthode et sera hopefully plus précis.

/* Compilez avec l'option -O0 */
#include 
#include 
#include 
#include 

#include 
#include 

#include 
#include 
#include 

#define ITERATIONS 50000

int maxchild = 0;
volatile int numspawned = 0;

void childhandle(
    int signal)
{
  int stat;
  /* Gérer tous les enfants terminés, jusqu'à ce qu'il n'y en ait plus à gérer */
  while (waitpid(-1, &stat, WNOHANG) > 0) {
    numspawned--;
  }
}

/* Tâche stupide pour nos enfants à faire */
void do_task(
    void)
{
  int i,j;
  for (i=0; i < ITERATIONS; i++)
    j++;
  exit(0);
}

int main() {
  pid_t pid;

  struct sigaction act;
  sigset_t sigs, old;

  maxchild = sysconf(_SC_NPROCESSORS_ONLN);

  /* Configuration du gestionnaire d'enfants */
  memset(&act, 0, sizeof(act));
  act.sa_handler = childhandle;
  if (sigaction(SIGCHLD, &act, NULL) < 0)
    err(EXIT_FAILURE, "sigaction");

  /* Reporter le signal sigchild */
  sigemptyset(&sigs);
  sigaddset(&sigs, SIGCHLD);
  if (sigprocmask(SIG_BLOCK, &sigs, &old) < 0)
    err(EXIT_FAILURE, "sigprocmask");

  /* Créer des processus, où notre valeur maxchild n'est pas atteinte */
  while (1) {
    while (numspawned < maxchild) {
      pid = fork();
      if (pid < 0)
        err(EXIT_FAILURE, "fork");

      else if (pid == 0) /* processus enfant */
        do_task();
      else               /* parent */
        numspawned++;
    }
    /* débloque le signal de manière atomique, le gestionnaire le récupère puis le re-bloque à la fin */
    if (sigsuspend(&old) < 0 && errno != EINTR)
      err(EXIT_FAILURE, "sigsuspend");
  }
}

La raison de ce comportement est que l'algorithme passe plus de temps à créer des processus enfants qu'à exécuter la tâche réelle (compter jusqu'à 10000). Les tâches pas encore créées ne peuvent pas compter comme étant dans un état 'en cours d'exécution', mais elles occuperont le %sys du temps CPU lorsqu'elles seront lancées.

Ainsi, la réponse pourrait vraiment être dans votre cas que tout travail en cours génère de grands nombres de tâches très rapidement (threads, ou processus).

5voto

berto Points 221

Si la charge moyenne n'augmente pas beaucoup, cela signifie simplement que les spécifications matérielles et la nature des tâches à traiter résultent en un débit global satisfaisant, évitant ainsi qu'elles ne s'accumulent dans la file d'attente des tâches pendant un certain temps.

S'il y avait un phénomène de contention parce que, par exemple, la complexité moyenne des tâches est trop élevée ou que le temps de traitement moyen des tâches prend trop de cycles CPU, alors oui, la charge moyenne augmenterait.

MISE À JOUR :

Il se peut que ce ne soit pas clair dans ma réponse initiale, je clarifie maintenant :

La formule exacte du calcul de la charge moyenne est : charge = tâches en cours d'exécution + tâches en attente (pour les cœurs) + tâches bloquées.

Vous pouvez certainement avoir un bon débit et approcher une charge moyenne de 24 sans pénalité sur le temps de traitement des tâches. D'autre part, vous pouvez également avoir 2 à 4 tâches périodiques ne se terminant pas assez rapidement, alors vous verrez le nombre de tâches en attente (pour les cycles CPU) augmenter et vous finirez par atteindre une charge moyenne élevée. Une autre chose qui peut arriver est d'avoir des tâches en cours d'exécution effectuant des opérations E/S synchrone exceptionnelles, puis bloquant un cœur, réduisant le débit et faisant croître la file d'attente des tâches en attente (dans ce cas, vous pouvez voir la métrique iowait changer)

4voto

GGGGGMAN Points 11

Alors que la réponse de Matthew Ife était très utile et nous a orientés dans la bonne direction, ce n'était pas exactement ce qui a causé le comportement dans notre cas. Dans notre cas, nous avons une application Java multi-threadée qui utilise un pool de threads, où aucun travail n'est effectué pour créer les tâches réelles.

Cependant, le travail effectivement effectué par les threads est de courte durée et comprend des attentes d'IO ou de synchronisation. Comme le mentionne Matthew dans sa réponse, la charge moyenne est échantillonnée par le système d'exploitation, donc les tâches de courte durée peuvent être manquées.

J'ai créé un programme Java qui reproduit le comportement. La classe Java suivante génère une utilisation du processeur de 28% (650% empilée) sur l'un de nos serveurs. Tout en faisant cela, la charge moyenne est d'environ 1,3. La clé ici est le sleep() à l'intérieur du thread, sans cela le calcul de charge est correct.

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MultiThreadLoad {

    private ThreadPoolExecutor e = new ThreadPoolExecutor(200, 200, 0l, TimeUnit.SECONDS,
            new ArrayBlockingQueue(1000), new ThreadPoolExecutor.CallerRunsPolicy());

    public void load() {
        while (true) {
            e.execute(new Runnable() {

                @Override
                public void run() {
                    sleep100Ms();
                    for (long i = 0; i < 5000000l; i++)
                        ;
                }

                private void sleep100Ms() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    public static void main(String[] args) {
        new MultiThreadLoad().load();
    }

}

En résumé, la théorie est que les threads dans nos applications sont souvent inactifs et effectuent ensuite un travail de courte durée, ce qui fait que les tâches ne sont pas correctement échantillonnées par le calcul de la charge moyenne.

3voto

psusi Points 3197

La charge moyenne inclut les tâches bloquées sur l'E/S disque, il est donc possible d'avoir une utilisation de cpu nulle et une charge moyenne de 10 simplement en ayant 10 tâches essayant toutes de lire à partir d'un disque très lent. Ainsi, il est courant pour un serveur occupé de commencer à accéder au disque de manière excessive et toutes les recherches entraînent beaucoup de tâches bloquées, faisant monter la charge moyenne, tandis que l'utilisation du cpu diminue, puisque toutes les tâches sont bloquées sur le disque.

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X