Minha suposição básica sobre o sistema "iowait" não é válida

13

Minha suposição básica é que, quando apenas os fatores limitantes de um processo são disco e CPU, o total de "iowait" + uso da CPU deve ser igual a pelo menos 100% de uma CPU lógica. (Em outros casos, isso não será válido. Por exemplo, ao baixar um arquivo usando wget, a rede geralmente é o fator limitante).

Essa suposição é violada por um teste simples. Isso é esperado? Se é esperado, existe um conjunto de condições em que eu deveria esperar que minha suposição se mantivesse verdadeira?

Há alguns antecedentes sobre "iowait" aqui: como uma CPU sabe que há IO pendente? A resposta aqui cita a ideia contra-intuitiva de que o iowait cumulativo "pode ​​diminuir em determinadas condições". Gostaria de saber se o meu teste simples pode estar desencadeando uma condição não documentada?

ATUALIZAÇÃO : pule para a resposta .

A resposta tem um teste mais simples do que o usado originalmente. Eu preservei a pergunta original abaixo. A pergunta original pode mostrar alguns detalhes adicionais.

Pergunta original

Em um teste curto, eu uso ddpara solicitar que o kernel gere bytes aleatórios e os escreva em um arquivo. Eu executo o ddcomando dentro perf stat, apenas para obter uma contagem do tempo de CPU gasto dentro do kernel. Também corro para dentro perf trace -s, para relatar o tempo gasto lá dentro write(). Ao mesmo tempo, eu corro vmstat 5em outro terminal, para ver o sistema "iowait".

  1. Eu esperava ver pelo menos uma CPU inteira como "não ociosa", ou seja, 100% do tempo em que ela está sendo executada ou interrompida, mas aguardando IO (estado "iowait"). Não era.
  2. (Além disso, eu esperava ver o tempo "iowait" corresponder aproximadamente ao tempo gasto em write (). Mas não pareceu fazê-lo.)

Os resultados detalhados e o ambiente de teste são mostrados abaixo. Também é mostrado um teste alternativo, onde minha suposição se manteve. Nota: era necessário correr para perf statdentro perf trace, e não o contrário. Isso é detalhado aqui: "perf stat" (e "time"!) Mostram resultados incorretos ao executar "perf trace - s"?

Informações básicas sobre "iowait"

A seguir está a definição retirada da página de sarmanual:

% iowait:

Porcentagem de tempo em que a CPU ou CPUs ficaram inativas durante as quais o sistema teve uma solicitação de E / S de disco pendente.

Portanto,% iowait significa que, do ponto de vista da CPU, nenhuma tarefa foi executável, mas pelo menos uma E / S estava em andamento. O iowait é simplesmente uma forma de tempo ocioso em que nada pode ser agendado. O valor pode ou não ser útil para indicar um problema de desempenho, mas informa ao usuário que o sistema está ocioso e poderia ter exigido mais trabalho.

https://support.hpe.com/hpsc/doc/public/display?docId=c02783994

Há também um artigo mais longo: Entendendo a espera de E / S (ou por que 0% de ociosidade pode estar OK) . Isso explica como você pode ver claramente a definição no código do kernel. O código mudou um pouco, mas a ideia ainda é clara:

/*
 * Account for idle time.
 * @cputime: the CPU time spent in idle wait
 */
void account_idle_time(u64 cputime)
{
    u64 *cpustat = kcpustat_this_cpu->cpustat;
    struct rq *rq = this_rq();

    if (atomic_read(&rq->nr_iowait) > 0)
        cpustat[CPUTIME_IOWAIT] += cputime;
    else
        cpustat[CPUTIME_IDLE] += cputime;
}

O artigo também mostra várias experiências relacionadas em um sistema de CPU única. Alguns dos experimentos até mesmo usar ddcom if=/dev/urandom ! No entanto, os experimentos não incluem o meu teste dd if=/dev/urandom of=test.out . Ele usa apenas dd if=/dev/urandom of=/dev/null .

A "espera de E / S" é um pouco mais complicada de se pensar agora, porque usamos sistemas com várias CPUs, mas acho que ainda o entendo, com base no código citado.

Meio Ambiente

Eu tenho quatro CPUs lógicas.

Eu uso o LVM e o sistema de arquivos ext4. Não estou usando nenhuma criptografia no meu disco ou sistema de arquivos. Eu não tenho nenhum sistema de arquivos de rede montado, então não estou lendo ou escrevendo um sistema de arquivos de rede.

Os resultados abaixo são do kernel 4.20.15-200.fc29.x86_64, usando o noopagendador de E / S. O cfqplanejador de E / S também fornece resultados semelhantes.

(Eu também vi resultados semelhantes em uma compilação do kernel que se baseava em uma configuração semelhante, mas estava mais próxima da versão 5.1 do kernel e em uso mq-deadline. Portanto, estava usando o novo blk-mqcódigo).

Teste e resultados

$ sudo perf trace -s \
       perf stat \
       dd if=/dev/urandom of=test.out bs=1M oflag=direct count=3000

3000+0 records in
3000+0 records out
3145728000 bytes (3.1 GB, 2.9 GiB) copied, 31.397 s, 100 MB/s

 Performance counter stats for 'dd if=/dev/urandom of=test.out bs=1M oflag=direct count=3000':

         18,014.26 msec task-clock                #    0.574 CPUs utilized          
             3,199      context-switches          #    0.178 K/sec                  
                 4      cpu-migrations            #    0.000 K/sec                  
               328      page-faults               #    0.018 K/sec                  
    45,232,163,658      cycles                    #    2.511 GHz                    
    74,538,278,379      instructions              #    1.65  insn per cycle         
     4,372,725,344      branches                  #  242.737 M/sec                  
         4,650,429      branch-misses             #    0.11% of all branches        

      31.398466725 seconds time elapsed

       0.006966000 seconds user
      17.910332000 seconds sys

 Summary of events:
...
 dd (4620), 12156 events, 12.0%

   syscall            calls    total       min       avg       max      stddev
                               (msec)    (msec)    (msec)    (msec)        (%)
   --------------- -------- --------- --------- --------- ---------     ------
   read                3007 17624.985     0.002     5.861    12.345      0.21%
   write               3003 13722.837     0.004     4.570   179.928      2.63%
   openat                12     0.371     0.002     0.031     0.267     70.36%
...

Eu li a iowaitfigura da wacoluna de vmstat. Você pode saber quando o teste está sendo executado observando a iocoluna ( bo= 1K bloqueia a saída).

$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 5126892 176512 1486060   0   0  1788  4072  321  414  4  4 83  9  0
 1  0      0 5126632 176520 1485988   0   0     0     7  212  405  0  1 99  0  0
 0  0      0 5126884 176520 1485988   0   0     0     0  130  283  0  0 99  0  0
 0  0      0 5126948 176520 1485908   0   0     0     1  157  325  0  0 99  0  0
 0  0      0 5126412 176520 1486412   0   0   115     0  141  284  0  0 99  0  0
 0  2      0 5115724 176548 1487056   0   0     0  6019 18737 10733  3  6 89  2  0
 1  0      0 5115708 176580 1487104   0   0     3 91840 1276  990  0 13 77  9  0
 1  0      0 5115204 176600 1487128   0   0     2 91382 1382 1014  0 14 81  4  0
 1  0      0 5115268 176636 1487084   0   0     4 88281 1257  901  0 14 83  3  0
 0  1      0 5113504 177028 1487764   0   0    77 92596 1374 1111  0 15 83  2  0
 1  0      0 5114008 177036 1487768   0   0     0 113282 1460 1060  0 16 81  2  0
 1  0      0 5113472 177044 1487792   0   0     0 110821 1489 1118  0 16 74 10  0
 0  0      0 5123852 177068 1487896   0   0     0 20537  631  714  1  3 94  2  0
 0  0      0 5123852 177076 1487856   0   0     0    10  324  529  2  1 98  0  0
 2  0      0 5123852 177084 1487872   0   0     0    70  150  299  0  0 99  0  0

Resultados do teste onde ele se mantém (dentro de uma VM)

Eu tentei o mesmo teste dentro de uma VM com 1 CPU, que estava executando o kernel 5.0.9-301.fc30.x86_64e usando mq-deadline(e, portanto, blk-mq). Neste teste, funcionou como eu esperava.

$ sudo perf trace -s \
       perf stat \
       dd if=/dev/urandom of=test.out bs=1M oflag=direct count=3000
[sudo] password for alan-sysop:
3000+0 records in
3000+0 records out
3145728000 bytes (3.1 GB, 2.9 GiB) copied, 46.8071 s, 67.2 MB/s

 Performance counter stats for 'dd if=/dev/urandom of=test.out bs=1M oflag=direct count=3000':

         18,734.89 msec task-clock                #    0.400 CPUs utilized
            16,690      context-switches          #    0.891 K/sec
                 0      cpu-migrations            #    0.000 K/sec
               328      page-faults               #    0.018 K/sec
   <not supported>      cycles
   <not supported>      instructions
   <not supported>      branches
   <not supported>      branch-misses

      46.820355993 seconds time elapsed

       0.011840000 seconds user
      18.531449000 seconds sys


 Summary of events:
...
 dd (1492), 12156 events, 38.4%

   syscall            calls    total       min       avg       max      stddev
                               (msec)    (msec)    (msec)    (msec)        (%)
   --------------- -------- --------- --------- --------- ---------     ------
   write               3003 28269.070     0.019     9.414  5764.657     22.39%
   read                3007 18371.469     0.013     6.110    14.848      0.53%
   execve                 6    10.399     0.012     1.733    10.328     99.18%
...

Saída de vmstat 5:

$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----                                                                     
 r  b  swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st                                                                     
 0  0     0 726176  52128 498508    0    0  2040   231  236  731  7  5 77 11  0                                                                     
 0  0     0 726176  52136 498508    0    0     0    10   25   46  0  0 99  1  0                                                                     
 0  0     0 726208  52136 498508    0    0     0     0   29   56  0  0 100  0  0                                                                    
 0  1     0 702280  55944 511780    0    0  2260 13109 4399 9049  3 17 55 25  0                                                                     
 0  1     0 701776  56040 511960    0    0    18 129582 1406 1458 0 73  0 27  0                                                                    
 0  2     0 701524  56156 512168    0    0    22 87060  960  991  0 50  0 50  0                                                                     
 3  1     0 701524  56228 512328    0    0    14 118170 1301 1322 0 68  0 32  0                                                                    
 1  1     0 701272  56260 512392    0    0     6 86426  994  982  0 53  0 46  0                                                                     
 0  2     0 701020  56292 512456    0    0     6 56115  683  660  0 37  0 63  0                                                                     
 3  2     0 700540  56316 512504    0    0     5 33450  446  457  0 26  0 74  0                                                                     
 0  2     0 700860  56332 512536    0    0     3 16998  311  240  0 19  0 81  0                                                                     
 1  2     0 700668  56368 512616    0    0     7 32563  443  428  0 24  0 76  0                                                                     
 1  0     0 700668  56392 512648    0    0     3 20338  245  272  0 12  0 88  0                                                                   
 0  1     0 707096  56408 512920    0    0    54 20913  312  530  0 12 79  8  0                                                                     
 0  0     0 707064  56432 512920    0    0     0    49   39   64  0  0 45 55  0                                                                     
 0  0     0 707064  56432 512920    0    0     0     0   24   46  0  0 100  0  0                                                                    
 0  0     0 707064  56432 512920    0    0     0    80   28   47  0  0 100  0  0

Tentei adicionar quente uma CPU à VM e testar novamente. Os resultados foram variáveis: algumas vezes, mostrou cerca de 0% na coluna ociosa, e outras, 50% ocioso (ou seja, uma em cada duas CPUs). No caso de 0% de "ocioso", "iowait" era muito alto, ou seja, mais do que uma CPU. Ou seja, minha expectativa no ponto 2 não estava correta. Posso aceitar de má vontade essa aparente limitação de "iowait" em sistemas com várias CPUs. (Embora eu não entenda direito. Se alguém quiser explicar exatamente, isso seria ótimo). No entanto, "inativo" não estava acima de 50% em ambos os casos, portanto esses testes ainda eram consistentes com minha primeira suposição sobre "iowait".

Tentei desligar a VM e iniciá-la com 4 CPUs. Da mesma forma, muitas vezes eu tinha exatamente 75% de ociosidade e, às vezes, 50% de ociosidade, mas não havia mais de 75% de ociosidade (ou seja, mais de três em cada quatro CPUs).

Enquanto no sistema físico com 4 CPUs, ainda posso reproduzir o resultado de mais de 80% ocioso, como mostrado acima.

sourcejedi
fonte
Você se importaria de anotar um pouco suas duas expectativas. Você poderia adicionar se o valor real foi maior ou menor que a sua expectativa. Eu entendo que isso está nos dados brutos, seria apenas um pouco mais legível. Não sei ao certo por que você espera 1 CPU (100%). Com base em um de seus links e no código do kernel que você cita, uma única operação de E / S alternará todo o tempo IDLE para o tempo de IOWAIT (todos os 4 núcleos - 400%).
Philip Couling 14/05/19
@PhilipCouling "Eu esperava ver pelo menos uma CPU inteira como" não ociosa "... Não era". O tempo ocioso foi maior do que o esperado, o que eu culpo pelo tempo do iowait ser menor do que eu esperava. No código do kernel, acho que this_rq()->nr_iowaité o número de tarefas que estão aguardando usando io_schedule() apenas a CPU atual . Estou errado?
sourcejedi
1
Não tenho certeza, mas acho surpreendente se for. Essa surpresa parece coincidir com a resposta de Stephen Kitt, onde ele diz que " iowaittenta medir o tempo gasto na espera de E / S, em geral. Não é rastreado por uma CPU específica, nem pode ser" . Deixe-me enfatizar que não tenho certeza disso, apenas expressando surpresa.
Philip Couling 14/05/19
@PhilipCouling se você executar atop, ou atopsar -c 5verá os valores de uso por CPU. Eles incluem iowait, e os números por CPU iowait podem mostrar valores diferentes e diferentes de zero :-). Ou sar -P ALL 1, se você não usar atop. É assim que o iowaitmodelo foi estendido para sistemas com várias CPUs ... O que não sei é se esse modelo é realmente utilizável ou se é uma maneira de permitir que o código iowait continue a funcionar quando houver apenas uma CPU on-line, mas de outra forma não é confiável.
Fonte # jedi #

Respostas:

7

Aviso de conteúdo : este post inclui links para várias discussões e códigos do Linux. Alguns conteúdos vinculados não atendem ao Código de Conduta atual do StackExchange ou do Linux . Principalmente eles "insultam o código [mas não a pessoa]". No entanto, é usada alguma linguagem que simplesmente não deve ser repetida. Peço que evite imitar, papagaio ou debate sobre esse idioma.


Re: Iowait vs contabilidade ociosa é "inconsistente" - iowait é muito baixo

Em 05/07/2019 12:38, Peter Zijlstra escreveu:

Em Sex, 05/07/2019 às 12:25:46 +0100, Alan Jenkins escreveu:

O tempo da minha CPU "iowait" parece ser relatado incorretamente. Você sabe por que isso pode acontecer?

Porque iowait é um número aleatório mágico que não tem significado sadio. Pessoalmente, eu prefiro excluir a coisa toda, exceto ABI : /

Veja também o comentário perto de nr_iowait ()

Obrigado. Considero [os problemas mencionados na documentação atual] como problemas diferentes, mas você quer dizer que não há muita demanda (ou ponto) para "corrigir" meu problema.

Eu encontrei o meu problema. Já foi notado há cinco anos e não seria trivial consertar.

O tempo "iowait" é atualizado pela função account_idle_time():

/*
 * Account for idle time.
 * @cputime: the CPU time spent in idle wait
 */
void account_idle_time(u64 cputime)
{
    u64 *cpustat = kcpustat_this_cpu->cpustat;
    struct rq *rq = this_rq();

    if (atomic_read(&rq->nr_iowait) > 0)
        cpustat[CPUTIME_IOWAIT] += cputime;
    else
        cpustat[CPUTIME_IDLE] += cputime;
}

Isso funciona como eu esperava, se você estiver aproximando o tempo da CPU "amostrando" com a interrupção tradicional do timer ("tick"). No entanto, pode não funcionar se o tique for desativado durante o tempo ocioso para economizar energia - NO_HZ_IDLE. Também pode falhar se você permitir que o tiquetaque seja desativado por motivos de desempenho - NO_HZ_FULL- porque isso requer iniciarVIRT_CPU_ACCOUNTING . A maioria dos kernels Linux usa o recurso de economia de energia. Alguns sistemas embarcados não usam nenhum desses recursos. Aqui está a minha explicação:

Quando o IO estiver concluído, o dispositivo envia uma interrupção . O manipulador de interrupção do kernel ativa o processo usando try_to_wake_up(). Subtrai um do nr_iowaitbalcão:

if (p->in_iowait) {
    delayacct_blkio_end(p);
    atomic_dec(&task_rq(p)->nr_iowait);
}

Se o processo for ativado em uma CPU inativa, essa CPU chama account_idle_time(). Dependendo de qual configuração se aplica, isso é chamado de tick_nohz_account_idle_ticks()from __tick_nohz_idle_restart_tick()ou from vtime_task_switch()fromfinish_task_switch() .

A essa altura, ->nr_iowaitjá foi diminuído. Se for reduzido a zero, nenhum tempo de iowait será registrado.

Esse efeito pode variar: depende da CPU em que o processo foi ativado. Se o processo for acordado na mesma CPU que recebeu a interrupção de conclusão de E / S, o tempo ocioso poderá ser contabilizado anteriormente, antes de ->nr_iowaitser diminuído. No meu caso, achei que a CPU 0 lida com a interrupção ahci , olhando watch cat /proc/interrupts.

Eu testei isso com uma leitura sequencial simples:

dd if=largefile iflag=direct bs=1M of=/dev/null

Se eu fixar o comando na CPU 0 usando taskset -c 0 ..., vejo valores "corretos" para iowait. Se eu fixá-lo em uma CPU diferente, vejo valores muito mais baixos. Se eu executar o comando normalmente, ele varia dependendo do comportamento do planejador, que foi alterado entre as versões do kernel. Nos kernels recentes (4.17, 5.1, 5.2-rc5-ish), o comando parece gastar cerca de 1/4 do tempo na CPU 0, porque o tempo "iowait" é reduzido para essa fração.

(Não explicado: por que a execução deste teste na minha máquina virtual agora parece reproduzir o iowait "correto" para cada (ou qualquer) CPU. Suspeito que isso possa envolver IRQ_TIME_ACCOUNTING, embora esse recurso também esteja sendo usado nos meus testes fora da VM.

Também não confirmei exatamente por que a supressão NO_HZ_IDLEfornece iowait "correto" para cada CPU em 4.17+, mas não em 4.16 ou 4.15.

A execução desse teste na minha máquina virtual parece reproduzir o iowait "correto", para cada (ou qualquer) CPU. Isso é devido ao IRQ_TIME_ACCOUNTING . Também é usado nos testes fora da VM, mas recebo mais interrupções ao testar dentro da VM. Especificamente, existem mais de 1000 "Interrupções de chamadas de funções" por segundo na CPU virtual em que "dd" é executado.

Portanto, você não deve confiar muito nos detalhes da minha explicação :-)

Há alguns antecedentes sobre "iowait" aqui: como uma CPU sabe que há IO pendente? A resposta aqui cita a ideia contra-intuitiva de que o iowait cumulativo "pode ​​diminuir em determinadas condições". Gostaria de saber se o meu teste simples pode estar desencadeando uma condição não documentada?

Sim.

Quando procurei pela primeira vez, encontrei boatos de "soluços". Além disso, o problema foi ilustrado, mostrando que o tempo cumulativo de "iowait" era não monotônico. Ou seja, às vezes pulou para trás (diminuiu). Não foi tão direto quanto o teste acima.

No entanto, quando eles investigaram, encontraram o mesmo problema fundamental. Uma solução foi proposta e prototipada por Peter Zijlstra e Hidetoshi Seto, respectivamente. O problema é explicado na mensagem de capa:

[RFC PATCH 0/8] retrabalha a contabilidade de iowait (07-07-2014)

Não encontrei evidências de progresso além disso. Havia uma pergunta em aberto sobre um dos detalhes. Além disso, a série completa tocou o código específico para as arquiteturas de CPU PowerPC, S390 e IA64. Então eu digo que isso não é trivial de corrigir.

sourcejedi
fonte
2
Você pode confirmar ou negar (usando vmstat): O Kernel 4.15 faz o que você espera, independentemente dos estados de inatividade ativados ou desativados; O Kernel 4.16 não faz o que você espera, independentemente. O vmstat parece usar /proc/stat, mas eu o uso /sys/devices/system/cpu/cpu*/cpuidle/state*/usagee, até onde sei , sempre foi preciso (+ - alguns%). Não posso usar minhas ferramentas em kernels antigos porque algumas informações novas não estão lá. Note que eu espero test1 e test3 para dar os mesmos resultados, porque o carrapato nunca pára no estado ocioso 0.
Doug Smythies
1
Eu pretendia escrever /sys/devices/system/cpu/cpu*/cpuidle/state*/timeacima. Só consigo pensar em dividir o kernel, uma vez entre o kernel 4.15 e 4.16, depois novamente entre 4.16 e 4.17. A segunda bissecção pode ir mais rápido com o conhecimento adquirido a partir da primeira. Não tenho tempo para fazer isso agora, talvez em alguns dias.
Doug Smythies 03/07/19
1
@DougSmythies thank you! Seus testes funcionam tão bem quanto os meus originais. Meus resultados 4.15.0-1.fc28e 4.16.0-300.fc28concordo com os seus.
sourcejedi
OK. Acho que estou pronto para uma resposta da lista linux-pm. Espero que alguém tenha alguma ideia e possamos evitar uma bissecção do kernel.
Doug Smythies 03/07/19
1
@DougSmythies wtf. a primeira bissecção ( 4.15-4.16 ) fornece ao github.com/torvalds/linux/commit/806486c377e3 "sched / fair: não migre se o prev_cpu estiver ocioso". Então eu testei com taskset -c 0a v4.15 ... Executar o ddcomando com taskset -c 2dá o iowait "certo". Fixar em qualquer outra CPU fornece o iowait "errado". E cpu2 é onde ddtermina se eu não usar taskset. (Eu costumava atopver o tempo de CPU por iowait). Estou dando uma olhada na segunda bissecção, para explicar o comportamento atual. Na chance, pode ter havido algum comentário sobre isso na segunda alteração.
sourcejedi