Alta utilização da CPU, mas baixa carga média

21

Estamos nos deparando com um comportamento estranho, no qual vemos uma alta utilização da CPU, mas uma carga média muito baixa.

O comportamento é melhor ilustrado pelos seguintes gráficos do nosso sistema de monitoramento.

Porvoltadas11:57,autilizaçãodaCPUpassade25%para75%.Amédiadacarganãoéalteradasignificativamente.

Nósrodamosservidorescom12núcleoscom2hyperthreadscada.Osistemaoperacionalvêissocomo24CPUs.

OsdadosdeutilizaçãodaCPUsãocoletadosexecutando/usr/bin/mpstat601acadaminuto.Osdadosparaacolunaalleacoluna%usrsãomostradosnográficoacima.TenhocertezadequeissomostraamédiapordadosdaCPU,nãoautilização"empilhada". Enquanto vemos 75% de utilização no gráfico, vemos um processo mostrando usar cerca de 2000% de CPU "empilhada" em top .

O valor médio da carga é obtido de /proc/loadavg a cada minuto.

uname -a dá:

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

O Linux dist é Red Hat Enterprise Linux Server release 6.3 (Santiago)

Nós executamos um par de aplicações web Java sob carga bastante pesada nas máquinas, pensamos em 100 pedidos / s por máquina.

Se eu interpretar corretamente os dados de utilização da CPU, quando tivermos 75% de utilização da CPU, isso significa que nossas CPUs estão executando um processo em 75% do tempo, em média. No entanto, se nossas CPUs estiverem ocupadas 75% do tempo, não deveríamos ver uma média de carga maior? Como as CPUs poderiam estar 75% ocupadas enquanto temos apenas 2-4 trabalhos na fila de execução?

Estamos interpretando nossos dados corretamente? O que pode causar esse comportamento?

    
por K Erlandsson 12.02.2015 / 12:53

7 respostas

1

Embora a resposta de Matthew Ife tenha sido muito útil e nos tenha levado na direção certa, não foi exatamente o que causou o comportamento em nosso caso. No nosso caso, temos um aplicativo Java multi-thread que usa o conjunto de encadeamentos, por que nenhum trabalho é feito criando as tarefas reais.

No entanto, o trabalho real que os encadeamentos fazem é de curta duração e inclui esperas de IO ou esperas de sincronização. Como Mateus menciona em sua resposta, a média de carga é amostrada pelo SO, portanto, tarefas de curta duração podem ser perdidas.

Eu fiz um programa em Java que reproduziu o comportamento. A seguinte classe Java gera uma utilização da CPU de 28% (650% empilhada) em um dos nossos servidores. Ao fazer isso, a média da carga é de aproximadamente 1,3. A chave aqui é o sleep () dentro do thread, sem que o cálculo da carga esteja correto.

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();
    }

}

Para resumir, a teoria é que os threads em nossos aplicativos ficam inativos e executam trabalhos de curta duração, porque as tarefas não são amostradas corretamente pelo cálculo da média de carga.

    
por 17.02.2015 / 09:45
32

No Linux, pelo menos, a média de carga e a utilização da CPU são, na verdade, duas coisas diferentes. A média de carga é uma medida de quantas tarefas estão esperando em uma fila de execução do kernel (não apenas tempo de CPU, mas também atividade de disco) durante um período de tempo. A utilização da CPU é uma medida de quão ocupada a CPU está agora. A maior carga que um único encadeamento da CPU indexou em 100% por um minuto pode "contribuir" para a média de carga de 1 minuto é 1. Uma CPU de 4 núcleos com hyperthreading (8 núcleos virtuais) a 100% por 1 minuto contribuiria com 8 para a média de carga de 1 minuto.

Muitas vezes, esses dois números têm padrões que se correlacionam entre si, mas você não pode pensar neles como o mesmo. Você pode ter uma alta carga com quase 0% de utilização da CPU (como quando você tem muitos dados de E / S armazenados em um estado de espera) e você pode ter uma carga de 1 e 100% de CPU, quando você tem um único processo em execução inclinação total. Também por curtos períodos de tempo, você pode ver a CPU perto de 100%, mas a carga ainda é inferior a 1 porque a média das métricas ainda não foi "capturada".

Eu vi um servidor ter uma carga de mais de 15.000 (sim, realmente não é um erro de digitação) e uma CPU% de quase 0%. Isso aconteceu porque um compartilhamento do Samba estava tendo problemas e muitos e muitos clientes começaram a ficar presos em um estado de espera do IO. As chances são de que se você estiver vendo um número de carga alta regular sem atividade de CPU correspondente, você está tendo algum problema de armazenamento. Em máquinas virtuais, isso também pode significar que há outras VMs competindo pesadamente por recursos de armazenamento no mesmo host da VM.

    
por 12.02.2015 / 22:38
20

A carga é um número muito enganador. Tome com um grão de sal.

Se você gerar muitas tarefas em uma sucessão muito rápida, que são concluídas rapidamente, o número de processos na fila de execução é muito pequeno para registrar a carga para eles (as contagens do kernel são carregadas uma vez a cada cinco segundos).

Considere este exemplo, no meu host que tem 8 núcleos lógicos, este script python registrará um grande uso de CPU no topo (cerca de 85%), mas dificilmente qualquer carregamento.

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()

Outra implementação, essa evita wait em grupos de 8 (o que distorce o teste). Aqui, o pai sempre tenta manter o número de filhos no número de CPUs ativas, de modo que ele será muito mais ocupado do que o primeiro método e, esperamos, mais preciso.

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

A razão para esse comportamento é que o algoritmo gasta mais tempo criando processos filho do que executando a tarefa real (contando até 10000). As tarefas ainda não criadas não podem contar para o estado "executável", mas ocuparão% sys no tempo de CPU à medida que são geradas.

Assim, a resposta poderia ser, no seu caso, que qualquer trabalho que esteja sendo feito gera um grande número de tarefas em rápida sucessão (threads ou processos).

    
por 12.02.2015 / 14:05
5

Se a média de carga não aumentar muito, isso significa apenas que suas especificações de hardware e a natureza das tarefas a serem processadas resultam em uma boa taxa de transferência geral, evitando que elas sejam empilhadas na fila de tarefas por algum tempo.

Se houvesse um fenómeno de contenção porque, por exemplo, a complexidade média da tarefa é muito alta ou o tempo médio de processamento da tarefa requer muitos ciclos de CPU, então sim, a média de carga aumentaria.

ATUALIZAÇÃO:

Pode não estar claro na minha resposta original, então estou esclarecendo agora:

A fórmula exata do cálculo da média de carga é: loadvg = tasks running + tasks waiting (for cores) + tasks blocked .

Você pode definitivamente ter uma boa taxa de transferência e chegar perto de uma média de carga de 24, mas sem penalizar o tempo de processamento das tarefas. Por outro lado, você também pode ter 2-4 tarefas periódicas não concluídas com rapidez suficiente, então você verá o número de tarefas em espera (para ciclos de CPU) crescendo e, eventualmente, você atingirá uma média de carga alta. Outra coisa que pode acontecer é ter tarefas executando excelentes operações de E / S síncronas, bloqueando um núcleo, diminuindo o throughput e aumentando a fila de tarefas em espera (nesse caso, você poderá ver a mudança da iowait métrica)

    
por 12.02.2015 / 14:00
2

A média de carga inclui tarefas que são bloqueadas no disco IO, para que você possa ter zero utilização de cpu e uma média de carga de 10 apenas por ter 10 tarefas todas tentando ler a partir de um disco muito lento. Portanto, é comum que um servidor ocupado inicie o thrashing do disco e toda a busca cause muitas tarefas bloqueadas, aumentando a carga média, enquanto o uso da CPU cai, já que todas as tarefas estão bloqueadas no disco.

    
por 12.02.2015 / 21:34
0

A média de carga é o número médio de processos na fila da CPU. É específico para cada sistema, você não pode dizer que um LA é genericamente alto em todos os sistemas, e outro é baixo. Então você tem 12 núcleos, e para LA aumentar significativamente o número de processos deve ser muito alto.

Outra questão é o significado do gráfico "CPU Usage". Se for tirado do SNMP, como deveria ser, e sua implementação do SNMP for net-snmp , então, em apenas pilhas, o carregamento da CPU de cada uma das suas 12 CPUs. Portanto, para net-snmp , a quantidade total de carga da CPU é de 1200%.

Se minhas suposições estiverem corretas, o uso da CPU não aumentou significativamente. Assim, o LA não aumentou significativamente.

    
por 12.02.2015 / 13:21
0

O cenário aqui não é particularmente inesperado, embora seja um pouco incomum. O que Xavier toca, mas não desenvolve muito, é que embora o Linux (por padrão) e a maioria dos sabores do Unix implementem multitarefa preventiva, em uma máquina saudável, as tarefas raramente serão antecipadas. Cada tarefa recebe um intervalo de tempo para ocupar a CPU, só é antecipada se exceder esse tempo e há outras tarefas aguardando para serem executadas (observe que a carga relata o número médio de processos na CPU e aguardando execução) . Na maioria das vezes, um processo renderá em vez de ser interrompido.

(em geral, você só precisa se preocupar com o carregamento quando chega perto do número de CPUs - ou seja, quando o agendador começa a antecipar tarefas).

if our CPUs are busy 75% of the time, shouldn't we see higher load average?

É tudo sobre o padrão de atividade, claramente o aumento da utilização da CPU por algumas tarefas (provavelmente uma pequena mocidade) não estava tendo um efeito adverso no processamento de outras tarefas. Se você pudesse isolar as transações que estão sendo processadas, esperaria ver um novo grupo surgindo durante a desaceleração, enquanto o conjunto de tarefas existentes não foi afetado.

atualização

Um cenário comum em que a alta CPU pode ocorrer sem um grande aumento na carga é quando uma tarefa aciona uma (ou uma sequência) de outras tarefas, por exemplo, no recebimento de uma solicitação de rede, o manipulador roteia a solicitação para um thread separado, o thread separado faz algumas chamadas assíncronas para outros processos ... a amostragem da fila de execução faz com que a carga seja relatada mais baixa do que realmente é - mas não aumenta linearmente com o uso da CPU - a cadeia de tarefas disparadas não teria sido executada sem o evento inicial, e porque elas ocorrem (mais ou menos) seqüencialmente, a fila de execução não é inflada.

    
por 12.02.2015 / 14:55