2010-12-02 16 views
25

Estoy intentando acelerar una colección de scripts que invocan subcapas y hacen todo tipo de cosas. Me preguntaba si hay herramientas disponibles para cronometrar la ejecución de un script de shell y sus shells anidados e informar qué partes del script son las más caras.Herramientas de creación de perfiles de rendimiento para scripts de shell

Por ejemplo, si tuviera un script como el siguiente.

#!/bin/bash 

echo "hello" 
echo $(date) 
echo "goodbye" 

Me gustaría saber cuánto tiempo tomó cada una de las tres líneas. time solo me dará tiempo total para el guión. bash -x es interesante, pero no incluye marcas de tiempo u otra información de tiempo.

+2

el comando se llama 'tiempo' – ajreal

+1

puede poner tiempo para cada uno de los comandos, como' tiempo de eco "hola" ' – ajreal

+1

@ajreal, eso es una respuesta válida, ¿por qué no lo puesto? –

Respuesta

43

Puede establecer PS4 para mostrar la hora y el número de línea. Hacer esto no requiere la instalación de utilidades y funciona sin redirigir stderr a stdout.

Para este script:

#!/bin/bash -x 
# Note the -x flag above, it is required for this to work 
PS4='$(date "+%s.%N ($LINENO) + ")' 
for i in {0..2} 
do 
    echo $i 
done 
sleep 1 
echo done 

La salida será similar a:

+ PS4='$(date "+%s.%N ($LINENO) + ")' 
1291311776.108610290 (3) + for i in '{0..2}' 
1291311776.120680354 (5) + echo 0 
0 
1291311776.133917546 (3) + for i in '{0..2}' 
1291311776.146386339 (5) + echo 1 
1 
1291311776.158646585 (3) + for i in '{0..2}' 
1291311776.171003138 (5) + echo 2 
2 
1291311776.183450114 (7) + sleep 1 
1291311777.203053652 (8) + echo done 
done 

Esto supone fecha de GNU, pero se puede cambiar la especificación de salida a lo que quiera o lo que coincide con la versión de la fecha que usas

Nota: Si usted tiene un guión existente que desea hacer esto con sin modificarlo, puede hacerlo:

PS4='$(date "+%s.%N ($LINENO) + ")' bash -x scriptname 
+4

Tenga en cuenta que '-x' en la parte superior de' #!/Bin/bash -x' es importante –

-2

No conozco ninguna herramienta de creación de perfiles de shell.

Históricamente uno solo reescribe shell scripts demasiado lentos en Perl, Python, Ruby, C o incluso

Una idea menos drástica sería utilizar una concha más rápido que bash. Dash y ash están disponibles para todos los sistemas de estilo Unix y suelen ser bastante más pequeños y rápidos.

+9

Pero, ¿cómo sabes que es lento o que un caparazón más rápido mejoraría el rendimiento si no puedes perfilar y, por lo tanto, no sabes dónde está el cuello de botella? – Sorpigal

1

Parece que quieres sincronizar cada eco. Si echo es todo lo que está haciendo, esto es fácil

alias echo='time echo' 

Si ejecuta otro comando, esto obviamente no será suficiente.

10

Puede canalizar la salida de ejecución bajo -x a través de algo que marca el tiempo cada línea cuando se recibe. Por ejemplo, tai64n de djb's daemontools. En un ejemplo básico,

sh -x slow.sh 2>&1 | tai64n | tai64nlocal 

Esto confunde stdout y stderr pero nos da todo lo que una marca de tiempo. Tendría que analizar la salida para encontrar líneas costosas y correlacionarlas con su origen.

También podría ser útil utilizar strace útil. Por ejemplo,

strace -f -ttt -T -o /tmp/analysis.txt slow.sh 

Esto producirá un informe muy detallado, con una gran cantidad de información de tiempo en /tmp/analysis.txt, pero a un nivel de llamadas por cada sistema, que podría ser demasiado detallada.

+0

¡También es una buena respuesta! Prefiero el primero porque no tengo que encontrar ningún paquete adicional, pero es bueno que no tenga que modificar el script que está creando. ¡Gracias! – bradtgmurray

+0

gracias, realmente útil! –

+0

Utilicé esto para encontrar la línea más lenta en mi script: https://github.com/brycepg/notebooks/blob/master/Profiling%20Bashrc.ipynb –

1

Mi enfoque preferido es abajo. La razón es que también es compatible con OSX (que no tiene fecha de alta precisión) & se ejecuta incluso si no tiene bc instalado.

#!/bin/bash 

_profiler_check_precision() { 
    if [ -z "$PROFILE_HIGH_PRECISION" ]; then 
     #debug "Precision of timer is unknown" 
     if which bc > /dev/null 2>&1 && date '+%s.%N' | grep -vq '\.N$'; then 
      export PROFILE_HIGH_PRECISION=y 
     else 
      export PROFILE_HIGH_PRECISION=n 
     fi 
    fi 
} 


_profiler_ts() { 
    _profiler_check_precision 

    if [ "y" = "$PROFILE_HIGH_PRECISION" ]; then 
     date '+%s.%N' 
    else 
     date '+%s' 
    fi 
} 

profile_mark() { 
    _PROF_START="$(_profiler_ts)" 
} 

profile_elapsed() { 
    _profiler_check_precision 

    local NOW="$(_profile_ts)" 
    local ELAPSED= 

    if [ "y" = "$PROFILE_HIGH_PRECISION" ]; then 
     ELAPSED="$(echo "scale=10; $NOW - $_PROF_START" | bc | sed 's/\(\.[0-9]\{0,3\}\)[0-9]*$/\1/')" 
    else 
     ELAPSED=$((NOW - _PROF_START)) 
    fi 

    echo "$ELAPSED" 
} 

do_something() { 
    local _PROF_START 
    profile_mark() 
    sleep 10 
    echo "Took $(profile_elapsed()) seconds" 
} 
+0

El tiempo bash incorporado Bash hará esto por usted. –

Cuestiones relacionadas