Как профилировать медленный запуск сценария оболочки bash?

124

Моя оболочка bash запускается до 3-4 секунд, а если я запускаю ее, --norcона запускается немедленно.

Я начал «профилирование» /etc/bash.bashrcи ~/.bashrcвручную вставив returnзаявления и ищут улучшения в скорости, но это не количественный процесс , и это не является эффективным.

Как я могу профилировать свои сценарии bash и посмотреть, какие команды запускаются больше всего?

Андреа Спадаччини
источник
3
Я профилировал скрипты, и большую часть времени я потратил на настройку bash_completion.
Андреа Спадаччини,
1
Это не удивительно, ведь он довольно большой. Вы можете ускорить это, удалив части, которые, как вы знаете, вам никогда не понадобятся, если вы хотите заниматься сохранением изменений в обновлениях и т. Д.
Приостановлено до дальнейшего уведомления.
2
Можно сравнить: time bash -c 'exit'и time bash -i -c 'exit'и может играть с --norcи --noprofile.
F. Hauri
См. Также этот ответ (отказ от ответственности: это мой). Не совсем то, о чем вы спрашиваете, но определенно связано: unix.stackexchange.com/a/555510/384864
Йохан Уоллес

Ответы:

128

Если у вас есть GNU date(или другая версия, которая может выводить наносекунды), сделайте это в начале /etc/bash.bashrc(или где бы вы ни хотели начать трассировку в любом сценарии Bash):

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

Добавить

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

в конце ~/.bashrc(или в конце раздела любого сценария Bash, трассировку которого вы хотите остановить). Это \011восьмеричный символ табуляции.

Вы должны получить журнал трассировки, /tmp/bashstart.PID.logкоторый показывает временную метку seconds.nanoseconds каждой выполненной команды. Разница от одного раза к другому - это количество времени, которое занял промежуточный шаг.

По мере того, как вы сужаете круг вопросов, вы можете двигаться дальше set -xи set +xраньше (или выборочно ограничивать несколько интересующих разделов).

Хотя он не такой детализированный, как dateнаносекунды GNU , Bash 5 включает переменную, которая дает время в микросекундах. Его использование избавляет вас от создания внешнего исполняемого файла для каждой строки и работает на Mac или где-либо еще, где нет GNU date- конечно, если у вас есть Bash 5. Измените настройку PS4:

PS4='+ $EPOCHREALTIME\011 '

Как указано в @pawamoy, вы можете использовать BASH_XTRACEFDдля отправки вывода трассировки в отдельный файловый дескриптор, если у вас Bash 4.1 или новее. Из этого ответа :

#!/bin/bash

exec 5> command.txt
BASH_XTRACEFD="5"

echo -n "hello "

set -x
echo -n world
set +x

echo "!"

Это приведет к тому, что вывод трассировки перейдет в command.txtоставленный файл stdoutи stdoutбудет выводиться нормально (или будет перенаправлен отдельно).

Приостановлено до дальнейшего уведомления.
источник
Это нормально, что приглашение оболочки невидимо и мои команды не отображаются эхом? Тем не менее, у меня есть трассировка, поэтому я могу начать анализ ... большое спасибо!
Андреа Спадаччини
1
@AndreaSpadaccini: последний execдолжен вернуть fd2 в нормальное состояние, так что вы должны вернуть приглашение.
Приостановлено до дальнейшего уведомления.
7
... на самом деле, с bash 4.2 можно добиться большего - использование \D{...}in PS4позволяет расширять полностью произвольные строки формата времени без дополнительных затрат производительности на запуск dateв качестве подпроцесса.
Charles Duffy
3
@CharlesDuffy: Они оба действительно крутые. Однако GNU dateпонимает, %Nа Bash 4.2 - нет (потому strftime(3)что не понимает ) в системе GNU - так произвольно с ограничениями. Ваше мнение о производительности и разрешении является правильным, и пользователь должен сделать выбор с умом, помня, что снижение производительности является временным только во время отладки (и только тогда, когда set -xоно действует).
Приостановлено до дальнейшего уведомления.
1
В Bash 4 также можно использовать переменную BASH_XTRACEFD для перенаправления вывода отладки на другой файловый дескриптор, отличный от стандартного (2 или stderr). Это очень помогает, когда приходит время анализировать вывод (данные профилирования), поскольку больше не нужно распутывать stderr и устанавливать -x output (так много крайних случаев).
pawamoy
107

Профилирование (4 ответа)

Изменить:script метод добавления в марте 2016 г.

Прочитав это, и поскольку профилирование является важным шагом, я провел несколько тестов и исследований по всему этому вопросу SO и уже опубликовал ответы.

Есть 4+ ответа:

  • Первый основан на идее @DennisWilliamson, но требует гораздо меньшего потребления ресурсов.
  • Второй был моим (до этого;)
  • Третий основан на ответе @fgm, но более точен.
  • Последнее использование script, scriptreplayи файл синхронизации .

  • Напоследок, небольшое сравнение выступлений.

Использование set -xи, dateно с ограниченными вилками

Возьмите идею @DennisWilliamson, но со следующим синтаксисом будет только одна начальная вилка на 3 команды:

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

Это будет выполняться dateтолько один раз. Есть небольшая демонстрация / тест, чтобы показать, как это работает:

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

Пример сценария:

#!/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>&-

Запустив этот сценарий, вы создаете 2 файла: /tmp/sample-XXXX.logи /tmp/sample-XXXX.tim(где XXXX - это идентификатор процесса запущенного сценария).

Вы можете представить их, используя paste:

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

Или вы даже можете вычислить время разницы:

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

или на двух столбцах:

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

Может оказывать:

 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

Использование trap debugи /proc/timer_listна последних ядрах GNU / Linux без вилок .

В последних ядрах GNU / Linux вы можете найти /procфайл с именем timer_list:

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

Где текущее время - это сумма 5461935212966259 + 1383718821564493249, но в наносекундах.

Таким образом, для вычисления прошедшего времени нет необходимости знать смещение.

Для такого рода заданий я написал elap.bash (V2) , который будет получен с использованием следующего синтаксиса:

source elap.bash-v2

или

. elap.bash-v2 init

(Полный синтаксис см. В комментариях)

Итак, вы можете просто добавить эту строку в начало своего скрипта:

. elap.bash-v2 trap2

Маленький образец:

#!/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

Сделайте рендеринг на моем хосте:

 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

Использование trap2вместо trapаргумента исходной команды:

#!/bin/bash

. elap.bash-v2 trap2
...

Отрендерит две колонки последней команды и итога :

 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

С помощью strace

Да, straceсправился бы:

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

Зато можно было сделать много всего!

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

Использование более ограниченной команды:

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

Сбросим более легкое бревно:

  4519  36695 374453 sample-script-strace.log

В зависимости от того, что вы ищете, вы можете ввести более строгие ограничения:

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

Читать их будет немного сложнее:

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

Оригинальный Баш скрипт не так легко следовать в этом ...

Использование script, scriptreplayи файл синхронизации

Как часть BSD Utils , scriptscriptreplay) - очень старый инструмент, который можно использовать для профилирования bash, с очень маленьким размером.

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
"'

Изготовим:

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

и сгенерируйте два файла:

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

Файл script.logсодержит все трассировки и script.timявляется файлом времени :

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

Вы можете увидеть общее время выполнения с первой и последней строками файла журнала и / или суммируя время в файле времени:

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

В файле синхронизации второе значение - это количество следующих байтов в соответствующем файле журнала. Это дает вам возможность воспроизводить файл журнала опционально с коэффициентом ускорения :

scriptreplay script.{tim,log}

или

scriptreplay script.{tim,log} 5

или

 scriptreplay script.{tim,log} .2

Отображение времени и команд бок о бок тоже немного сложнее:

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

Тесты и заключение

Чтобы провести тесты, я загрузил второй образец в bash complex hello world , выполнение этого скрипта на моем хосте занимает около 0,72 секунды.

Я добавил вверху скрипта одно из:

  • по elap.bashфункции

    #!/bin/bash
    
    source elap.bash-v2 trap2
    
    eval "BUNCHS=(" $(perl <<EOF | gunzip
    ...
  • по set -xиPS4

    #!/bin/bash
    
    PS4='+ $(date "+%s.%N")\011 '
    exec 3>&2 2>/tmp/bashstart.$$.log
    set -x
    
    eval "BUNCHS=(" $(perl <<EOF | gunzip
    ...
  • by set -xи начальная вилка для длинной команды exec

    #!/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
  • по scriptset +x)

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

раз

И сравните время выполнения (на моем хосте):

  • Прямой 0,72 с
  • elap.bash 13,18 сек
  • установить + дата @ PS4 54,61 сек
  • комплект + 1 вилка 1,45 сек
  • скрипт и файл времени 2,19 сек.
  • Strace 4.47 сек

Выходы

  • по elap.bashфункции

         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))
  • по set -xиPS4

    ++ 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=
  • by set -xи начальная вилка для длинной команды exec (и мой второй pasteпример скрипта)

     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=
  • по 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)         = ?
  • по 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

Вывод

Хорошо! Если мой чистый bash быстрее, чем разветвление для каждой команды , мой чистый bash подразумевает некоторые операции для каждой команды.

Способ выделения независимого процесса для регистрации и хранения явно более эффективен.

strace интересный способ, более подробный, но трудный для чтения.

script, с scriptreplayкоэффициентом ускорения и тоже очень хороши, не такой точности, поскольку это основано на обмене консолью вместо выполнения процесса, но очень легкое и эффективное (не та же цель, не то же использование).

Наконец, я думаю, что более эффективным с точки зрения читаемости и производительности является set + 1 forkпервый из этого ответа, но в порядке, в зависимости от конкретного случая я иногда использую straceи / или scriptтоже.

Ф. Хаури
источник
2
Раздел Times довольно информативен и убеждает в том, что вилки - это не то, на что можно чихать (действительно, они полностью доминируют над многими видами скриптов). +1 за хороший (если протяжный) ответ. Возможно, в будущем вам стоит подумать о публикации отдельных ответов
см.
1
Большое спасибо, @sehe! Здесь вы найдете полный готовый к запуску исходный файл на bash: elap-bash-v3 (с некоторыми функциями, такими как разрешение прозрачного использования STDIN и STDERR )
Ф. Хаури
1
В последних версиях bash (> = 4.1) вы можете exec {BASH_XTRACEFD}>вместо exec 3>&2 2>этого заполнить файл журнала только выходными данными журнала трассировки, а не другими выходными данными stderr.
ws_e_c421 03
1
Выполнение метода обработки с одной датой очень умен, и я предпочитаю субсекундную точность. Ведь script.shя могу просто делать bash -c "exec {BASH_XTRACEFD}> >(tee trace.log | sed -u 's/^.*$//' | date -f - +%s.%N > timing.log); set -x; . script.shи получать данные профилирования без изменения script.sh. Когда не требуется субсекундная точность, мне нравится, bash -c "exec {BASH_XTRACEFD}>trace.log; set -x; PS4='+\t'; . script.shкогда каждая строка трассировки маркируется со второй точностью и без разветвления до даты (низкие накладные расходы).
ws_e_c421 03
17

Часто помогает отслеживать системные вызовы

strace -c -f ./script.sh

Из руководства:

-c Подсчитывать время, количество вызовов и ошибок для каждого системного вызова и сообщать сводку при выходе из программы.

-f Отслеживать дочерние процессы ...

Это не совсем то, что вам нужно, и то, что покажет вам линейно-ориентированный профилировщик, но обычно он помогает находить горячие точки.

Фриц Г. Менер
источник
5

Вы можете посмотреть trapкоманду с условием DEBUG . Есть способ установить команды, которые будут выполняться вместе с вашими командами. См. Примечания к ответу.


источник
@ Деннис Уильямсон: Я не использовал его некоторое время, но в справке моей системы указано, что «Если SIGNAL_SPEC имеет значение DEBUG, ARG выполняется после каждой простой команды».
Из Bash 4.0.33 help trap: «Если SIGNAL_SPEC имеет значение DEBUG, ARG выполняется перед каждой простой командой». В Bash 3.2 написано «после». Это опечатка. Начиная с Bash 2.05b, он работал раньше. Ссылка : «В этом документе подробно описаны изменения между этой версией, bash-2.05b-alpha1, и предыдущей версией, bash-2.05a-release. ... 3. Новые возможности в Bash ... w. Ловушка DEBUG теперь запускать перед простыми командами, ((...)) командами, [[...]] условными командами и для ((...)) циклов. " Тестирование в каждой версии подтверждает, что это было раньше .
Приостановлено до дальнейшего уведомления.
@ Деннис Уильямсон: Хорошо, тогда вот какая у меня версия. Я исправляю ответ :)
0

Time, xtrace, bash -x set -xи set+x( http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html ) остаются традиционным способом отладки скрипта.

Тем не менее, чтобы расширить наш кругозор, можно проверить некоторую систему для отладки и профилирования, доступную для обычных программ Linux [здесь один из списков] , например, это должно привести к полезной системе, основанной на valgrind, особенно для отладки памяти или sysprof для профилирования вся система:

Для sysprof:

С помощью sysprof вы можете профилировать все приложения, запущенные на вашем компьютере, включая многопоточные или многопроцессорные приложения ...

А после выбрать интересующую вас ветку подпроцессов.


Для Valgrind:
Имея еще немного тренажерного зала, кажется, можно сделать видимыми для Valgrind некоторые программы, которые мы обычно устанавливаем из двоичного кода (например, OpenOffice ).

Можно прочитать из FAQ valgrind , Valgrindкоторый профилирует дочерние процессы, если явно запрошено.

... Даже если по умолчанию профили отслеживают только процесс верхнего уровня, и поэтому, если ваша программа запускается сценарием оболочки, сценарием Perl или чем-то подобным, Valgrind будет отслеживать оболочку или интерпретатор Perl или эквивалент. ..

Он сделает это с включенной опцией

 --trace-children=yes 

Дополнительные ссылки:

Hastur
источник
1
Не тот, кто проголосовал против, но большинство этих советов, хотя и классные, здесь не актуальны. Здесь более приветствуется задавать соответствующий вопрос и отвечать на него самому - Google «Ответы на самовыражение stackoverflow» для соответствующего этикета.
Blaisorblade
0

Это сообщение от Alan Hargreaves описывает метод профилирования Bourne сценария оболочки поставщика DTrace. Насколько мне известно, это работает с Solaris и OpenSolaris (см. / Bin / sh DTrace Provider ).

Итак, учитывая следующий сценарий dtrace ( sh_flowtime.dв GH на основе оригинала ):

#!/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));
}

вы можете отслеживать выполнение функции, включая дельта-времена.

Пример вывода:

# ./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

Затем, используя sort -nrk7команду, вы можете отсортировать вывод, чтобы показать наиболее ресурсоемкие вызовы.

Мне не известны какие-либо зонды поставщиков, доступные для других оболочек, поэтому проведите небольшое исследование (поиск GitHub?), Или, если вы хотите потратить некоторое время, вы можете написать такое на основе существующего примера sh : (см .: Как активировать sh Провайдер DTrace? ).

kenorb
источник