Come profilare l'avvio lento di uno script della shell bash?


124

La mia shell bash impiega fino a 3-4 secondi per avviarsi, mentre se la avvio con --norcessa viene eseguita immediatamente.

Ho iniziato a "profilare" /etc/bash.bashrce ~/.bashrcinserendo manualmente returndichiarazioni e cercando miglioramenti di velocità, ma non è un processo quantitativo e non è efficiente.

Come posso profilare i miei script bash e vedere quali comandi impiegano più tempo per avviarsi?


3
Ho profilato gli script e la maggior parte del tempo è stata spesa durante l'installazione di bash_completion.
Andrea Spadaccini

1
Non è sorprendente dal momento che è piuttosto grande. Puoi velocizzarlo rimuovendo le parti che sai non ti serviranno mai se vuoi prenderti la briga di mantenere le tue modifiche attraverso gli aggiornamenti, ecc.
In pausa fino a nuovo avviso.

2
Puoi confrontare: time bash -c 'exit'e time bash -i -c 'exit'e potresti giocare con --norce --noprofile.
F. Hauri

Vedi anche questa risposta (disclaimer: è mia). Non esattamente quello che stai chiedendo, ma sicuramente correlato: unix.stackexchange.com/a/555510/384864
Johan Walles

Risposte:


128

Se hai GNU date(o un'altra versione che può produrre nanosecondi), fallo all'inizio di /etc/bash.bashrc(o ovunque desideri iniziare una traccia in qualsiasi script Bash):

PS4='+ $(date "+%s.%N")\011 '
exec 3>&2 2>/tmp/bashstart.$$.log
set -x

Inserisci

set +x
exec 2>&3 3>&-

alla fine di ~/.bashrc(o alla fine della sezione di qualsiasi script Bash di cui desideri interrompere la traccia). Il\011 è un carattere di tabulazione ottale.

Dovresti ottenere una traccia di accesso /tmp/bashstart.PID.log che mostri il timestamp in secondi.nanosecondi di ciascun comando eseguito. La differenza da una volta all'altra è il tempo impiegato dal passaggio intermedio.

Man mano che restringi le cose, puoi spostarti set -xpiù tardi e set +xprima (o raggruppare in modo selettivo diverse sezioni di interesse).

Sebbene non sia così fine come datei nanosecondi di GNU , Bash 5 include una variabile che fornisce il tempo in microsecondi. Usarlo ti evita di generare un eseguibile esterno per ogni riga e funziona su Mac o altrove che non hanno GNU date, purché tu abbia Bash 5, ovviamente. Modificare l'impostazione di PS4:

PS4='+ $EPOCHREALTIME\011 '

Come sottolineato da @pawamoy, puoi usare BASH_XTRACEFDper inviare l'output della traccia a un descrittore di file separato se hai Bash 4.1 o successivo. Da questa risposta :

#!/bin/bash

exec 5> command.txt
BASH_XTRACEFD="5"

echo -n "hello "

set -x
echo -n world
set +x

echo "!"

Ciò farà sì che l'output della traccia vada al file in command.txtuscita stdoute stdoutvenga emesso normalmente (o venga reindirizzato separatamente).


È normale che il prompt della shell sia invisibile e che i miei comandi non vengano riecheggiati? Comunque ho la traccia così posso iniziare l'analisi .. grazie mille!
Andrea Spadaccini

1
@AndreaSpadaccini: La finale execdovrebbe riportare fd2 alla normalità, quindi dovresti riavere il prompt.
In pausa fino a nuovo avviso.

7
... in realtà, con bash 4.2, si può fare di meglio: l'uso di \D{...}in PS4consente di espandere stringhe di formato ora completamente arbitrarie senza l'overhead delle prestazioni dell'avvio datecome sottoprocesso.
Charles Duffy

3
@CharlesDuffy: Sono entrambi davvero fantastici. Tuttavia GNU datecapisce %Ne Bash 4.2 no (perché strftime(3)non lo fa) sul sistema GNU - quindi arbitrario con i limiti. Il tuo punto di vista sulle prestazioni rispetto alla risoluzione è buono e un utente dovrebbe fare la scelta con saggezza, tenendo presente che il calo delle prestazioni è temporaneo solo durante il debug (e solo quando set -xè attivo).
In pausa fino a nuovo avviso.

1
Con Bash 4, si può anche usare la variabile BASH_XTRACEFD per reindirizzare l'output di debug a un altro descrittore di file rispetto a quello predefinito (2 o stderr). Aiuta immensamente quando arriva il momento di analizzare l'output (i dati di profilazione), poiché non è più necessario districare stderr e impostare l'output -x (così tanti casi limite).
pawamoy

107

profiling (4 risposte)

Modifica: marzo 2016 aggiungi scriptmetodo

Leggendo questo e poiché la creazione di profili è un passo importante, ho fatto alcuni test e ricerche su tutta questa domanda SO e ho già pubblicato le risposte.

C'è una risposta 4+:

  • Il primo è basato sull'idea di @ DennisWilliamson ma con un consumo di risorse molto inferiore
  • Il secondo era mio (prima di questo;)
  • Il terzo si basa sulla risposta @fgm, ma più preciso.
  • L'ultimo uso script, scriptreplaye file di tempistica .

  • Infine, un piccolo confronto delle prestazioni alla fine.

Utilizzo di set -xe datema con forcelle limitate

Prendi dall'idea di @ DennisWilliamson, ma con la seguente sintassi, ci sarà solo un fork iniziale per 3 comandi:

exec 3>&2 2> >(tee /tmp/sample-time.$$.log |
                 sed -u 's/^.*$/now/' |
                 date -f - +%s.%N >/tmp/sample-time.$$.tim)
set -x

Questa operazione verrà eseguita datesolo una volta. C'è una rapida demo / test per mostrare come funziona:

for i in {1..4};do echo now;sleep .05;done| date -f - +%N

Script di esempio:

#!/bin/bash

exec 3>&2 2> >( tee /tmp/sample-$$.log |
                  sed -u 's/^.*$/now/' |
                  date -f - +%s.%N >/tmp/sample-$$.tim)
set -x

for ((i=3;i--;));do sleep .1;done

for ((i=2;i--;))
do
    tar -cf /tmp/test.tar -C / bin
    gzip /tmp/test.tar
    rm /tmp/test.tar.gz
done

set +x
exec 2>&3 3>&-

Eseguendo questo script, crei 2 file: /tmp/sample-XXXX.loge /tmp/sample-XXXX.tim(dove XXXX è l'ID di processo dello script in esecuzione).

Potresti presentarli usando paste:

paste tmp/sample-XXXX.{tim,log}

Oppure puoi anche calcolare il tempo di differenza:

paste <(
    while read tim ;do
        crt=000000000$((${tim//.}-10#0$last))
        printf "%12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9}
        last=${tim//.}
      done < sample-time.24804.tim
  ) sample-time.24804.log 

 1388487534.391309713        + (( i=3 ))
 0.000080807        + (( i-- ))
 0.000008312        + sleep .1
 0.101304843        + (( 1 ))
 0.000032616        + (( i-- ))
 0.000007124        + sleep .1
 0.101251684        + (( 1 ))
 0.000033036        + (( i-- ))
 0.000007054        + sleep .1
 0.104013813        + (( 1 ))
 0.000026959        + (( i-- ))
 0.000006915        + (( i=2 ))
 0.000006635        + (( i-- ))
 0.000006844        + tar -cf /tmp/test.tar -C / bin
 0.022655107        + gzip /tmp/test.tar
 0.637042668        + rm /tmp/test.tar.gz
 0.000823649        + (( 1 ))
 0.000011314        + (( i-- ))
 0.000006915        + tar -cf /tmp/test.tar -C / bin
 0.016084482        + gzip /tmp/test.tar
 0.627798263        + rm /tmp/test.tar.gz
 0.001294946        + (( 1 ))
 0.000023187        + (( i-- ))
 0.000006845        + set +x

o su due colonne:

paste <(
    while read tim ;do
        [ -z "$last" ] && last=${tim//.} && first=${tim//.}
        crt=000000000$((${tim//.}-10#0$last))
        ctot=000000000$((${tim//.}-10#0$first))
        printf "%12.9f %12.9f\n" ${crt:0:${#crt}-9}.${crt:${#crt}-9} \
                                 ${ctot:0:${#ctot}-9}.${ctot:${#ctot}-9}
        last=${tim//.}
      done < sample-time.24804.tim
  ) sample-time.24804.log

Può rendere:

 0.000000000  0.000000000   + (( i=3 ))
 0.000080807  0.000080807   + (( i-- ))
 0.000008312  0.000089119   + sleep .1
 0.101304843  0.101393962   + (( 1 ))
 0.000032616  0.101426578   + (( i-- ))
 0.000007124  0.101433702   + sleep .1
 0.101251684  0.202685386   + (( 1 ))
 0.000033036  0.202718422   + (( i-- ))
 0.000007054  0.202725476   + sleep .1
 0.104013813  0.306739289   + (( 1 ))
 0.000026959  0.306766248   + (( i-- ))
 0.000006915  0.306773163   + (( i=2 ))
 0.000006635  0.306779798   + (( i-- ))
 0.000006844  0.306786642   + tar -cf /tmp/test.tar -C / bin
 0.022655107  0.329441749   + gzip /tmp/test.tar
 0.637042668  0.966484417   + rm /tmp/test.tar.gz
 0.000823649  0.967308066   + (( 1 ))
 0.000011314  0.967319380   + (( i-- ))
 0.000006915  0.967326295   + tar -cf /tmp/test.tar -C / bin
 0.016084482  0.983410777   + gzip /tmp/test.tar
 0.627798263  1.611209040   + rm /tmp/test.tar.gz
 0.001294946  1.612503986   + (( 1 ))
 0.000023187  1.612527173   + (( i-- ))
 0.000006845  1.612534018   + set +x

Utilizzando trap debuge /proc/timer_listsui kernel GNU / Linux recenti , senza fork .

Sotto i kernel recenti di GNU / Linux , potresti trovare un /procfile chiamato timer_list:

grep 'now at\|offset' /proc/timer_list
now at 5461935212966259 nsecs
  .offset:     0 nsecs
  .offset:     1383718821564493249 nsecs
  .offset:     0 nsecs

Dove l'ora corrente è la somma di 5461935212966259 + 1383718821564493249, ma in nanosecondi.

Quindi, per calcolare il tempo trascorso , non è necessario conoscere l'offset.

Per questo tipo di lavori, ho scritto elap.bash (V2) , che ha la seguente sintassi:

source elap.bash-v2

o

. elap.bash-v2 init

(Vedere i commenti per la sintassi completa)

Quindi potresti semplicemente aggiungere questa riga all'inizio del tuo script:

. elap.bash-v2 trap2

Piccolo campione:

#!/bin/bash

. elap.bash-v2 trap

for ((i=3;i--;));do sleep .1;done

elapCalc2
elapShowTotal \\e[1mfirst total\\e[0m

for ((i=2;i--;))
do
    tar -cf /tmp/test.tar -C / bin
    gzip /tmp/test.tar
    rm /tmp/test.tar.gz
done

trap -- debug
elapTotal \\e[1mtotal time\\e[0m

Esegui il rendering sul mio host:

 0.000947481 Starting
 0.000796900 ((i=3))
 0.000696956 ((i--))
 0.101969242 sleep .1
 0.000812478 ((1))
 0.000755067 ((i--))
 0.103693305 sleep .1
 0.000730482 ((1))
 0.000660360 ((i--))
 0.103565001 sleep .1
 0.000719516 ((1))
 0.000671325 ((i--))
 0.000754856 elapCalc2
 0.316018113 first total
 0.000754787 elapShowTotal \e[1mfirst total\e[0m
 0.000711275 ((i=2))
 0.000683408 ((i--))
 0.075673816 tar -cf /tmp/test.tar -C / bin
 0.596389329 gzip /tmp/test.tar
 0.006565188 rm /tmp/test.tar.gz
 0.000830217 ((1))
 0.000759466 ((i--))
 0.024783966 tar -cf /tmp/test.tar -C / bin
 0.604119903 gzip /tmp/test.tar
 0.005172940 rm /tmp/test.tar.gz
 0.000952299 ((1))
 0.000827421 ((i--))
 1.635788924 total time
 1.636657204 EXIT

Usando trap2invece di trapcome argomento per il comando sorgente:

#!/bin/bash

. elap.bash-v2 trap2
...

Renderà due colonne l' ultimo comando e il totale :

 0.000894541      0.000894541 Starting
 0.001306122      0.002200663 ((i=3))
 0.001929397      0.004130060 ((i--))
 0.103035812      0.107165872 sleep .1
 0.000875613      0.108041485 ((1))
 0.000813872      0.108855357 ((i--))
 0.104954517      0.213809874 sleep .1
 0.000900617      0.214710491 ((1))
 0.000842159      0.215552650 ((i--))
 0.104846890      0.320399540 sleep .1
 0.000899082      0.321298622 ((1))
 0.000811708      0.322110330 ((i--))
 0.000879455      0.322989785 elapCalc2
 0.322989785 first total
 0.000906692      0.323896477 elapShowTotal \e[1mfirst total\e[0m
 0.000820089      0.324716566 ((i=2))
 0.000773782      0.325490348 ((i--))
 0.024752613      0.350242961 tar -cf /tmp/test.tar -C / bin
 0.596199363      0.946442324 gzip /tmp/test.tar
 0.003007128      0.949449452 rm /tmp/test.tar.gz
 0.000791452      0.950240904 ((1))
 0.000779371      0.951020275 ((i--))
 0.030519702      0.981539977 tar -cf /tmp/test.tar -C / bin
 0.584155405      1.565695382 gzip /tmp/test.tar
 0.003058674      1.568754056 rm /tmp/test.tar.gz
 0.000955093      1.569709149 ((1))
 0.000919964      1.570629113 ((i--))
 1.571516599 total time
 0.001723708      1.572352821 EXIT

utilizzando strace

Sì, stracepotrebbe fare il lavoro:

strace -q -f -s 10 -ttt sample-script 2>sample-script-strace.log

Ma potrebbero fare molte cose!

wc sample-script-strace.log
    6925  57637 586518 sample-script-strace.log

Utilizzando un comando più limitato:

strace -f -s 10 -ttt -eopen,access,read,write ./sample-script 2>sample-script-strace.log

Scaricherà il registro più leggero:

  4519  36695 374453 sample-script-strace.log

A seconda di ciò che stai cercando, potresti essere più restrittivo:

 strace -f -s 10 -ttt -eaccess,open ./sample-script 2>&1 | wc
  189    1451   13682

Leggerli sarà un po 'più difficile:

{
    read -a first
    first=${first//.}
    last=$first
    while read tim line;do
        crt=000000000$((${tim//.}-last))
        ctot=000000000$((${tim//.}-first))
        printf "%9.6f %9.6f %s\n" ${crt:0:${#crt}-6}.${crt:${#crt}-6} \
            ${ctot:0:${#ctot}-6}.${ctot:${#ctot}-6} "$line"
        last=${tim//.}
      done
  } < <(
    sed </tmp/sample-script.strace -e '
        s/^ *//;
        s/^\[[^]]*\] *//;
        /^[0-9]\{4\}/!d
  ')

 0.000110  0.000110 open("/lib/x86_64-linux-gnu/libtinfo.so.5", O_RDONLY) = 4
 0.000132  0.000242 open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY) = 4
 0.000121  0.000363 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 4
 0.000462  0.000825 open("/dev/tty", O_RDWR|O_NONBLOCK) = 4
 0.000147  0.000972 open("/usr/lib/locale/locale-archive", O_RDONLY) = 4
 ...
 0.000793  1.551331 open("/etc/ld.so.cache", O_RDONLY) = 4
 0.000127  1.551458 open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 4
 0.000545  1.552003 open("/usr/lib/locale/locale-archive", O_RDONLY) = 4
 0.000439  1.552442 --- SIGCHLD (Child exited) @ 0 (0) ---

Lo script bash originale non è così facile da seguire in questo ...

Utilizzando script, scriptreplaye file di temporizzazione

Come parte di BSD Utils , script(and scriptreplay) è uno strumento molto vecchio che può essere utilizzato per profilare bash, con un ingombro molto ridotto.

script -t script.log 2>script.tim -c 'bash -x -c "
    for ((i=3;i--;));do sleep .1;done

    for ((i=2;i--;)) ;do
        tar -cf /tmp/test.tar -C / bin
        gzip /tmp/test.tar
        rm /tmp/test.tar.gz
    done
"'

Produrrà:

Script started on Fri Mar 25 08:29:37 2016
+ (( i=3 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ sleep .1
+ (( 1 ))
+ (( i-- ))
+ (( i=2 ))
+ (( i-- ))
+ tar -cf /tmp/test.tar -C / bin
+ gzip /tmp/test.tar
+ rm /tmp/test.tar.gz
+ (( 1 ))
+ (( i-- ))
+ tar -cf /tmp/test.tar -C / bin
+ gzip /tmp/test.tar
+ rm /tmp/test.tar.gz
+ (( 1 ))
+ (( i-- ))
Script done on Fri Mar 25 08:29:39 2016

e genera due file:

ls -l script.*
-rw-r--r-- 1 user user 450 Mar 25 08:29 script.log
-rw-r--r-- 1 user user 177 Mar 25 08:29 script.tim

Il file script.logcontiene tutte le tracce ed script.timè il file di temporizzazione :

head -n 4 script.*
==> script.log <==
Script started on Fri Mar 25 08:29:37 2016
+ (( i=3 ))
+ (( i-- ))
+ sleep .1

==> script.tim <==
0.435331 11
0.000033 2
0.000024 11
0.000010 2

È possibile visualizzare il tempo totale di esecuzione con la prima e l'ultima riga del file di log e / o riepilogando i tempi nel file di temporizzazione:

head -n1 script.log ;tail -n1 script.log 
Script started on Fri Mar 25 08:29:37 2016
Script done on Fri Mar 25 08:29:39 2016

sed < script.tim  's/ .*$//;H;${x;s/\n/+/g;s/^\+//;p};d' | bc -l
2.249755

Nel file di temporizzazione, il secondo valore è il numero di byte successivi nel file di registro corrispondente. Ciò ti consente di riprodurre il file di registro facoltativamente con un fattore di accelerazione :

scriptreplay script.{tim,log}

o

scriptreplay script.{tim,log} 5

o

 scriptreplay script.{tim,log} .2

Anche mostrare i tempi e i comandi fianco a fianco è un po 'più complesso:

exec 4<script.log
read -u 4 line
echo $line ;while read tim char;do
    read -u 4 -N $char -r -s line
    echo $tim $line
  done < script.tim &&
while read -u 4 line;do
    echo $line
done;exec 4<&-
Script started on Fri Mar 25 08:28:51 2016
0.558012 + (( i=3 ))
0.000053 
0.000176 + (( i-- ))
0.000015 
0.000059 + sleep .1
0.000015 
 + sleep .1) + (( 1 ))
 + sleep .1) + (( 1 ))
 + tar -cf /tmp/test.tar -C / bin
0.035024 + gzip /tmp/test.tar
0.793846 + rm /tmp/test.tar.gz
 + tar -cf /tmp/test.tar -C / bin
0.024971 + gzip /tmp/test.tar
0.729062 + rm /tmp/test.tar.gz
 + (( i-- )) + (( 1 ))
Script done on Fri Mar 25 08:28:53 2016

Test e conclusione

Per eseguire i test, ho scaricato il secondo esempio su bash complex hello world , questo script impiega circa 0,72 secondi per essere completato sul mio host.

Ho aggiunto in cima allo script uno di:

  • per elap.bashfunzione

    #!/bin/bash
    
    source elap.bash-v2 trap2
    
    eval "BUNCHS=(" $(perl <<EOF | gunzip
    ...
  • di set -xePS4

    #!/bin/bash
    
    PS4='+ $(date "+%s.%N")\011 '
    exec 3>&2 2>/tmp/bashstart.$$.log
    set -x
    
    eval "BUNCHS=(" $(perl <<EOF | gunzip
    ...
  • by set -xe fork iniziale per il comando di esecuzione lungo

    #!/bin/bash
    
    exec 3>&2 2> >(tee /tmp/sample-time.$$.log |
                     sed -u 's/^.*$/now/' |
                     date -f - +%s.%N >/tmp/sample-time.$$.tim)
    set -x
    
    eval "BUNCHS=(" $(perl <<EOF | gunzip
  • da script(e set +x)

    script -t helloworld.log 2>helloworld.tim -c '
        bash -x complex_helloworld-2.sh' >/dev/null 

Volte

E confronta i tempi di esecuzione (sul mio host):

  • Diretto 0,72 sec
  • elap.bash 13.18 sec
  • imposta + data @ PS4 54,61 sec
  • set + 1 forchetta 1,45 sec
  • script e file di temporizzazione 2.19 sec
  • strace 4.47 sec

Uscite

  • per elap.bashfunzione

         0.000950277      0.000950277 Starting
         0.007618964      0.008569241 eval "BUNCHS=(" $(perl <<EOF | gunzi
         0.005259953      0.013829194 BUNCHS=("2411 1115 -13 15 33 -3 15 1
         0.010945070      0.024774264 MKey="V922/G/,2:"
         0.001050990      0.025825254 export RotString=""
         0.004724348      0.030549602 initRotString
         0.001322184      0.031871786 for bunch in "${BUNCHS[@]}"
         0.000768893      0.032640679 out=""
         0.001008242      0.033648921 bunchArray=($bunch)
         0.000741095      0.034390016 ((k=0))
  • di set -xePS4

    ++ 1388598366.536099290  perl
    ++ 1388598366.536169132  gunzip
    + 1388598366.552794757   eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 15 1
    ++ 1388598366.555001983  BUNCHS=("2411 1115 -13 15 33 -3 15 13111 -6 1
    + 1388598366.557551018   MKey=V922/G/,2:
    + 1388598366.558316839   export RotString=
    + 1388598366.559083848   RotString=
    + 1388598366.560165147   initRotString
    + 1388598366.560942633   local _i _char
    + 1388598366.561706988   RotString=
  • da set -xe fork iniziale al comando di esecuzione lungo (e il mio secondo pastescript di esempio)

     0.000000000  0.000000000    ++ perl
     0.008141159  0.008141159    ++ gunzip
     0.000007822  0.008148981    + eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 
     0.000006216  0.008155197    ++ BUNCHS=("2411 1115 -13 15 33 -3 15 13111 
     0.000006216  0.008161413    + MKey=V922/G/,2:
     0.000006076  0.008167489    + export RotString=
     0.000006007  0.008173496    + RotString=
     0.000006006  0.008179502    + initRotString
     0.000005937  0.008185439    + local _i _char
     0.000006006  0.008191445    + RotString=
  • di strace

     0.000213  0.000213 brk(0)                = 0x17b6000
     0.000044  0.000257 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
     0.000047  0.000304 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7faf1c0dc000
     0.000040  0.000344 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
     0.000040  0.000384 open("/etc/ld.so.cache", O_RDONLY) = 4
     ...
     0.000024  4.425049 close(10)             = 0
     0.000042  4.425091 rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
     0.000028  4.425119 read(255, "", 4409)   = 0
     0.000058  4.425177 exit_group(0)         = ?
  • di script

    Le script a débuté sur ven 25 mar 2016 09:18:35 CET
    0.667160 ++ gunzip
    0.000025 
    0.000948 ++ perl
    0.000011 
    0.005338 + eval 'BUNCHS=(' '"2411' 1115 -13 15 33 -3 15 13111 -6 1 111 4
    0.000044 1223 15 3311 121121 17 3311 121121 1223 3311 121121 17 3311 121
    0.000175 ++ BUNCHS=("2411 1115 -13 15 33 -3 15 13111 -6 15 1114 15 12211
    0.000029 1 1321 12211 412 21211 33 21211 -2 15 2311 11121 232 121111 122
    0.000023 4 3311 121121 12221 3311 121121 12221 3311 121121 1313 -6 15 33

Conclusione

Bene! Se la mia pura bash è più veloce del fork fino ad oggi su ogni comando , la mia pura bash implica alcune operazioni su ogni comando.

Il modo di dedicare un processo indipendente per la registrazione e l'archiviazione è chiaramente più efficiente.

strace è un modo interessante, più dettagliato, ma difficile da leggere.

script, con scriptreplaye anche il fattore di accelerazione è molto bello, non la stessa precisione che si basa sullo scambio della console invece dell'esecuzione del processo, ma molto leggero ed efficiente (non lo stesso obiettivo, non lo stesso utilizzo).

Infine, penso che il più efficiente, in termini di leggibilità e prestazioni set + 1 fork, sia , La prima di questa risposta, ma alla fine, a seconda del caso specifico, io uso qualche volta stracee / o scriptanche.



2
La sezione Times è piuttosto istruttiva e fa capire che i fork non sono niente da starnutire (anzi dominano completamente molti tipi di script). +1 per una buona (se lunga) risposta. Forse in futuro dovresti prendere in considerazione la possibilità di pubblicare risposte separate
vedi

1
Molte grazie, @sehe! Qui troverai un file sorgente bash completo pronto per essere eseguito : elap-bash-v3 (con alcune funzionalità come consentire l'uso trasparente di STDIN e STDERR )
F. Hauri

1
Nelle versioni recenti di bash (> = 4.1), si può fare exec {BASH_XTRACEFD}>invece di exec 3>&2 2>che popolerà il file di log solo con l'output di registrazione della traccia e non con altri output di stderr.
ws_e_c421

1
Il metodo di elaborazione di una singola data è molto intelligente e la mia preferenza per la precisione al di sotto del secondo. Perché script.shposso semplicemente fare bash -c "exec {BASH_XTRACEFD}> >(tee trace.log | sed -u 's/^.*$//' | date -f - +%s.%N > timing.log); set -x; . script.she ottenere dati di profilazione senza apportare modifiche script.sh. Quando non è necessaria una precisione al di sotto del secondo, mi piace il bash -c "exec {BASH_XTRACEFD}>trace.log; set -x; PS4='+\t'; . script.shtempo che timbra ogni linea di traccia con una precisione al secondo e senza fork fino alla data (overhead basso).
ws_e_c421

17

Spesso aiuta a rintracciare le chiamate di sistema

strace -c -f ./script.sh

Dal manuale:

-c Conta il tempo, le chiamate e gli errori per ogni chiamata di sistema e riporta un riepilogo all'uscita dal programma.

-f Traccia processi figlio ...

Questo non è esattamente quello che vuoi e quello che ti mostrerebbe un profiler orientato alla linea, ma di solito aiuta a trovare gli hot spot.


5

Potresti dare un'occhiata al trapcomando con la condizione DEBUG . C'è un modo per impostare uno o più comandi da eseguire insieme ai tuoi comandi. Vedi le note alla risposta.


@ Dennis Williamson: non lo uso da un po ', ma la guida del mio sistema afferma che "Se un SIGNAL_SPEC è DEBUG, ARG viene eseguito dopo ogni semplice comando."

Da Bash 4.0.33 help trap: "Se SIGNAL_SPEC è DEBUG, ARG viene eseguito prima di ogni semplice comando." In Bash 3.2, dice "dopo". È un errore di battitura. A partire da Bash 2.05b, viene eseguito prima. Riferimento : "Questo documento descrive in dettaglio le modifiche tra questa versione, bash-2.05b-alpha1, e la versione precedente, bash-2.05a-release. ... 3. Nuove funzionalità in Bash ... w. La trappola DEBUG è ora eseguito prima di comandi semplici, comandi ((...)), comandi [[...]] condizionali e cicli for ((...)). " Il test in ogni versione conferma che è prima .
In pausa fino a nuovo avviso.

@ Dennis Williamson: Ok, allora questa è la versione che ho.

0

Time, xtrace, bash -x set -xe set+x( http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html ) rimangono il modo ortodosso per eseguire il debug di uno script.

Tuttavia, per allargare il nostro orizzonte, è possibile dare una spunta ad alcuni sistemi di debugging e profiling disponibili per i soliti programmi Linux [qui una delle liste] , es. Dovrebbe risultare utile uno basato su valgrind soprattutto per il debug della memoria o sysprof per profilare l'intero sistema:

Per sysprof:

Con sysprof, puoi profilare tutte le applicazioni in esecuzione sulla tua macchina, inclusa un'applicazione multithread o multiprocesso ...

E poi seleziona il ramo dei sottoprocessi che trovi interessante.


Per Valgrind:
con un po 'di palestra in più, sembra sia possibile rendere visibili a Valgrind alcuni programmi che di solito installiamo da binario (es. OpenOffice ).

E 'possibile leggere dalle FAQ di valgrind che Valgrindprofileranno i processi figli se esplicitamente richiesto.

... Anche se per impostazione predefinita traccia solo il profilo del processo di primo livello, quindi se il tuo programma viene avviato da uno script di shell , script Perl o qualcosa di simile, Valgrind traccerà la shell, o l'interprete Perl, o equivalente. ..

Lo farà con questa opzione abilitata

 --trace-children=yes 

Riferimenti aggiuntivi:


1
Non il downvoter, ma la maggior parte di questi suggerimenti, sebbene interessanti, non sono molto rilevanti qui. Porre una domanda appropriata e rispondere da solo è più gradito qui: Google "stackoverflow auto risponde" per l'etichetta pertinente.
Blaisorblade

0

Questo post di Alan Hargreaves descrive il metodo di profilazione dello script della shell Bourne utilizzando il provider DTrace. Per quanto ne so, funziona con Solaris e OpenSolaris (vedere: / bin / sh DTrace Provider ).

Quindi dato il seguente script dtrace ( sh_flowtime.da GH basato sull'originale ):

#!/usr/sbin/dtrace -Zs
#pragma D option quiet
#pragma D option switchrate=10

dtrace:::BEGIN
{
        depth = 0;
        printf("%s %-20s  %-22s   %s %s\n", "C", "TIME", "FILE", "DELTA(us)", "NAME");
}

sh*:::function-entry
{
        depth++;
        printf("%d %-20Y  %-22s %*s-> %s\n", cpu, walltimestamp,
            basename(copyinstr(arg0)), depth*2, "", copyinstr(arg1));
}

sh*:::function-return
{
        printf("%d %-20Y  %-22s %*s<- %s\n", cpu, walltimestamp,
            basename(copyinstr(arg0)), depth*2, "", copyinstr(arg1));
        depth--;
}

sh*:::builtin-entry
{
        printf("%d %-20Y  %-22s %*s   > %s\n", cpu, walltimestamp,
            basename(copyinstr(arg0)), depth*2, "", copyinstr(arg1));
}

sh*:::command-entry
{
        printf("%d %-20Y  %-22s %*s   | %s\n", cpu, walltimestamp,
            basename(copyinstr(arg0)), depth*2, "", copyinstr(arg1));
}

è possibile tracciare il flusso della funzione inclusi i tempi delta.

Output di esempio:

# ./sh_flowtime.d
C TIME                  FILE                 DELTA(us)  -- NAME
0 2007 Aug 10 18:52:51  func_abc.sh                  0   -> func_a
0 2007 Aug 10 18:52:51  func_abc.sh                 54      > echo
0 2007 Aug 10 18:52:52  func_abc.sh            1022880      | sleep
0 2007 Aug 10 18:52:52  func_abc.sh                 34     -> func_b
0 2007 Aug 10 18:52:52  func_abc.sh                 44        > echo
0 2007 Aug 10 18:52:53  func_abc.sh            1029963        | sleep
0 2007 Aug 10 18:52:53  func_abc.sh                 44       -> func_c
0 2007 Aug 10 18:52:53  func_abc.sh                 43          > echo
0 2007 Aug 10 18:52:54  func_abc.sh            1029863          | sleep
0 2007 Aug 10 18:52:54  func_abc.sh                 33       <- func_c
0 2007 Aug 10 18:52:54  func_abc.sh                 14     <- func_b
0 2007 Aug 10 18:52:54  func_abc.sh                  7   <- func_a

Quindi utilizzando sort -nrk7 comando, puoi ordinare l'output per mostrare le chiamate più consumate.

Non sono a conoscenza di sonde di provider disponibili per altre shell, quindi fai qualche ricerca (ricerca GitHub?) O se vuoi investire un po 'di tempo, puoi scriverlo in base all'esempio sh esistente : (vedi: Come attivare sh Provider DTrace? ).

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.