วิธีสร้างโปรไฟล์ bash shell script การเริ่มต้นช้า?


124

bash shell ของฉันใช้เวลาถึง 3-4 วินาทีในการเริ่มต้นในขณะที่ถ้าฉันเริ่มด้วย--norcมันจะทำงานทันที

ฉันเริ่ม "การทำโปรไฟล์" /etc/bash.bashrcและ~/.bashrcด้วยการใส่returnข้อความและค้นหาการปรับปรุงความเร็วด้วยตนเองแต่มันไม่ใช่กระบวนการเชิงปริมาณและไม่มีประสิทธิภาพ

ฉันจะสร้างโปรไฟล์สคริปต์ทุบตีของฉันได้อย่างไรและดูว่าคำสั่งใดใช้เวลาในการเริ่มต้นมากที่สุด


3
ฉันทำโปรไฟล์สคริปต์และเวลาส่วนใหญ่ใช้ไปกับการตั้งค่า bash_completion
Andrea Spadaccini

1
ไม่น่าแปลกใจเพราะมันใหญ่มาก คุณสามารถเร่งความเร็วได้โดยการลบชิ้นส่วนที่คุณรู้ว่าคุณไม่จำเป็นต้องใช้หากคุณต้องการแก้ไขปัญหาในการรักษาการเปลี่ยนแปลงในการอัปเดต ฯลฯ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

2
คุณสามารถเปรียบเทียบ: time bash -c 'exit'และtime bash -i -c 'exit'และอาจจะเล่นด้วยและ--norc --noprofile
F. Hauri

ดูคำตอบนี้ด้วย (ข้อจำกัดความรับผิดชอบ: เป็นของฉัน) ไม่ใช่สิ่งที่คุณถาม แต่เกี่ยวข้องแน่นอน: 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ที่แสดงการประทับเวลาวินาทีนาโนวินาทีของแต่ละคำสั่งที่ดำเนินการ ความแตกต่างจากครั้งหนึ่งไปอีกครั้งคือระยะเวลาที่ขั้นตอนการแทรกแซงเกิดขึ้น

เมื่อคุณ จำกัด สิ่งต่างๆให้แคบลงคุณสามารถย้ายใน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เอาต์พุตตามปกติ (หรือถูกเปลี่ยนทิศทางแยกต่างหาก)


เป็นเรื่องปกติหรือไม่ที่เชลล์พร้อมต์จะมองไม่เห็นและคำสั่งของฉันจะไม่สะท้อนกลับ? อย่างไรก็ตามฉันได้รับร่องรอยจึงสามารถเริ่มการวิเคราะห์ได้ .. ขอบคุณมาก!
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 และ set -x อีกต่อไป (กรณี edge จำนวนมาก)
pawamoy

107

โปรไฟล์ (4 คำตอบ)

แก้ไข: มีนาคม 2559เพิ่ม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>&-

เมื่อเรียกใช้สคริปต์นี้คุณจะสร้างไฟล์ 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 , 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 -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และคำสั่งเริ่มต้น fork ถึง long 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(and 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 วินาที
  • set + 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และคำสั่งเริ่มต้น fork ถึง long 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 บริสุทธิ์ของฉันหมายถึงการดำเนินการบางอย่างในแต่ละคำสั่ง

วิธีการอุทิศกระบวนการที่เป็นอิสระสำหรับการบันทึกและการจัดเก็บนั้นมีประสิทธิภาพมากกว่าอย่างชัดเจน

strace เป็นวิธีที่น่าสนใจมีรายละเอียดมากกว่า แต่อ่านยาก

scriptด้วยscriptreplayและปัจจัยเร่งความเร็วก็ดีมากเช่นกันความแม่นยำไม่เท่ากันเนื่องจากขึ้นอยู่กับการแลกเปลี่ยนคอนโซลแทนการดำเนินการตามกระบวนการ แต่เบาและมีประสิทธิภาพมาก (ไม่ใช่เป้าหมายเดียวกันไม่ใช่การใช้งานแบบเดียวกัน)

สุดท้ายฉันคิดว่ายิ่งมีประสิทธิภาพในการอ่านและประสิทธิภาพมากขึ้นset + 1 forkคำตอบนี้ก็คือคำตอบแรก แต่โดยดีฉันใช้บางครั้งstraceและ / หรือscriptมากเกินไป



2
ไทม์ส่วนสวยข้อมูลและไดรฟ์บ้านที่ส้อมมีอะไรจะจามที่ (ที่จริงสมบูรณ์มีอำนาจเหนือหลายชนิดสคริปต์) +1 สำหรับคำตอบที่ดี (หากลากยาว) บางทีในอนาคตคุณควรพิจารณาโพสต์คำตอบแยกต่างหาก
ดู

1
ขอบคุณมาก @sehe! คุณจะพบไฟล์ต้นฉบับ bash ที่พร้อมใช้งานเต็มรูปแบบที่นั่น: elap-bash-v3 (ซึ่งมีคุณสมบัติบางอย่างเช่นอนุญาตให้ใช้STDIN และ STDERRอย่างโปร่งใส)
F. Hauri

1
ในเวอร์ชันล่าสุดของ bash (> = 4.1) คุณสามารถทำexec {BASH_XTRACEFD}>แทนexec 3>&2 2>ซึ่งจะเติมข้อมูลในไฟล์บันทึกด้วยเอาต์พุตการบันทึกการติดตามเท่านั้นไม่ใช่เอาต์พุต stderr อื่น ๆ
ws_e_c421

1
วิธีการประมวลผลของ exec to a single date นั้นฉลาดมากและฉันชอบความแม่นยำย่อยของวินาที สำหรับ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

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 จะระบุว่า "after" นั่นเป็นการพิมพ์ผิด สำหรับ Bash 2.05b จะทำงานก่อน เอกสารอ้างอิง : "เอกสารนี้ให้รายละเอียดการเปลี่ยนแปลงระหว่างเวอร์ชันนี้ bash-2.05b-alpha1 และเวอร์ชันก่อนหน้า bash-2.05a-release ... 3. คุณลักษณะใหม่ใน Bash ... w. กับดัก DEBUG อยู่ในขณะนี้ รันก่อนคำสั่งธรรมดาคำสั่ง ((... )) คำสั่ง [[... ]] คำสั่งเงื่อนไขและสำหรับ ((... )) ลูป " การทดสอบในแต่ละเวอร์ชันเป็นการยืนยันว่าก่อนหน้านี้
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

@ เดนนิสวิลเลียมสัน: โอเคนั่นคือรุ่นที่ฉันมี ฉันแก้ไขคำตอบ :)

0

เวลา, 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 )

เป็นไปได้ที่จะอ่านคำถามที่พบบ่อยของ valgrindซึ่ง Valgrindจะให้รายละเอียดของกระบวนการย่อยหากมีการร้องขออย่างชัดเจน

... แม้ว่าโดยค่าเริ่มต้นโปรไฟล์จะติดตามกระบวนการระดับบนสุดเท่านั้นดังนั้นหากโปรแกรมของคุณเริ่มต้นด้วยเชลล์สคริปต์สคริปต์ Perl หรือสิ่งที่คล้ายกัน Valgrind จะติดตามเชลล์หรือตัวแปล Perl หรือเทียบเท่า ..

มันจะทำเมื่อเปิดใช้งานตัวเลือกนี้

 --trace-children=yes 

ข้อมูลอ้างอิงเพิ่มเติม:


1
ไม่ใช่ผู้โหวตลดคะแนน แต่เคล็ดลับเหล่านี้ส่วนใหญ่ในขณะที่เจ๋ง ๆ ไม่เกี่ยวข้องกับที่นี่ การถามคำถามที่เหมาะสมและการตอบด้วยตนเองเป็นเรื่องที่น่ายินดีมากกว่าที่นี่ Google "stackoverflow self answer" สำหรับมารยาทที่เกี่ยวข้อง
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? )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.