La mia ipotesi di base sul sistema "iowait" non regge


13

La mia ipotesi di base è che quando i soli fattori limitanti di un processo sono il disco e la CPU, l'utilizzo totale della CPU "iowait" + CPU dovrebbe essere pari almeno al 100% di una CPU logica. (In altri casi questo non regge. Ad esempio quando si scarica un file utilizzando wget, la rete è spesso il fattore limitante).

Questa ipotesi è violata da un semplice test. È previsto? Se è previsto, c'è una serie di condizioni in cui dovrei aspettarmi che la mia ipotesi sia vera?

Ci sono alcuni retroscena su "iowait" qui: come fa una CPU a sapere che c'è IO in sospeso? La risposta qui cita l'idea contro-intuitiva, secondo cui lo iowait cumulativo "può diminuire in determinate condizioni". Mi chiedo se il mio semplice test possa innescare una condizione così non documentata?

AGGIORNAMENTO : saltare alla risposta .

La risposta ha un test più semplice di quello che ho usato originariamente. Ho conservato la domanda originale qui sotto. La domanda originale potrebbe mostrare alcuni dettagli aggiuntivi.

Domanda originale

In un breve test, utilizzo ddper richiedere al kernel di generare byte casuali e scriverli in un file. Eseguo il ddcomando all'interno perf stat, solo per ottenere un conteggio del tempo di CPU trascorso all'interno del kernel. Lo eseguo anche all'interno perf trace -s, per segnalare il tempo trascorso all'interno write(). Allo stesso tempo, corro vmstat 5su un altro terminale, per vedere il sistema "iowait".

  1. Mi aspettavo di vedere almeno un'intera CPU come "non inattiva", ovvero il 100% delle volte che è in esecuzione o arrestata ma in attesa di IO (stato "iowait"). Non era.
  2. (Inoltre, mi aspettavo di vedere che il tempo "iowait" corrispondeva approssimativamente al tempo trascorso in write (). Ma non sembrava farlo.)

I risultati dettagliati e l'ambiente di test sono mostrati di seguito. Viene anche mostrato un test alternativo, in cui la mia ipotesi ha tenuto. Nota: era necessario correre perf statall'interno perf trace, non viceversa. Questo è dettagliato qui: "perf stat" (e "time"!) Mostra risultati errati quando si esegue "perf trace - s"?

Informazioni di base su "iowait"

Di seguito è la definizione presa dalla sarmanpage:

% Iowait:

Percentuale di tempo in cui la CPU o le CPU erano inattive durante le quali il sistema aveva una richiesta di I / O su disco in sospeso.

Pertanto,% iowait significa che dal punto di vista della CPU, nessuna attività era eseguibile, ma era in corso almeno un I / O. iowait è semplicemente una forma di tempo di inattività in cui nulla può essere programmato. Il valore può essere utile o meno per indicare un problema di prestazioni, ma indica all'utente che il sistema è inattivo e potrebbe aver richiesto più lavoro.

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

C'è anche un articolo più lungo: Comprendere I / O Wait (o perché lo 0% inattivo può essere OK) . Questo spiega come è possibile vedere chiaramente la definizione dal codice del kernel. Il codice è leggermente cambiato, ma l'idea è ancora chiara:

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

L'articolo mostra anche una serie di esperimenti correlati su un sistema a CPU singola. Alcuni degli esperimenti usano anche ddcon if=/dev/urandom ! Tuttavia gli esperimenti non includono il mio test dd if=/dev/urandom of=test.out . Usa solo dd if=/dev/urandom of=/dev/null .

"IO aspetta" è un po 'più difficile da pensare ora perché usiamo sistemi multi-CPU, ma penso di averlo ancora capito, basato sul codice citato.

Ambiente

Ho quattro CPU logiche.

Uso LVM e il filesystem ext4. Non sto usando alcuna crittografia sul mio disco o filesystem. Non ho alcun filesystem di rete montato, quindi non sto leggendo o scrivendo un filesystem di rete.

I seguenti risultati provengono dal kernel 4.20.15-200.fc29.x86_64, usando lo noopscheduler IO. Anche lo cfqscheduler IO fornisce risultati simili.

(Ho anche visto risultati simili su una build del kernel che si basava su una configurazione simile, ma era più vicina alla versione 5.1 del kernel e all'utilizzo mq-deadline. Quindi utilizzava il nuovo blk-mqcodice).

Test e risultati

$ 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%
...

Ho letto la iowaitfigura dalla wacolonna di vmstat. Puoi sapere quando il test è in esecuzione guardando la iocolonna ( bo= 1K blocchi di output).

$ 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

Risultati dei test in cui si trova (all'interno di una VM)

Ho provato lo stesso test all'interno di una VM con 1 CPU, che eseguiva il kernel 5.0.9-301.fc30.x86_64e utilizzava mq-deadline(e quindi blk-mq). In questo test, ha funzionato come mi aspettavo.

$ 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%
...

Uscita di 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

Ho provato ad aggiungere a caldo una CPU alla VM e testare di nuovo. I risultati erano variabili: a volte mostrava circa lo 0% nella colonna inattiva, e talvolta mostrava circa il 50% inattiva (ovvero una CPU su due). Nel caso dello 0% "inattivo", "iowait" era molto alto, vale a dire più di una CPU. Cioè il mio punto di aspettativa 2 non era corretto. Posso accettare a malincuore questa apparente limitazione di "iowait" sui sistemi multi-CPU. (Anche se non lo capisco del tutto. Se qualcuno vuole spiegarlo esattamente, sarebbe fantastico). Tuttavia, in entrambi i casi "inattivo" non era superiore al 50%, quindi questi test erano ancora coerenti con la mia prima ipotesi su "iowait".

Ho provato a spegnere la VM e avviarla con 4 CPU. Allo stesso modo, spesso avevo esattamente il 75% di inattività e talvolta avevo un minimo di 50% di inattività, ma non vedevo più del 75% di inattività (ovvero più di tre CPU su quattro).

Considerando che sul sistema fisico con 4 CPU, posso ancora riprodurre il risultato di oltre l'80% inattivo, come mostrato sopra.


Ti dispiacerebbe annotare un po 'le tue due aspettative. Potresti aggiungere se il valore reale era più o meno delle tue aspettative. Capisco che questo sia nei dati grezzi, sarebbe solo un po 'più leggibile. Non sono chiaro il motivo per cui ti aspetti 1 CPU (100%). Sulla base di uno dei tuoi collegamenti e del codice del kernel che citi, una singola operazione IO passerà tutto il tempo IDLE al tempo IOWAIT (tutti i 4 core - 400%).
Philip Couling,

@PhilipCouling "Mi aspettavo di vedere almeno un'intera CPU come" non inattiva "... Non lo era". Il tempo di inattività era superiore al previsto, il che incolpa del fatto che il tempo di attesa sia inferiore al previsto. Nel codice del kernel, penso this_rq()->nr_iowaitsia il numero di attività che sono in attesa di essere utilizzate solo io_schedule() sulla CPU corrente . Ho sbagliato?
sourcejedi

1
Non ne sono affatto sicuro, ma lo trovo sorprendente se lo è. Questa sorpresa sembra coincidere con la risposta di Stephen Kitt in cui dice " iowaitcerca di misurare il tempo speso aspettando l'I / O, in generale. Non è monitorato da una CPU specifica, né può essere" . Lasciami sottolineare che non ne sono certo, sto solo esprimendo sorpresa.
Philip Couling,

@PhilipCouling se corri atop, o atopsar -c 5vedrai cifre sull'utilizzo per CPU. Includono iowait e le cifre iowait per CPU possono mostrare valori diversi, diversi da zero :-). Oppure sar -P ALL 1, se non si utilizza atop. Questo è il modo in cui il iowaitmodello è stato esteso per i sistemi multi-CPU ... Quello che non sono chiaro è se questo modello è effettivamente utilizzabile o se questo è un modo che consente al codice iowait di continuare a funzionare quando c'è solo una CPU online, ma per il resto non è affidabile.
sourcejedi

Risposte:


7

Avviso di contenuto : questo post include collegamenti a varie discussioni e codici su Linux. Alcuni contenuti collegati non soddisfano l'attuale Codice di condotta per StackExchange o per Linux . Per lo più "insultano il codice [ma non la persona]". Tuttavia, viene utilizzato un po 'di lingua, che semplicemente non deve essere ripetuta. Ti chiedo di evitare di imitare, fare da pappagallo o discutere di questo linguaggio.


Ri: iowait vs contabilità inattiva è "incoerente" - iowait è troppo basso

Il 05/07/2019 12:38, Peter Zijlstra ha scritto:

Venerdì 5 luglio 2019 alle 12:25:46 +0100, Alan Jenkins ha scritto:

Il mio tempo "iowait" della cpu sembra essere segnalato in modo errato. Sai perché questo potrebbe accadere?

Perché iowait è un numero casuale magico che non ha alcun significato sano. Personalmente preferirei cancellare tutto, tranne ABI : /

Vedi anche il commento vicino a nr_iowait ()

Grazie. Prendo [i problemi menzionati nella documentazione attuale] come problemi diversi, ma intendi che non c'è molta richiesta (o punto) per "risolvere" il mio problema.

Ho trovato il mio problema È stato già notato cinque anni fa e non sarebbe banale da risolvere.

Il tempo "iowait" viene aggiornato dalla funzione 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;
}

Funziona come mi aspettavo, se si sta approssimando il tempo della CPU "campionando" con l'interruzione del timer tradizionale ("tick"). Tuttavia, potrebbe non funzionare se il segno di spunta è disattivato durante il tempo di inattività per risparmiare energia - NO_HZ_IDLE. Potrebbe anche fallire se si consente di disattivare la spunta per motivi di prestazioni - NO_HZ_FULL- perché ciò richiede l'avvio VIRT_CPU_ACCOUNTING. La maggior parte dei kernel Linux utilizza la funzione di risparmio energetico. Alcuni sistemi integrati non utilizzano nessuna delle funzionalità. Ecco la mia spiegazione:

Al termine dell'IO, il dispositivo invia un interrupt . Il gestore di interrupt del kernel riattiva il processo usando try_to_wake_up(). Sottrae uno dal nr_iowaitcontatore:

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

Se il processo viene attivato su una CPU inattiva, quella CPU chiama account_idle_time(). A seconda della configurazione applicabile, questa viene chiamata da tick_nohz_account_idle_ticks()da __tick_nohz_idle_restart_tick()o da vtime_task_switch()da finish_task_switch().

A questo punto, ->nr_iowaitè già stato diminuito. Se viene ridotto a zero, non verrà registrato alcun tempo di iowait.

Questo effetto può variare: dipende dalla CPU su cui è stato attivato il processo. Se il processo viene svegliato sulla stessa CPU che ha ricevuto l'interrupt di completamento dell'IO, il tempo di inattività potrebbe essere contabilizzato in precedenza, prima che ->nr_iowaitvenga ridotto. Nel mio caso, ho scoperto che CPU 0 gestisce l' interruzione ahci , osservando watch cat /proc/interrupts.

Ho provato questo con una semplice lettura sequenziale:

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

Se taskset -c 0 ...blocco il comando su CPU 0 utilizzando , vedo i valori "corretti" per iowait. Se lo aggiungo a un'altra CPU, vedo valori molto più bassi. Se eseguo normalmente il comando, varia a seconda del comportamento dello scheduler, che è cambiato tra le versioni del kernel. Nei kernel recenti (4.17, 5.1, 5.2-rc5-ish), il comando sembra impiegare circa 1/4 del tempo sulla CPU 0, perché il tempo "iowait" è ridotto a quella frazione.

(Non spiegato: perché eseguire questo test sulla mia macchina virtuale ora sembra riprodurre "iowait" corretto, per ogni (o qualsiasi) CPU. Sospetto che ciò possa comportare IRQ_TIME_ACCOUNTING, sebbene questa funzionalità sia utilizzata anche nei miei test al di fuori della VM.

Inoltre non ho confermato esattamente il motivo per cui la soppressione NO_HZ_IDLEdà iowait "corretto" per ogni CPU su 4.17+, ma non su 4.16 o 4.15.

L'esecuzione di questo test sulla mia macchina virtuale sembra riprodurre "corretto" iowait, per ciascuna (o qualsiasi) CPU. Ciò è dovuto a IRQ_TIME_ACCOUNTING. Viene anche utilizzato nei test all'esterno della VM, ma ottengo più interruzioni durante i test all'interno della VM. In particolare, ci sono più di 1000 "Interrupt di chiamata funzione" al secondo sulla CPU virtuale su cui gira "dd".

Quindi non dovresti fare troppo affidamento sui dettagli della mia spiegazione :-)

Ci sono alcuni retroscena su "iowait" qui: come fa una CPU a sapere che c'è IO in sospeso? La risposta qui cita l'idea contro-intuitiva, secondo cui lo iowait cumulativo "può diminuire in determinate condizioni". Mi chiedo se il mio semplice test possa innescare una condizione così non documentata?

Sì.

Quando ho cercato questo per la prima volta, ho scoperto che si parlava di "singhiozzo". Inoltre, il problema è stato illustrato mostrando che il tempo cumulativo di "iowait" era non monotonico. Cioè a volte è saltato indietro (diminuito). Non è stato semplice come il test sopra.

Tuttavia, quando hanno investigato hanno trovato lo stesso problema fondamentale. Una soluzione è stata proposta e prototipata, rispettivamente da Peter Zijlstra e Hidetoshi Seto. Il problema è spiegato nel messaggio di copertina:

[RFC PATCH 0/8] rielaborare contabilità iowait (07-07-2014)

Non ho trovato prove di progressi oltre a questo. C'era una domanda aperta su uno dei dettagli. Inoltre, la serie completa ha toccato il codice specifico per le architetture CPU PowerPC, S390 e IA64. Quindi dico che questo non è banale da risolvere.


2
Potresti confermare o negare (usando vmstat): il kernel 4.15 fa quello che ti aspetti, indipendentemente dagli stati inattivi abilitati o disabilitati; Il kernel 4.16 non fa ciò che ti aspetti a prescindere. vmstat sembra usare /proc/stat, ma io uso /sys/devices/system/cpu/cpu*/cpuidle/state*/usage, e per quanto ne so sono sempre stati precisi (+ - un paio di%). Non riesco a usare i miei strumenti su kernel più vecchi perché alcune nuove informazioni non ci sono. Nota che mi aspetto che test1 e test3 forniscano gli stessi risultati, perché il segno di spunta non si ferma mai nello stato Inattivo 0.
Doug Smythies,

1
Volevo scrivere /sys/devices/system/cpu/cpu*/cpuidle/state*/timesopra. Posso solo pensare di dividere in due il kernel, una volta tra il kernel 4.15 e 4.16, poi di nuovo tra 4.16 e 4.17. La seconda sezione potrebbe andare più veloce con le conoscenze acquisite dalla prima. Non ho tempo di farlo proprio ora, forse tra qualche giorno.
Doug Smythies,

1
@DougSmythies grazie! I tuoi test funzionano esattamente come i miei originali. I miei risultati 4.15.0-1.fc28e sono 4.16.0-300.fc28d'accordo con i tuoi.
sourcejedi,

OK, penso di essere pronto per una risposta alla lista di linux-pm. Speriamo che qualcuno abbia qualche intuizione e possiamo evitare una bisection del kernel.
Doug Smythies,

1
@DougSmythies wtf. first bisection (4.15-4.16) dà github.com/torvalds/linux/commit/806486c377e3 "sched / fair: non migrare se prev_cpu è inattivo". Quindi ho provato con taskset -c 0su v4.15 ... L'esecuzione del ddcomando con taskset -c 2dà lo iowait "giusto". Il blocco su qualsiasi altra CPU dà lo iowait "sbagliato". E cpu2 è dove ddfinisce se non lo uso taskset. ( atopVedevo il tempo per-cpu iowait). Sto dando un'occhiata alla seconda sezione, per spiegare il comportamento attuale. Sulla possibilità potrebbe esserci stato qualche commento al riguardo nella seconda modifica.
sourcejedi
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.