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. Hauri 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에는 시간을 마이크로 초 단위로 제공하는 변수가 포함되어 있습니다. 이를 사용하면 모든 라인에 대해 외부 실행 파일을 생성하지 않아도되며 date, 물론 Bash 5가있는 한 Mac 또는 GNU가없는 다른 곳에서 작동합니다 . 설정 변경 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

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 월 추가 script방법

이것을 읽고 프로파일 링 이 중요한 단계 이기 때문에 전체 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>&-

이 스크립트를 실행하면이 개 파일을 만들 : /tmp/sample-XXXX.log/tmp/sample-XXXX.tim(여기서 XXXX는 스크립트를 실행의 프로세스 ID입니다).

다음을 사용하여 프레젠테이션 할 수 있습니다 paste.

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

또는 diff 시간을 계산할 수도 있습니다.

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 / 리눅스 커널 없이 포크 .

에서 GNU / 리눅스 의 최근 커널, 당신은 찾을 수 있습니다 /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) ---

원래 bash 스크립트 는 이것에서 따라하기가 쉽지 않습니다 ...

사용 script, scriptreplay파일 타이밍

일환으로 BSD의 Utils , script(및 scriptreplay)는 매우 작은 공간으로 떠들썩한 파티를 프로파일 링하는 데 사용될 수있는 매우 오래된 공구이다.

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

    #!/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 초
  • set + 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 -xPS4

    ++ 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 흥미롭고 더 자세하지만 읽기는 어렵습니다.

script, scriptreplay및 가속 계수도 매우 훌륭합니다. 프로세스 실행 대신 콘솔 교환을 기반으로하는 것과 동일한 정밀도는 아니지만 매우 가볍고 효율적입니다 (동일한 목표가 아니라 동일한 용도).

마지막으로, 나는이 더 가독성과 공연, 효율적 생각 set + 1 fork, 특정한 경우에 따라, 나는 언젠가이 답변의 첫 번째,하지만 벌금 사용 strace및 / 또는 script도.



2
타임즈 섹션은 포크 (스크립트 실제로 완전히 지배 많은 종류)에 재채기 아무것도 꽤 유익하고 드라이브 곳입니다. 좋은 답변 (오래 그려지는 경우)에 +1합니다. 아마도 미래에 별도의 답변을 게시 고려해야한다
sehe을

1
감사합니다, @sehe! 당신은 발견 할 것이다 전체 바로 실행 이 bash는 소스 파일 : ELAP-bash는-V3를 (아 파크의 투명한 사용을 허용 같은 일부 기능 STDIN STDERR )
F. 하우리

1
최신 버전의 bash (> = 4.1)에서는 exec {BASH_XTRACEFD}>대신 exec 3>&2 2>로그 파일을 추적 로깅 출력으로 만 채우고 다른 stderr 출력으로는 채울 수 없습니다.
ws_e_c421

1
단일 날짜 처리 방법에 대한 exec는 매우 영리하고 초 미만의 정밀도를 선호합니다. 의 경우 수정없이 프로파일 링 데이터를 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현재까지 분기하지 않고 (낮은 오버 헤드) 두 번째 정밀도로 모든 추적 라인에 타임 스탬프를 찍는 것이 좋습니다.
ws_e_c421

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 트랩은 이제 간단한 명령, ((...)) 명령, [[...]] 조건부 명령 및 for ((...)) 루프 전에 실행하십시오 . " 그것의 것을 각각의 버전 확인한다에서 테스트 하기 전에 .
추후 공지가있을 때까지 일시 중지되었습니다.

@Dennis Williamson : 좋아, 그게 내가 가지고있는 버전이다. 나는 답을 고친다 :)

0

시간, xtrace, bash -x set -xset+x( http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html )은 스크립트를 디버깅하는 전통적인 방법으로 남아 있습니다.

Neverteless 우리의 지평을 확대하기 위해서는 일반적인 리눅스 프로그램을 사용할 수 디버깅 및 프로파일 링에 대한 몇 가지 시스템에 대한 검사를 제공하는 것이 가능 [여기 목록 중 하나] , 예를 들어 그것을 기반으로 유용 하나의 결과한다 Valgrind의 특히 디버그 메모리 또는 sysprof 프로필을 전체 시스템 :

sysprof의 경우 :

sysprof를 사용하면 다중 스레드 또는 다중 처리 애플리케이션을 포함하여 컴퓨터에서 실행중인 모든 애플리케이션을 프로파일 링 할 수 있습니다.

그리고 흥미를 느끼는 하위 프로세스의 분기를 선택합니다.


Valgrind의 경우 :
체육관이 더 많으면 일반적으로 바이너리에서 설치하는 일부 프로그램 (예 : OpenOffice ) 을 Valgrind에 표시 할 수 있습니다 .

그것은에서 읽을 가능 Valgrind의의 자주 묻는 질문Valgrind 프로필과 것이다 자식 프로세스를 명시 적으로 요청하는 경우.

... 기본적으로 프로파일 링이 최상위 프로세스 만 추적하더라도 프로그램이 쉘 스크립트 , Perl 스크립트 또는 이와 유사한 것으로 시작되면 Valgrind는 쉘이나 Perl 인터프리터 또는 이와 동등한 것을 추적합니다. ..

이 옵션이 활성화 된 상태에서 수행됩니다.

 --trace-children=yes 

추가 참조 :

  • Valgrind 매뉴얼.
  • 인터페이스 KCachegrind 및 Callgrind 또는 여기 에 대한 몇 가지 뉴스 , CERN 위키에서보고 된대로 여전히 사용 중입니다.
  • gdb 매뉴얼. 대한 GDB 프로파일 C, 스크립트에 의해 호출 C ++ 프로그램에 도움이 될 수 있습니다.

1
다운 보 터는 아니지만 이러한 팁의 대부분은 멋지지만 여기서는 실제로 관련이 없습니다. 적절한 질문을하고 스스로 대답하는 것은 여기에서 더 환영합니다. 관련 에티켓에 대한 Google "stackoverflow self answer".
Blaisorblade 2017-04-03

0

Alan Hargreaves 의이 게시물 은 DTrace 공급자를 사용하여 Bourne 쉘 스크립트를 프로파일 링하는 방법을 설명합니다. 내가 아는 한 이것은 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 공급자? ).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.