bashシェルスクリプトの起動が遅いのをプロファイルする方法は?


124

私のbashシェルは起動に最大3〜4秒かかりますが、起動すると--norcすぐに実行されます。

私は「プロファイリング」を開始/etc/bash.bashrc~/.bashrc、手動でreturnステートメントを挿入して速度の向上を追求しましたが、これは定量的なプロセスではなく、効率的ではありません。

bashスクリプトのプロファイルを作成し、起動に最も時間がかかるコマンドを確認するにはどうすればよいですか?


3
スクリプトのプロファイルを作成しましたが、ほとんどの時間はbash_completionのセットアップ中に費やされました。
Andrea Spadaccini、2011年

1
それはかなり大きいので、それは驚くことではありません。あなたは、あなたがなどのアップデート、全体で変更内容を維持するためのトラブルに行きたい場合は、必要は決してないだろう知っている部分を除去することにより、そのスピードアップすることができ
追って通知があるまで一時停止します。

2
あなたは比較ができますtime bash -c 'exit'し、time bash -i -c 'exit'としてプレイしてもよい--norc--noprofile
F.ハウリ、2014年

この回答も参照してください(免責事項:自分のものです)。正確にはあなたが求めているものではありませんが、間違いなく関連しています:unix.stackexchange.com/a/555510/384864
Johan Walles

回答:


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前後に移動できます(または、関心のあるいくつかのセクションを選択して囲みます)。

GNU dateのナノ秒ほど細かいわけではありませんが、Bash 5にはマイクロ秒単位で時間を提供する変数が含まれています。これを使用すると、すべての行に対して外部実行可能ファイルを生成する必要がなくなり、Macなど、GNUがインストールされていない場所でも動作しますdate-もちろん、Bash 5があれば可能です。の設定を変更しますPS4

PS4='+ $EPOCHREALTIME\011 '

@pawamoyで指摘されているように、BASH_XTRACEFDBash 4.1以降を使用している場合は、を使用してトレースの出力を別のファイル記述子に送信できます。この回答から:

#!/bin/bash

exec 5> command.txt
BASH_XTRACEFD="5"

echo -n "hello "

set -x
echo -n world
set +x

echo "!"

これは、トレース出力をファイルに行くことになりますcommand.txt残しstdoutstdout正常に出力する(または別々にリダイレクトされます)。


シェルプロンプトが表示されず、コマンドがエコーバックされないのは正常ですか?しかし、トレースを取得したので、分析を開始できます。
Andrea Spadaccini、2011

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出力を設定する必要がないので(非常に多くのエッジケース)、出力(プロファイリングデータ)を分析するときに非常に役立ちます。
pawamoy

107

プロファイリング (4つの答え)

編集:2016年3月の add scriptメソッド

これを読んで、プロファイリングは重要なステップなので、私はこのSO全体の質問についていくつかのテストと調査を行い、回答を投稿しました。

4つ以上の答えがあります:

  • 1つ目は、@ DennisWilliamsonのアイデアに基づいていますが、リソース消費が大幅に少なくなっています。
  • 2つ目は自分のものでした(この前に)。
  • 3番目は@fgmの回答に基づいていますが、より正確です。
  • 最後の使用scriptscriptreplayおよびタイミングファイル

  • 最後に、最後のパフォーマンスの比較です。

フォークの使用set -xdate制限あり

@DennisWilliamsonのアイデアを参考にしてください。ただし、次の構文では、3つのコマンドに対する最初のフォークは1つだけです。

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

これを実行するとdate、1回だけ実行されます。それがどのように機能するかを示すための簡単なデモ/テストがあります:

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.logand /tmp/sample-XXXX.tim(XXXXは実行中のスクリプトのプロセスID)。

あなたはそれらを使用して提示することができます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

または2つの列:

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

最後のコマンドと合計で 2列をレンダリングします。

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

オリジナルのbashスクリプトは、これを理解するのがそれほど簡単ではありません...

scriptscriptreplayおよびタイミングファイルの使用

一部としてBSD Utilsのscript(とscriptreplay)非常に小さなフットプリントでは、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

2つのファイルを生成します。

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

タイミングファイルでは、2番目の値は対応するログファイルの次のバイト数です。これにより、オプションで加速係数を使用してログファイルを再生できます。

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 world2番目のサンプルをダウンロードしました。このスクリプトは、ホストで完了するまでに約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
    ...
  • よる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
  • script(とset +x

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

タイムズ

そして(私のホストで)実行時間を比較します:

  • 直接 0.72秒
  • elap.bash 13.18秒
  • セット+ date @ 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=
  • よる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 興味深い方法ですが、より詳細ですが読みにくいです。

scriptscriptreplayおよび加速係数も非常に優れています。これは、プロセス実行ではなくコンソール交換に基づいているため、同じ精度ではありませんが、非常に軽量で効率的です(同じ目標ではなく、同じ使用方法ではありません)。

最後に、私はより効率的で、読みやすさとパフォーマンスであると思いset + 1 fork、私はいつか使い、特定のケースに応じて、この答えの最初のが、罰金straceおよび/またはscriptあまりにも。



2
タイムズのセクションでは、フォークは、(実際に完全にスクリプトの多くの種類を支配)でくしゃみをすることは何もしていないことを家にかなり有益であるとドライブ。良い回答(長い場合)の場合は+1。おそらく将来的には、個別の回答を投稿することを検討する必要があります
sehe

1
どうもありがとう、@ sehe!あなたは見つけるでしょうフルすぐに実行できるが、bashのソースファイルを:ELAP-のbash-V3(whithを透過的に使用可能とするようないくつかの機能STDIN STDERR
F. HAURI

1
最近のバージョンのbash(> = 4.1)では、exec {BASH_XTRACEFD}>代わりにexec 3>&2 2>ログファイルにトレースログ出力のみを入力し、他のstderr出力は入力しません。
ws_e_c421 2017

1
単一の日付処理メソッドへのexecは非常に賢く、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ます。1秒未満の精度が必要ない場合は、bash -c "exec {BASH_XTRACEFD}>trace.log; set -x; PS4='+\t'; . script.shすべてのトレース行に2番目の精度でタイムスタンプを付け、日付をフォークしない(オーバーヘッドが低い)ようにします。
ws_e_c421 2017

17

多くの場合、システムコールを追跡するのに役立ちます

strace -c -f ./script.sh

マニュアルから:

-cシステムコールごとに時間、呼び出し、エラーをカウントし、プログラムの終了に関する概要を報告します。

-f子プロセスのトレース...

これは正確にあなたが望むものではなく、ライン指向のプロファイラーがあなたに示すものですが、それは通常ホットスポットを見つけるのに役立ちます。


5

DEBUG状態のtrapコマンドを確認することができます。コマンドとともに実行されるコマンドを設定する方法があります。回答の注を参照してください。


@Dennis Williamson:しばらく使用していませんが、システムのヘルプでは、「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トラップが追加されました実行前に簡単なコマンド、((...))のコマンド、[[...]]条件付き命令、及び((...))ループの。」各バージョンでのテストにより、それが以前であることが確認されます。
追って通知があるまで一時停止。

@Dennis Williamson:わかりました。それが私のバージョンです。私は答えを修正します:)

0

時間、xtrace、bash -x、set -xおよびset+xhttp://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html)は、スクリプトをデバッグするための方法のままです。

Nevertelessが私たちの地平線を拡大し、それは通常のLinuxプログラムのために利用できるのデバッグとプロファイリングのために、いくつかのシステムにチェックを与えることが可能です[ここにリストの1] 、例えば、それはに基づいて有用な1もたらすべきであるvalgrindの特にデバッグメモリまたはにSYSPROFをプロファイルにしますシステム全体:

sysprofの場合:

sysprofを使用すると、マルチスレッドまたはマルチプロセスのアプリケーションを含む、マシンで実行されているすべてのアプリケーションをプロファイルできます...

そして、あなたが興味深いと思うサブプロセスのブランチを選択した後。


Valgrindの場合:
ジムが増えると、通常バイナリからインストールする一部のプログラム(OpenOfficeなど)をValgrindに表示できるようになるようです。

それはから読み取ることが可能ですvalgrindののよくある質問Valgrindプロフィールます子プロセスを明示的に要求された場合。

...デフォルトでは、プロファイルは最上位のプロセスのみをトレースするため、プログラムがシェルスクリプト、Perlスクリプト、または類似のものによって開始された場合でも、Valgrindはシェル、Perlインタープリター、または同等のものをトレースします。 ..

このオプションを有効にしてそれを行います

 --trace-children=yes 

その他の参考資料:


1
反対投票者ではありませんが、これらのヒントのほとんどは、クールですが、ここではあまり関係ありません。ここでは、適切な質問をして自己回答することをお勧めします。関連するエチケットについては、Googleの「stackoverflow自己回答」をご覧ください。
Blaisorblade 2017

0

Alan Hargreavesによるこの投稿では、DTraceプロバイダーを使用してBourneシェルスクリプトをプロファイリングする方法について説明しています。私の知る限り、これはSolarisおよびOpenSolarisで機能します(/ bin / sh DTrace Providerを参照)。

したがって、次のdtraceスクリプトが与えられます(元のsh_flowtime.d GHに基づいて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プロバイダー?)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.