performance - example - Herramientas de perfilado de rendimiento para shell scripts
shell script example (5)
Estoy intentando acelerar una colección de scripts que invocan subshells y hacen todo tipo de cosas. Me pregunto si hay herramientas disponibles para cronometrar la ejecución de un script de shell y sus shells anidados e informar sobre qué partes del script son las más caras.
Por ejemplo, si tuviera un script como el siguiente.
#!/bin/bash
echo "hello"
echo $(date)
echo "goodbye"
Me gustaría saber cuánto duró 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.
Mi enfoque preferido es a continuación. La razón es que también es compatible con OSX (que no tiene fecha de alta precisión) y 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"
}
No tengo conocimiento de ninguna herramienta de creación de perfiles de shell.
Históricamente, uno simplemente vuelve a escribir scripts de shell demasiado lentos en Perl, Python, Ruby o incluso C.
Una idea menos drástica sería usar un shell más rápido que bash. Dash y ash están disponibles para todos los sistemas de estilo Unix y, por lo general, son bastante más pequeños y más rápidos.
Parece que quieres sincronizar cada eco. Si eco es todo lo que estás haciendo, es fácil.
alias echo=''time echo''
Si está ejecutando otro comando, esto obviamente no será suficiente.
Podría canalizar la salida de ejecutar debajo de -x
a algo que marca la hora de cada línea cuando se recibe. Por ejemplo, tai64n
de daemontools de djb. En un ejemplo básico,
sh -x slow.sh 2>&1 | tai64n | tai64nlocal
Esto combina stdout y stderr pero le da a todo un sello de tiempo. Tendría que analizar la salida para encontrar líneas costosas y correlacionar eso con su fuente.
También podría encontrar útil usar strace
. Por ejemplo,
strace -f -ttt -T -o /tmp/analysis.txt slow.sh
Esto producirá un informe muy detallado, con mucha información de tiempo en /tmp/analysis.txt
, pero a nivel de llamadas por sistema, que puede ser demasiado detallado.
Puedes configurar PS4
para que muestre la hora y el número de línea. Hacer esto no requiere instalar ninguna utilidad y funciona sin redireccionar 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 se ve como:
+ 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 la fecha de GNU, pero puede cambiar la especificación de salida a cualquier cosa que desee o que coincida con la versión de la fecha que utiliza.
Nota: Si tiene un script existente con el que desea hacer esto sin modificarlo, puede hacerlo:
PS4=''+ $(date "+%s.%N ($LINENO) ")'' bash -x scriptname
En el próximo Bash 5, podrá guardar la date
bifurcación (pero obtendrá microsegundos en lugar de nanosegundos):
PS4=''+ $EPOCHREALTIME ($LINENO) ''