web-dev-qa-db-fra.com

Utilisation élevée du processeur mais faible charge moyenne

Nous rencontrons un comportement étrange où nous constatons une utilisation élevée du processeur mais une charge moyenne assez faible.

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

CPU usage and load

Vers 11 h 57, l'utilisation du processeur passe de 25% à 75%. La moyenne de charge n'est pas modifiée de manière significative.

Nous exécutons des serveurs avec 12 cœurs avec 2 hyper threads chacun. Le système d'exploitation voit cela comme 24 CPU.

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

Le chiffre moyen de charge est tiré 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 dist Linux est Red Hat Enterprise Linux Server release 6.3 (Santiago)

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

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

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

32
K Erlandsson

Bien que la réponse de Matthew Ife ait été très utile et nous ait conduits 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 multi-thread Java qui utilise le pool de threads, pourquoi aucun travail n'est fait pour créer les tâches réelles.

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

J'ai créé un programme Java qui a reproduit le comportement. La classe Java Java suivante génère une utilisation CPU de 28% (650% empilés) sur l'un de nos serveurs). En faisant cela, la moyenne de charge est d'environ 1,3. La clé ici est le sleep () à l'intérieur du thread, sans lui le calcul de la 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<Runnable>(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();
    }

}

Pour résumer, la théorie est que les threads de nos applications tournent beaucoup au ralenti puis effectuent un travail de courte durée, pourquoi les tâches ne sont pas correctement échantillonnées par le calcul de la moyenne de charge.

2
K Erlandsson

Sur Linux au moins, la moyenne de charge et l'utilisation du processeur 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'attente d'exécution du noyau (pas seulement le temps CPU mais aussi l'activité du disque) sur une période de temps. L'utilisation du processeur est une mesure de l'occupation actuelle du processeur. La charge la plus élevée qu'un thread d'unité centrale fixe à 100% pendant une minute peut "contribuer" à la moyenne de charge d'une minute est 1. Un processeur à 4 cœurs avec hyperthreading (8 cœurs virtuels) tous à 100% pendant 1 minute contribuerait de 8 à la moyenne de charge de 1 minute.

Souvent, ces deux nombres ont des modèles qui sont en corrélation, mais vous ne pouvez pas les considérer comme identiques. Vous pouvez avoir une charge élevée avec près de 0% d'utilisation du processeur (comme lorsque vous avez beaucoup de données IO bloquées dans un état d'attente) et vous pouvez avoir une charge de 1 et 100% CPU , lorsque vous avez un processus à thread unique exécutant une inclinaison totale. Également pendant de courtes périodes, vous pouvez voir le CPU à près de 100% mais la charge est toujours inférieure à 1 car les mesures 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% CPU proche de 0%. Cela s'est produit parce qu'un partage Samba rencontrait des problèmes et que de nombreux clients ont commencé à se coincer dans un état d'attente IO. Les chances sont que si vous voyez un nombre de charge élevé régulier sans activité CPU correspondante, vous rencontrez un problème de stockage quelconque. Sur les machines virtuelles, cela peut également signifier que d'autres machines virtuelles sont fortement en concurrence pour les ressources de stockage sur le même hôte VM VM.

Une charge élevée n'est également pas nécessairement une mauvaise chose, la plupart du temps cela signifie simplement que le système est utilisé à sa pleine capacité ou peut-être au-delà de sa capacité à suivre (si le nombre de charge est supérieur au nombre de cœurs de processeur). À un endroit où j'étais un administrateur système, ils avaient quelqu'un qui regardait la charge moyenne sur leur système principal plus près que Nagios. Lorsque la charge était élevée, ils m'appelaient 24 heures sur 24, 7 jours sur 7 plus rapidement que vous ne pourriez le dire SMTP. La plupart du temps, rien ne tournait mal, mais ils ont associé le numéro de chargement à quelque chose de mal et l'ont regardé comme un faucon. Après vérification, ma réponse était généralement que le système faisait juste son travail. Bien sûr, c'était le même endroit où la charge dépassait 15 000 (pas le même serveur cependant), donc cela signifie parfois que quelque chose ne va pas. Vous devez considérer le but de votre système. Si c'est un cheval de bataille, attendez-vous à ce que la charge soit naturellement élevée.

56
deltaray

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

Si vous générez de nombreuses tâches en succession très rapide qui se terminent très rapidement, le nombre de processus dans la file d'attente 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 grande utilisation du processeur en haut (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()

Autre implémentation, celle-ci évite wait par groupe de 8 (ce qui fausserait le test). Ici, le parent essaie toujours de maintenir le nombre d'enfants au nombre de processeurs actifs, de sorte qu'il sera beaucoup plus occupé que la première méthode et, espérons-le, plus précis.

/* Compile with flags -O0 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <err.h>
#include <errno.h>

#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define ITERATIONS 50000

int maxchild = 0;
volatile int numspawned = 0;

void childhandle(
    int signal)
{
  int stat;
  /* Handle all exited children, until none are left to handle */
  while (waitpid(-1, &stat, WNOHANG) > 0) {
    numspawned--;
  }
}

/* Stupid task for our children to do */
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);

  /* Setup child handler */
  memset(&act, 0, sizeof(act));
  act.sa_handler = childhandle;
  if (sigaction(SIGCHLD, &act, NULL) < 0)
    err(EXIT_FAILURE, "sigaction");

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

  /* Create processes, where our maxchild value is not met */
  while (1) {
    while (numspawned < maxchild) {
      pid = fork();
      if (pid < 0)
        err(EXIT_FAILURE, "fork");

      else if (pid == 0) /* child process */
        do_task();
      else               /* parent */
        numspawned++;
    }
    /* Atomically unblocks signal, handler then picks it up, reblocks on finish */
    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 (en comptant jusqu'à 10000). Les tâches qui n'ont pas encore été créées ne peuvent pas être prises en compte dans l'état "exécutable", mais prendront% sys sur le temps CPU lors de leur apparition.

Ainsi, la réponse pourrait être dans votre cas que, quel que soit le travail effectué, un grand nombre de tâches se succèdent rapidement (threads ou processus).

27
Matthew Ife

Si la moyenne de charge n'augmente pas beaucoup, cela signifie simplement que vos spécifications matérielles et la nature des tâches à traiter entraînent un bon débit global, évitant qu'elles ne soient empilées dans la file d'attente des tâches pendant un certain temps.

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

MISE À JOUR:

Ce n'est peut-être pas clair dans ma réponse d'origine, alors je précise maintenant:

La formule exacte de calcul de la charge moyenne est: loadvg = tasks running + tasks waiting (for cores) + tasks blocked.

Vous pouvez certainement avoir un bon débit et atteindre une moyenne de charge de 24 mais sans pénalité sur le temps de traitement des tâches. D'un autre côté, vous pouvez également avoir 2-4 tâches périodiques qui ne se terminent pas assez rapidement, puis vous verrez le nombre de tâches en attente (pour les cycles CPU) augmenter et vous atteindrez finalement une moyenne de charge élevée. Une autre chose qui peut arriver est que des tâches exécutent des opérations d'E/S synchrones en suspens, puis bloquent un cœur, réduisent le débit et augmentent la file d'attente des tâches en attente (dans ce cas, vous pouvez voir la métrique iowait changer)

5
Xavier Lucas

La moyenne de charge inclut les tâches qui sont bloquées sur le disque IO, vous pouvez donc facilement utiliser zéro processeur et une moyenne de charge de 10 simplement en ayant 10 tâches essayant toutes de lire à partir d'un disque très lent. Ainsi, il est courant qu'un serveur occupé commence à écraser le disque et toutes les recherches provoquent de nombreuses tâches bloquées, augmentant la moyenne de charge, tandis que l'utilisation du processeur diminue, car toutes les tâches sont bloquées sur le disque.

2
psusi

La moyenne de charge est le nombre moyen de processus dans la file d'attente du processeur. Il est spécifique à chaque système, vous ne pouvez pas dire qu'un LA est génériquement élevé sur tous les systèmes et un autre est faible. Vous avez donc 12 cœurs, et pour que LA augmente de manière significative, le nombre de processus doit être vraiment élevé.

Une autre question est de savoir ce que l'on entend par le graphique "CPU Usage". S'il provient de SNMP, comme il se doit, et que votre implémentation SNMP est net-snmp, puis en pile juste la charge CPU de chacun de vos 12 CPU. Donc pour net-snmp la quantité totale de charge CPU est de 1200%.

Si mes hypothèses sont correctes, l'utilisation du processeur n'a pas augmenté de manière significative. Ainsi, LA n'a pas augmenté de manière significative.

0
drookie

Le scénario ici n'est pas particulièrement inattendu bien qu'il soit un peu inhabituel. Ce que Xavier aborde, mais ne développe pas beaucoup, c'est que bien que Linux (par défaut) et la plupart des versions d'Unix implémentent le multitâche préventif, sur une machine saine, les tâches seront rarement anticipées. Chaque tâche se voit attribuer une tranche de temps pour occuper le processeur, elle n'est préemptée que si elle dépasse ce temps et s'il y a d'autres tâches en attente d'exécution (notez que la charge indique le nombre moyen de processus à la fois dans le processeur et en attente d'exécution) . La plupart du temps, un processus cédera au lieu d'être interrompu.

(en général, vous n'avez à vous soucier de la charge que lorsque le nombre de CPU est proche - c'est-à-dire lorsque le planificateur commence les tâches de préemption).

si nos processeurs sont occupés 75% du temps, ne devrions-nous pas voir une moyenne de charge plus élevée?

Son tout sur le modèle d'activité, l'utilisation clairement accrue du CPU par certaines tâches (probablement une petite mintorité) n'a pas eu d'effet négatif sur le traitement d'autres tâches. Si vous pouviez isoler les transactions en cours de traitement, je m'attendrais à voir un nouveau groupe émerger pendant le ralentissement, alors que l'ensemble de tâches existant n'était pas affecté.

mise à jour

Un scénario courant où un processeur élevé peut se produire sans une augmentation importante de la charge est le cas où une tâche déclenche une (ou une séquence) d'autres tâches, par exemple à la réception d'une demande réseau, le gestionnaire achemine la demande vers un thread séparé, le thread séparé effectue ensuite des appels asynchrones vers d'autres processus .... l'échantillonnage de la file d'attente entraîne un rapport de la charge inférieur à ce qu'il est réellement - mais il n'augmente pas de façon linéaire avec l'utilisation du processeur - la chaîne de tâches déclenchée n'aurait pas pu être exécutée sans l'événement initial, et parce qu'elles se produisent (plus ou moins) séquentiellement, la file d'attente d'exécution n'est pas gonflée.

0
symcbean