SlideShare uma empresa Scribd logo
1 de 54
Baixar para ler offline
3 bash one-liners
ccaputo@sp.r7.com
carlo.caputo@gmail.com
onliners: sao codigos escritos todos em uma linha
bash: eh um shell muito popular, provavelmente quando vc abre o seu
terminal vc estah usando o bash
entao, vou contar 3 histórias.
1
To lá eu fazendo um dump de banco...
$ mongoexport -d sambatech -c portal | gzip > dump.json.gz
connected to: 127.0.0.1
Demorou mais de 10 segundos...
$ mongoexport -d sambatech -c portal | gzip > dump.json.gz
connected to: 127.0.0.1
^C
Tá, preciso mostrar o progresso...
$ mongoexport -d sambatech -c portal | gzip | pv > dump.json.
gz
connected to: 127.0.0.1
7.83MB 0:00:05 [1.75MB/s] [ <=> ]
vermelho: pv simples mostrando a taxa de transferencia
Hunnn... Ainda não sei quanto falta.
$ mongoexport -d sambatech -c portal | pv -c -N raw | gzip | pv
-c -N gz > dump.json.gz
connected to: 127.0.0.1
raw: 54.5MB 0:00:07 [8.28MB/s] [ <=> ]
gz: 11.1MB 0:00:07 [1.72MB/s] [ <=> ]
vermelho: pv mostrando o ratio de compressao on-the-fly
Ah, eu sei quantas linhas são...
$ ( export H=localhost D=sambatech C=portal; export F="
dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)";
mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c
-N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" |
mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 |
cut -c 2- )" | gzip | pv -c -N gz | tee $F.gz | md5sum > $F.md5 )
raw: 78.4MB 0:00:10 [7.97MB/s] [ <=> ]
gz: 16.1MB 0:00:10 [1.69MB/s] [ <=> ]
rows: 46.7k 0:00:10 [4.59k/s] [> ] 63% ETA 0:00:05
vermelho: novo pv contando porcentagem de linhas, calculadas por um
count() do mongo
azul: variaveis para nao repetir as informacoes
Mas tá muito idle ainda....
top - 23:19:46 up 53 days, 17 min, 24 users, load average: 0.15, 0.12, 0.09
Tasks: 334 total, 2 running, 331 sleeping, 0 stopped, 1 zombie
Cpu0 : 33.3%us, 8.8%sy, 0.0%ni, 57.9%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu1 : 2.3%us, 1.0%sy, 0.3%ni, 94.2%id, 2.3%wa, 0.0%hi, 0.0%si,
Cpu2 : 85.2%us, 4.6%sy, 0.0%ni, 10.2%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu3 : 2.6%us, 1.0%sy, 0.3%ni, 94.1%id, 2.0%wa, 0.0%hi, 0.0%si,
Mem: 4107620k total, 3687764k used, 419856k free, 278800k buffers
Swap: 4192960k total, 363512k used, 3829448k free, 658816k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24448 zed 20 0 17688 4300 2880 R 98 0.1 0:10.45 mongoexport
24451 zed 20 0 2052 504 344 S 31 0.0 0:03.20 gzip
24449 zed 20 0 4056 644 568 S 10 0.0 0:01.04 pv
24450 zed 20 0 4056 656 576 S 8 0.0 0:00.81 pv
3587 mongodb 20 0 804m 120m 119m S 1 3.0 0:08.59 mongod
24456 zed 20 0 3892 508 440 S 1 0.0 0:00.07 md5sum
24454 zed 20 0 3884 548 480 S 1 0.0 0:00.05 tee
24452 zed 20 0 4056 660 568 S 0 0.0 0:00.03 pv
vermelho: cpu idle, mongoexport eh gargalo
Vamos ver...
$ time --help
--help: command not found
$ type time
time is a shell keyword
$ help time
time: time [-p] pipeline
Report time consumed by pipeline's execution.
$ /usr/bin/time --help
Usage: /usr/bin/time [-apvV] [-f format] [-o file] [--append] [--verbose]
[--portability] [--format=format] [--output=file] [--version]
[--quiet] [--help] command [arg...]
tem dois times no sistema, cuidado com aliases e built-ins
Ei, espera aí, que tal usar xz?!
$ mongoexport -d sambatech -c portal | head -c 10000 | tee >( (
/usr/bin/time -f 'gz: %E real, %U user, %S sys' gzip -9 | wc -c |
sed 's/^/gz: /' ) 1>&2) | /usr/bin/time -f 'xz: %E real, %U user, %
S sys' xz -9 | wc -c | sed 's/^/xz: /'
connected to: 127.0.0.1
gz: 0:00.24 real, 0.00 user, 0.00 sys
gz: 2078
xz: 0:00.31 real, 0.02 user, 0.04 sys
xz: 2012
vemelho: limitei os bytes de entrada em um tamanho arbitrário, mas ele
foi insuficiente para chegar a alguma conclusão.
Melhor limitar por tempo do que por
bytes. Mas como parar?...
$ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed
's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U
user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) |
/usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c |
sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT %%
[1] 12289
connected to: 127.0.0.1
$ raw: 14623779
gz: 0:08.99 real, 0.75 user, 0.00 sys
gz: 2706591
Command terminated by signal 2
xz: 0:09.02 real, 8.78 user, 0.17 sys
vermelho: os 3 wc(raw, gz, xz) e os seus respectivos resultados (note
que falta um resultado)
azul: a causa; tentei matar o pipe inteiro, mas o resultado ficou cortado.
Ahh, aí está o número perdido!
$ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed
's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U
user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) |
/usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c |
sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.
*mongoexport(([0-9]*)).*/1/p')
[1] 12724
connected to: 127.0.0.1
$ raw: 14497402
gz: 0:09.10 real, 0.73 user, 0.01 sys
gz: 2695783
xz: 0:09.18 real, 8.73 user, 0.16 sys
xz: 2021928
vermelho: o wc e sue resultado ,(que estava perdido)
azul: o que fiz pra resolver, matei o processo inicial do pipe e não o
pipe todo
roxo: pid do ultimo processo que foi pra background
Mas deixa eu formatar isso melhor...
$ { ( { mongoexport -d sambatech -c portal 2>/dev/null | tee >
(wc -c | sed 's/^/s_raw=/' 1>&2) | tee >( ( /usr/bin/time -f
't_gz=%U+%S' gzip -9 | wc -c | sed 's/^/s_gz=/') 1>&2) |
/usr/bin/time -f 't_xz=%U+%S' xz -9 | wc -c | sed 's/^/s_xz=/' ;
echo 'scale=2; print "nxz is ", t_xz/t_gz; r_sz=s_gz/s_xz;
r_gz=s_gz/s_raw; r_xz=s_xz/s_raw; scale=0; print " times
slower and ", (r_sz-1)/.01,"% smallerncompression ratios:
gz=",(1-r_gz)/.01,"%, xz=",(1-r_xz)/.01,"%n"' ; } 2>&1 | bc ) & } ;
sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.*mongoexport(([0-9]
*)).*/1/p')
xz is 12.02 times slower and 33% smaller
compression ratios: gz=82%, xz=87%
vermelho: contas em bc pra calcular as taxas
azul: variaveis usadas no bc
fundo verde: bloco de shell script cujo output eh codigo para o bc
Ai... :/ será que tem algum outro nível
de compressão que valha a pena?
$ { ( { { echo -n 'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for
z in 0 1 ; do for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >
(/usr/bin/time -f 't[$z$l]=%U+%S' $(test $z == 1 && echo gzip || echo xz)
-$l | wc -c | sed 's/^/s[$z$l]=/' 1>&2)" ; done ; done ; echo -n ' | cat >
/dev/null' ; } | bash 2>&1 ; echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0;
x<=1;x++) { for(l=x;l<=9;l++) { i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print
1/s^p/t," (1/s^",p,"/t) "; if(x==1) print "gzip" else print "xz"; print" -",l," is ",
t," time and ",s," size of gzip -6n" } } }' ; } | bash <( echo -n 'bc | tee ' ; for
p in $(seq 1 6 13) ; do echo -n " >(grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f
2- 1>&2)" ; done ; echo -n ' > /dev/null' ) 2>&1 ) & } ; sleep 9 ; kill -INT
$(pstree -p $! | sed -n 's/.*mongoexport(([0-9]*)).*/1/p'); fg
(1/s^1/t) gzip -2 is .62 time and 1.12 size of gzip -6
(1/s^7/t) gzip -6 is 1.00 time and 1.00 size of gzip -6
(1/s^13/t) xz -6 is 14.00 time and .76 size of gzip -6
enfase na metaprogramacao
vermelho: interpretadores, o bash do meio teve que receber o script
como arquivo e nao como stdin pq o bc dentro dele precisava da stdin
pra ele.
azul: geracao dinamica dos scripts
fundo vermelho: primeiro bash, que vai gerar um bash script que,
finalmente, vai escrever as variaveis para o bc
fundo verde: codigo estatico para o bc
fundo azul: script shell que vai receber com stdin um codigo bc e vai
parsear a saida do bc
verde: o cat eh necessario pq os redirects se comportam diferentes do
pipe, sem ele as variaveis viriam depois do codigo bc que usa elas
branco com fundo vermelho: tem um race-condition na geracao
paralela das variaveis do bc, tive que rodar varias vezes pra conseguir
o resultado completo
pstree dos comandos no slide anterior
bash─┬─bash───bash─┬─cat
│ ├─mongoexport
│ └─tee─┬─10*[bash─┬─sed]
│ │ ├─time───xz]
│ │ └─wc]
│ └─9*[bash─┬─sed]
│ ├─time───gzip]
│ └─wc]
└─bash─┬─bc
└─tee───3*[bash─┬─cut]
├─grep]
├─sort]
└─tail]
vermelho: os pontos chaves de processamento
azul: o unico input que flui por toda essa arvore
verde: tees que forkam os processos e pipes
código bash, resultado do primeiro trecho
mongoexport -d sambatech -c portal 2>/dev/null | tee
>(/usr/bin/time -f 't[00]=%U+%S' xz -0 | wc -c | sed 's/^/s[00]=/' 1>&2)
>(/usr/bin/time -f 't[01]=%U+%S' xz -1 | wc -c | sed 's/^/s[01]=/' 1>&2)
>(/usr/bin/time -f 't[02]=%U+%S' xz -2 | wc -c | sed 's/^/s[02]=/' 1>&2)
>(/usr/bin/time -f 't[03]=%U+%S' xz -3 | wc -c | sed 's/^/s[03]=/' 1>&2)
>(/usr/bin/time -f 't[04]=%U+%S' xz -4 | wc -c | sed 's/^/s[04]=/' 1>&2)
>(/usr/bin/time -f 't[05]=%U+%S' xz -5 | wc -c | sed 's/^/s[05]=/' 1>&2)
>(/usr/bin/time -f 't[06]=%U+%S' xz -6 | wc -c | sed 's/^/s[06]=/' 1>&2)
>(/usr/bin/time -f 't[07]=%U+%S' xz -7 | wc -c | sed 's/^/s[07]=/' 1>&2)
>(/usr/bin/time -f 't[08]=%U+%S' xz -8 | wc -c | sed 's/^/s[08]=/' 1>&2)
>(/usr/bin/time -f 't[09]=%U+%S' xz -9 | wc -c | sed 's/^/s[09]=/' 1>&2)
>(/usr/bin/time -f 't[11]=%U+%S' gzip -1 | wc -c | sed 's/^/s[11]=/' 1>&2)
>(/usr/bin/time -f 't[12]=%U+%S' gzip -2 | wc -c | sed 's/^/s[12]=/' 1>&2)
>(/usr/bin/time -f 't[13]=%U+%S' gzip -3 | wc -c | sed 's/^/s[13]=/' 1>&2)
>(/usr/bin/time -f 't[14]=%U+%S' gzip -4 | wc -c | sed 's/^/s[14]=/' 1>&2)
>(/usr/bin/time -f 't[15]=%U+%S' gzip -5 | wc -c | sed 's/^/s[15]=/' 1>&2)
>(/usr/bin/time -f 't[16]=%U+%S' gzip -6 | wc -c | sed 's/^/s[16]=/' 1>&2)
>(/usr/bin/time -f 't[17]=%U+%S' gzip -7 | wc -c | sed 's/^/s[17]=/' 1>&2)
>(/usr/bin/time -f 't[18]=%U+%S' gzip -8 | wc -c | sed 's/^/s[18]=/' 1>&2)
>(/usr/bin/time -f 't[19]=%U+%S' gzip -9 | wc -c | sed 's/^/s[19]=/' 1>&2) | cat > /dev/null
fundo vermelho: codigo de bash que vai gerar codigo de bc
t[09]=2.17+0.09
s[09]=500800
t[08]=2.21+0.07
s[08]=500800
t[07]=2.12+0.06
s[07]=500800
t[06]=2.12+0.04
s[06]=500800
t[05]=1.60+0.03
s[05]=518820
t[04]=1.56+0.01
s[04]=520052
t[03]=1.44+0.02
s[03]=524920
t[02]=0.50+0.02
s[02]=603160
t[01]=0.35+0.01
s[01]=634096
t[00]=0.32+0.02
s[00]=671448
scale=2;
for(p=1;p<=13;p+=6) {
for(x=0;x<=1;x++) {
for(l=x;l<=9;l++) {
i=x*10+l;
s=s[16]/s[i];
t=t[i]/t[16];
print s^p/t," (s^",p,"/t) ";
if(x==1) print "gzip"
else print "xz";
print" -",l," is ",t," slower
and ",s," smaller than gzip -6
n"
} } }
t[19]=0.20+0.00
s[19]=639917
t[18]=0.20+0.00
s[18]=640335
t[17]=0.16+0.00
s[17]=649143
t[16]=0.14+0.00
s[16]=652085
t[15]=0.13+0.01
s[15]=663714
t[14]=0.12+0.01
s[14]=690369
t[13]=0.09+0.01
t[12]=0.08+0.00
s[13]=722305
s[12]=734534
t[11]=0.09+0.00
s[11]=752727
código bc, resultado do primeiro e segundo trechos
fundo vermelho: codigo de bc gerado pelo primeiro trecho
fundo verde: codigo de bc que vai processar as variaveis mostradas no
fundo vermelho
código bash, resultado do terceiro trecho
bc | tee
>(grep 's^1/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
>(grep 's^7/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
>(grep 's^13/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)
> /dev/null
fundo azul: ultimo script bash que recebe o stdin com o bc e faz o pos-
processamento do seu output
Como tirar o race-condition?
$ while true ; do { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { {
echo -n "$d: " ; ls /dev/fd/ ; } 1>&2 ; " ; done ; echo -n 'seq 2000
2>/dev/null | tee' ; for z in 0 1 ; do for l in $(seq 0 9); do test $z
= 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't[$z$l]=%U+%S'
2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc -c |
sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n '
>/dev/null' ; for d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 |
grep '' " ; done ; } | tee /dev/fd/2 | bash ; echo 123 ; } | sed 's/^/b:
/' | grep =.*= ; } 2>/dev/null ; done
vermelho: grep "" line buffered dos varios file descriptors; o ultimo grep
testa por linhas encavaladas
azul: debug supimido pelo 2>/dev/null no final
mas isso eh uma outra história...
Mais consistência no teste dos
parâmetros de compressão
$ { { { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { " ; done ; echo -n
'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for z in 0 1 ; do
for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't
[$z$l]=%U+%S' 2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc
-c | sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n ' > /dev/null' ; for
d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 | sed '' " ; done ; } | bash ;
echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0;x<=1;x++) { for(l=x;l<=9;l++)
{ i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print 1/s^p/t," (1/s^",p,"/t) "; if(x==1) print
"gzip" else print "xz"; print" -",l," is ",t," time and ",s," size than gzip -6n" }
} }' ; } | bash <( echo -n 'bc | { tee ' ; for p in $(seq 1 6 13) ; do echo -n " >
(grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)" ; done ; echo -n '
>/dev/null ; } 2>&1 | grep "" ' ) ; } & } ; A=$! ; { { sleep 9 ; kill -INT $(pstree
-laAp $A | sed -n 's/^[^,]*<mongoexport,([0-9][0-9]*)>.*/1/p') ; } & } ;
B=$! ; fg %- || { echo "finishing..." ; kill -INT $( { pstree -laAp $A ; pstree -
laAp $B ; } | sed -n 's/^[^,]*,([0-9][0-9]*)>.*/1/p') ; } ; } #xz-vs-gz
vermelho: separacao dos streams em multiplos file descriptors
azul: limpando rodadas incompletas
cinza: tag
Bom, mas voltando ao dump do banco,
com xv fica assim:
$ ( export H=localhost D=sambatech C=portal; export F="
dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)";
mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c
-N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" |
mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 |
cut -c 2- )" | xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 )
raw: 54.7MB 0:00:38 [ 1.4MB/s] [ <=> ]
xz: 7.85MB 0:00:38 [ 176kB/s] [ <=> ]
rows: 32.7k 0:00:38 [ 818/s ] [=> ] 44% ETA 0:00:47
vermelho: o xz na linha do dump de bando
Como está idle e o xv é o gargalo, o
negócio é paralelizar de algum jeito
top - 12:15:16 up 53 days, 13:13, 27 users, load average: 0.69, 0.24, 0.29
Tasks: 338 total, 3 running, 334 sleeping, 0 stopped, 1 zombie
Cpu0 : 99.7%us, 0.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu1 : 2.3%us, 1.3%sy, 0.0%ni, 96.4%id, 0.0%wa, 0.0%hi, 0.0%si,
Cpu2 : 18.3%us, 1.7%sy, 0.0%ni, 79.4%id, 0.7%wa, 0.0%hi, 0.0%si,
Cpu3 : 2.0%us, 1.3%sy, 0.0%ni, 96.7%id, 0.0%wa, 0.0%hi, 0.0%si,
Mem: 4107620k total, 1334364k used, 2773256k free, 24868k buffers
Swap: 4192960k total, 2373236k used, 1819724k free, 259280k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
20507 zed 20 0 99420 93m 736 R 100 2.3 1:06.74 xz
20504 zed 20 0 17688 4304 2880 S 17 0.1 0:11.88 mongoexport
20506 zed 20 0 4056 780 576 S 2 0.0 0:01.45 pv
20505 zed 20 0 4056 732 576 S 0 0.0 0:00.25 pv
vermelho: xv como gargalo, no lugar do mongoexport; processos single
thread
pixz ou parallel?
$ git clone https://github.com/vasi/pixz.git
$ cd pixz/
$ make
pixz.h:1: fatal error: lzma.h: No such file or directory
$ sudo apt-get install lzma-dev
Setting up lzma-dev (4.43-14ubuntu2) ...
$ make
pixz.h:1: fatal error: lzma.h: No such file or directory
$ grep lzma README
* liblzma 4.999.9-beta-212 or later (from the xz
distribution)
vermelho: muitos poblemas para instalar um xz paralelo
parallel? não?..
$ parallel
The program 'parallel' is currently not installed. You
can install it by typing: sudo apt-get install moreutils
$ apt-get install moreutils
...
$ parallel --help
parallel: invalid option -- '-'
parallel [OPTIONS] command -- arguments
for each argument, run command with argument, in
parallel
parallel [OPTIONS] -- commands
run specified commands in parallel
vermelho: o parallel do ubuntu parece muito velho
Bem vindo ao mundo real...
$ wget http://ftp.gnu.org/gnu/parallel/parallel-20110822.tar.bz2
$ tar xvf parallel-20110822.tar.bz2
$ cd parallel-20110822
$ ./configure && make -j 4
$ ./src/parallel --help
Usage:
parallel [options] [command [arguments]] < list_of_arguments
parallel [options] [command [arguments]] (::: arguments|:::: argfile(s))...
cat ... | parallel --pipe [options] [command [arguments]]
-j n Run n jobs in parallel
-k Keep same order
vermelho: parallel sendo buildado em paralelo :)
Quantos jobs pra compressão?...
$ uptime ; for j in $(seq $(( $(grep -c ^bogomips /proc/cpuinfo) * 4
)) ) ; do echo "$j: $( ( time ( mongoexport -d sambatech -c portal
2>/dev/null | head -20000 | parallel -j $j --pipe nice xz | xz -t ) )
2>&1 | tr "n" "t" )" ; done ; uptime
14:46:08 up 53 days, ... load average: 0.07, 1.29, 2.23
1: real 0m18.759s user 0m26.602s sys 0m2.448s
2: real 0m11.673s user 0m24.746s sys 0m1.916s
3: real 0m9.033s user 0m23.553s sys 0m1.460s
4: real 0m8.561s user 0m23.093s sys 0m1.468s
...
8: real 0m7.645s user 0m23.173s sys 0m1.356s
...
16: real 0m7.087s user 0m22.153s sys 0m1.260s
14:48:27 up 53 days, ... load average: 6.49, 2.88, 2.66
vermelho: parallel configurado para fazer split de um pipe para varios
xz
azul: numero de jogs testados: de 1 ateh 4 vezes o numero de cores
verde: o nice eh importante senao o mongoexport nao tem cpu o
suficiente para alimentar todos os xz
laranja: note que usei o time built-in, porque estou fazendo time de um
parenteses (sub-shell)
Achievement Unlocked
$ ( export H=localhost D=sambatech C=portal; export F="
dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)";
mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c
-N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" |
mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 |
cut -c 2- )" | parallel --pipe nice xz | pv -c -N xz | tee $F.xz |
md5sum > $F.md5 ) #dump-mongo-collection
raw: 48.9MB 0:00:10 [5.64MB/s] [ <=> ]
xz: 6.38MB 0:00:10 [ 678kB/s] [ <=> ]
rows: 29.6k 0:00:10 [3.27k/s] [==> ] 40% ETA 0:00:14
vermelho: o parallel na linha de dump do banco; o default do parallel eh
um para cada core
cinza: tag, pra lembrar do oneliner
2
E se eu quiser mandar pra net?
$ scp dump.xz example.com:
dump.xz 100% 97MB 32.3MB/s 00:03
Putz, mas e a integridade?
$ ssh example.com "xz -vd > dump" < dump-*.xz
(stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10
vermelho: descomprime do lado remoto para verificar integridade
azul: arquivo armazenado estah descomprimido
fundo amarelo: roda remoto
Mas não queria descomprimir lá...
$ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F"
vermelho: tee para testar apenas
azul: armazena o arquivo comprimido
fundo amarelo: roda remoto
Ueh?.. kd o output?
$ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F"
$
vermelho: xz -tv normalmente imprime na stderr
fundo amarelo: roda remoto
Aff, a stderr nao passou pelo ssh,
claro... mas tudo bem.
$ time ( F="dump.xz"; ssh example.com "{ tee >(xz -vt) > '$F' ; }
2>&1" < "$F" )
(stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10
real 0m10.580s
user 0m2.963s
sys 0m0.243s
vermelho: redirecionando a stderr para stdout antes de sair do ssh
azul: xz e seu output
verde: stdout dentro das chaves precisa ser preservada para o arquivo
laranja: tempo total do processo
fundo amarelo: roda remoto
note que ssh -t nao funciona: Pseudo-terminal will not be allocated
because stdin is not a terminal.
Soh que descomprimir o xz pra validar
eh overkill... que tal usar o md5sum?
$ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) >
(echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp -
d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee
>(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n
"s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee
'"$F"' | md5sum -c $FIFO/b | cut -c 4-'
vermelho: multiplexa arquivo, seu md5 e tamanho
azul: demultiplexa do outro lado
verde: fifo a carrega o tamanho do arquivo e o fifo b o md5
roxo: trap para apagar os fifos quando sair, mesmo se cancelado
fundo amarelo: roda remoto
Droga, deadlock! E agora?...
$ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) >
(echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp -
d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee
>(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n
"s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee
'"$F"' | md5sum -c $FIFO/b | cut -c 4-'
^C
vermelho: multiplexa arquivo, seu md5 e tamanho
azul: demultiplexa do outro lado
verde: fifo a carrega o tamanho do arquivo e o fifo b o md5
roxo: trap para apagar os fifos quando sair, mesmo se cancelado
fundo amarelo: roda remoto
o deadlock causado por arquivos maiores que o buffer do pipe porque
os metadados estao no final (nao tem como saber o md5 no inicio),
mas para comecar a gravar o arquivo no disco, e permitir que o pipe
avance, preciso do size e do md5 nos fifos
kkkk, tem um jeito bem mais simples!
$ time ( F=dump.xz; pv "$F" | { tee >(md5sum 1>&2) | ssh
example.com 'tee '"$F"' | md5sum' ; } 2>&1 | sort -u | wc -l |
grep -qx 1 && echo OK || echo ERR )
96.9MB 0:00:03 [28.7MB/s] [===========>] 100%
OK
real 0m3.212s
user 0m2.373s
sys 0m0.307s
$ ### time ssh + xz:
$ #real 0m10.580s
$ #user 0m2.963s
$ #sys 0m0.243s
vermelho: o md5 eh rodado local (para stderr) e remoto (stdout)
azul: no final verificamos se tem apenas uma variedade de arquivo
laranja: o md5sum eh bem mais rapido que o xz -t
fundo amarelo: roda remoto
Jah tah bem mais leve;
que tal tirar o ssh tb?
$ { F="dump.xz" ; H="example.com" ; P=9999 ; pv "$F" | tee >
(md5sum 1>&2) >(wc -c 1>&2) >( sed -n '' | ssh "$H" "{ netcat
$(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee
'$F' >(wc -c 1>&2) | md5sum ; } 2>&1" 1>&2 ) >(netcat -l $P)
>/dev/null ; } 2>&1 | tee >( sort -u | wc -l | grep -qx 2 && echo
OK || echo ERR )
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
19335168
85d99c783b541885b05ef263c1f35cdc -
ERR
azul: todo o fluxo foi controlado por um unico pipe, mas o ssh que nao
deveria receber os dados teve seu input editado
vermelho: deu erro na transmissao porque o pipe produtor fechou
antes do netcat receptor terminar; esse erro eh intermitente
verde: note que a seguranca eh fraca, se alguem estiver tentando
pegar o arquivo dessa porta vai conseguir, isso nao eh pra redes
desprotegidas
fundo amarelo: roda remoto
Morreu antes de terminar o envio,
o receptor vai ter que autorizar o fim
$ { F="dump.xz" ; H="example.com" ; P=$(printf "39%03d"
$RANDOM | cut -c 1-5) ; { ssh "$H" "{ netcat $(set | grep [S]
SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee '$F' >(wc -c
1>&2) | md5sum ; } 2>&1" 1>&2 ; } | { < "$F" tee >(md5sum
1>&2) >(wc -c 1>&2) >(netcat -l $P) >/dev/null ; } ; } 2>&1 | tee
>( sort -u | wc -l | grep -qx 2 && echo OK || echo ERR )
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
19405636
a84a3cd315f4ee235c5e15d82872ab5d -
OK
vermelho: o ssh estah dominando o pipe
azul: o pipe soh morre quando o ssh morrer
roxo: o produtor vem depois
verde: um pouco mais de segurança por obscuridade
fundo amarelo: roda remoto
isso funcionou no ubuntu 10.10, mas nao funcionou no archlinux 2011-
09
Cuidados com o netcat
$ { F="dump.xz" ; H=example.com ; P=$(printf "39%03d" $RANDOM |
cut -c 1-5) ; S=$(stat -c %s "$F") ; { { pv "$F" | { tee >(md5sum 1>&2) |
{ netcat -l -p $P 2>/dev/null || netcat -l $P ; } ; } 2>&1 ; } & } ; A=$! ; ssh
$H "{ H=$(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") ; {
netcat -w 5 -c $H $P 2>/dev/null || netcat -w 5 $H $P ; } | tee '$F' >
(md5sum 1>&2) | pv -n -s $S 2>&1 >/dev/null | while read X ; do test "
$X" -ge 100 && kill -INT $(pstree -laAp $$ | sed -n 's/^[^,]*<netcat,
([0-9][0-9]*)>.*/1/p') &>/dev/null ; done ; } 2>&1" ; kill -INT $(pstree -
laAp $A | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; } |
uniq | wc -l | grep -qx 1 && echo OK || echo ERR #netcat-send
537MB 0:00:04 [ 110MB/s] [=================>] 100%
OK
vermelho: por causa das inconsistencias de terminacao do netcat, tive
que monitorar o progresso da transmissao e matar explicitamente o
netcat
azul: alternativas de parametros do netcat em sistemas diferentes
verde: melhoria de tempo
a inversao do consumidor com o produtor acrescentou um race-
condition, o ssh podia tentar conectar no netcat local antes dele existir
(teoricamente)
e pior, em alguns sistemas o netcat precisa de um "-p" alem do "-l" e,
pior, algumas versões não fecham, sem o "-c", quando o pipe fecha, o
que faz o script anterior travar. nesse ultimo caso o ideal é usar a
mesma técnica do mongoexport; por em background e matar com
pstree
requisitos:
1) netcat escutando tem que ser criado primeiro;
2) logo o netcat do ssh tem que conectar depois,
3) ateh porque ele que tem a autoridade de terminar pq o receptor
terminar depois.
4) foi preciso fazer fallback entre os dois estilos de netcat;
5) soh o netcat que conecta fecha consistentemente com o pipe, mas,
como o ssh precisa conectar ./ fechar e ele nao tem o arquivo, foi
preciso monitorar a transmissao com pv e matar explicitamente o
netcat
melhor ter um netcat confiavel, mas se nao foi possivel o slide
apresenta um contorno para o problema
Mas serah que houve vantagem?
$ time !?#netcat-send
537MB 0:00:04 [ 110MB/s] [============>]
100%
real 0m5.298s
user 0m0.063s
sys 0m0.003s
OK
$ time !?scp
dump.xz 100% 537MB 35.8MB/s 00:15
real 0m14.658s
user 0m11.123s
sys 0m1.130s
vermelho: tempos do netcat e do ssh
3
E se forem vários arquivos?
$ find dumps/ -type f -printf "%10s %pn"
101632892 dumps/b/dump.xz.2
65536 dumps/b/a/dump.xz.1
0 dumps/a/3688
0 dumps/a/8958
(... total de 10000 arquivos no dumps/a/)
0 dumps/a/4308
0 dumps/a/3338
$ scp -r dumps/* example.com:dump-backup/
(...)
3338 100% 0 0.0KB/s 00:00
dump.xz.2 100% 97MB 48.5MB/s 00:02
dump.xz.1 100% 64KB 64.0KB/s 00:00
vermelho: scp recursivo
Mas quero selecionar melhor
$ tar c -C dumps/ --exclude=./a . | ssh example.com "tar xv -C
dump-backup/"
./
./b/
./b/dump.xz.2
./b/a/
./b/a/dump.xz.1
vermelho: tarpipe
azul: um lista menor foi selecionada
Progresso...
$ TAR="tar c -C dumps/ --exclude=./a ." ; $TAR | pv -s $($TAR |
wc -c) | ssh example.com "tar x -C dump-backup/"
97MB 0:00:02 [32.8MB/s] [================>] 100%
vermelho: pv mostrando o progresso a partir de uma rodada previa do
mesmo tar
Não rola fazer o tar duas vezes...
$ S="dumps" ; D="dump-backup" ; X="--exclude=a" ;
H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --
apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C
'$D' "
96.9MB 0:00:02 [33.5MB/s] [==============>] 100%
$ S="dumps" ; D="dump-backup" ; X="" ; H=example.com ; tar
c -C "$S" $X . | pv -s $(cd "$S" && du --apparent-size --block-
size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' "
102MB 0:00:03 [33.7MB/s] [================] 104%
vermelho: du do tamanho dos arquivos, isso eh impreciso quando tem
arquivos pequenos no meio
azul: filtros para um arquivo grande e um arquivo grande com muitos
pequenos, respectivamente
Mas assim tem muita imprecisão... :/
$ S="dumps" ; D="dump-backup" ; X="--exclude=./a --exclude=.
/b/d*" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" &&
du --apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar
x -C '$D' "
70kB 0:00:00 [ 544kB/s] [====> ] 29%
$ S="dumps" ; D="dump-backup" ; X="--exclude=./b/d*" ;
H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --
apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C
'$D' "
4.95MB 0:00:00 [7.77MB/s] [================] 1267%
vermelho: du do tamanho dos arquivos, isso eh impreciso
especialmente se tiverem varios arquivos pequenos, por causa do
overhead do tar, mas quebra um galho.
azul: filtros para um arquivo muito pequeno e muitos arquivos
pequenos, respectivamente
Vou tentar incorporar o overhead do tar
na conta do tamanho total
$ for X in "('./a/*' './b/a/*')" "()" "('./a/*' './b/d*')" "('./b/d*')" ; do eval
"X=$X" ; E=""; N=""; for x in "${X[@]}" ; do test -n "$N" && N="$N -and"
; N="$N -not -path '$x'" ; E="$E --exclude='$x'" ; done ; echo "${X[@]}
=> $E => $N"; S="dumps" ; D="dump-backup" ; H=example.com ; eval
"tar c -C '$S' $E ." | pv -s $(cd "$S" && eval "find . $N -printf '%y %s %
pn'" | awk 'BEGIN { sum=0 } { s=length($0)-length($2)-1+($1 ~ /f/ ? $2
: 0) ; c=512; r=s%c; s=s+(r?c-r:0) ; sum+=s } END { chunk=10240;
remain=sum%chunk; print sum+(remain?chunk-remain:0) }') | ssh $H
"tar x -C '$D' " ; done #tar-pipe-ssh
96.9MB 0:00:02 [34.7MB/s] [====================>] 100%
102MB 0:00:02 [41.5MB/s] [====================>] 100%
70kB 0:00:00 [ 835kB/s] [====================>] 100%
4.95MB 0:00:00 [21.2MB/s] [====================>] 100%
vermelho: find + awk calculando, respectivamente os arquivos da
mesma maneira que o tar vai fazer
azul: todas as configurações de exceção mostradas antes
passo pelos arquivos duas vezes, mas isso tem uma certa vantagem,
que a primeira eh soh pela listagem de diretorios (curta e que deve
ficar cacheada depois dessa passada) e a segunda pelo conteudo dos
arquivos.. que ficam em lugares bem diferentes do disco, entao
possivelmente fazendo isso estou evitando muito io-wait em seek
0
Não tentem isso em casa
Nesse fantástico mundo dos one-liners se pode usar o history
como repositório de comandos; basta comentá-los com
tags memoráveis e deixar o history bem, bem grande:
$ export HISTSIZE=999999999 HISTFILESIZE=999999999
$ echo !! >> $(ls -S ~/.{,bash}{rc,{,_}profile} 2>/dev/null | head
-1)
$ ls -ltr #lista-recentes
...
$ !?#lista-re
ls -ltr #lista-recentes
...
Referência
GNU bash, version 4.1.5(1)-release (i686-pc-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc. (*)
GNU bash, version 4.2.10(2)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc. (+)
License GPLv3+: GNU GPL version 3 or later <http://gnu.
org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
(*) Ubuntu 10.10
(+) Arch Linux 2011-09
Obrigado
Obrigado
( export H=localhost D=sambatech C=portal; export F="dump-$H-$D-$C-$(date +%Y%m%d_%H%M%
S)"; mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c -N rows -l -s "$( echo -e "use $D;
ndb.$C.find().count();" | mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 | cut -c 2- )" | parallel
--pipe nice xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 ) +++++++++ { F="dump.xz" ; H=example.com
; P=$(printf "39%03d" $RANDOM | cut -c 1-5) ; S=$(stat -c %s "$F") ; { { pv "$F" | { tee >(md5sum 1>&2) |
{ netcat -l -p $P 2>/dev/null || netcat -l $P ; } ; } 2>&1 ; } & } ; A=$! ; ssh $H "{ H=$(set | grep [S]
SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") ; { netcat -w 5 -c $H $P 2>/dev/null || netcat -w 5 $H $P ; } | tee
'$F' >(md5sum 1>&2) | pv -n -s $S 2>&1 >/dev/null | while read X ; do test "$X" -ge 100 && kill -INT
$(pstree -laAp $$ | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; done ; } 2>&1" ; kill -INT
$(pstree -laAp $A | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; } | uniq | wc -l | grep -qx 1
&& echo OK || echo ERR +++++++++ for X in "('./a/*' './b/a/*')" "()" "('./a/*' './b/d*')" "('./b/d*')" ; do eval
"X=$X" ; E=""; N=""; for x in "${X[@]}" ; do test -n "$N" && N="$N -and" ; N="$N -not -path '$x'" ; E="$E --
exclude='$x'" ; done ; echo "${X[@]} => $E => $N"; S="dumps" ; D="dump-backup" ; H=example.com ;
eval "tar c -C '$S' $E ." | pv -s $(cd "$S" && eval "find . $N -printf '%y %s %pn'" | awk 'BEGIN { sum=0 } {
s=length($0)-length($2)-1+($1 ~ /f/ ? $2 : 0) ; c=512; r=s%c; s=s+(r?c-r:0) ; sum+=s } END {
chunk=10240; remain=sum%chunk; print sum+(remain?chunk-remain:0) }') | ssh $H "tar x -C '$D' " ; done
http://bash.org/
http://tldp.org/LDP/abs/
TODO linha que pega uma lista de arquivos do banco, seleciona quais
não estão no destino e envia apenas esses por tar | netcat

Mais conteúdo relacionado

Destaque

The oscillating(cyclic) universe
The oscillating(cyclic) universeThe oscillating(cyclic) universe
The oscillating(cyclic) universeAnil Thanvi
 
Preparation of negatively charged polymer coated magnetic nanoparticles as t...
Preparation of negatively charged  polymer coated magnetic nanoparticles as t...Preparation of negatively charged  polymer coated magnetic nanoparticles as t...
Preparation of negatively charged polymer coated magnetic nanoparticles as t...Abeer Abd Elrahman
 
Introduction to Polymer
Introduction to PolymerIntroduction to Polymer
Introduction to PolymerEgor Miasnikov
 
Sagar grdds final ppt pd
Sagar grdds final ppt pdSagar grdds final ppt pd
Sagar grdds final ppt pdSagar Bansal
 
Hydrodynamically balanced systems
Hydrodynamically balanced systemsHydrodynamically balanced systems
Hydrodynamically balanced systemsNikhil Bhandiwad
 
An introduction-to-polymer-physics
An introduction-to-polymer-physicsAn introduction-to-polymer-physics
An introduction-to-polymer-physicsfhdjamshed
 
Strain gauge loadcell ppt
Strain gauge loadcell pptStrain gauge loadcell ppt
Strain gauge loadcell pptmaneeb
 
Compression pdf
Compression pdfCompression pdf
Compression pdfnisargrx
 
Mucoadhesive drug delivery system
Mucoadhesive drug delivery systemMucoadhesive drug delivery system
Mucoadhesive drug delivery systemJamia Hamdard
 
Polymer science: preparation and uses of polymers
Polymer science: preparation and uses of polymersPolymer science: preparation and uses of polymers
Polymer science: preparation and uses of polymersVARSHAAWASAR
 
Polymers and their properties
Polymers and their propertiesPolymers and their properties
Polymers and their propertiesripestone_ho
 
Transdermal drug delivery system ppt
Transdermal drug delivery system pptTransdermal drug delivery system ppt
Transdermal drug delivery system pptDeepak Sarangi
 
Transdermal drug delivery systems
Transdermal drug delivery systemsTransdermal drug delivery systems
Transdermal drug delivery systemsSonam Gandhi
 
Transdermal drug delivery system
Transdermal drug delivery systemTransdermal drug delivery system
Transdermal drug delivery systemDanish Kurien
 

Destaque (20)

The oscillating(cyclic) universe
The oscillating(cyclic) universeThe oscillating(cyclic) universe
The oscillating(cyclic) universe
 
Viscoelasticity
ViscoelasticityViscoelasticity
Viscoelasticity
 
Preparation of negatively charged polymer coated magnetic nanoparticles as t...
Preparation of negatively charged  polymer coated magnetic nanoparticles as t...Preparation of negatively charged  polymer coated magnetic nanoparticles as t...
Preparation of negatively charged polymer coated magnetic nanoparticles as t...
 
Introduction to Polymer
Introduction to PolymerIntroduction to Polymer
Introduction to Polymer
 
vcr engine
vcr enginevcr engine
vcr engine
 
Polymers and its Viscoelastic Nature
Polymers and its Viscoelastic NaturePolymers and its Viscoelastic Nature
Polymers and its Viscoelastic Nature
 
Sagar grdds final ppt pd
Sagar grdds final ppt pdSagar grdds final ppt pd
Sagar grdds final ppt pd
 
Viscoelasticity
ViscoelasticityViscoelasticity
Viscoelasticity
 
Hydrodynamically balanced systems
Hydrodynamically balanced systemsHydrodynamically balanced systems
Hydrodynamically balanced systems
 
An introduction-to-polymer-physics
An introduction-to-polymer-physicsAn introduction-to-polymer-physics
An introduction-to-polymer-physics
 
Strain guage
Strain guageStrain guage
Strain guage
 
Strain gauge loadcell ppt
Strain gauge loadcell pptStrain gauge loadcell ppt
Strain gauge loadcell ppt
 
Compression pdf
Compression pdfCompression pdf
Compression pdf
 
Mucoadhesive drug delivery system
Mucoadhesive drug delivery systemMucoadhesive drug delivery system
Mucoadhesive drug delivery system
 
Polymer science: preparation and uses of polymers
Polymer science: preparation and uses of polymersPolymer science: preparation and uses of polymers
Polymer science: preparation and uses of polymers
 
Polymers and their properties
Polymers and their propertiesPolymers and their properties
Polymers and their properties
 
Transdermal drug delivery system ppt
Transdermal drug delivery system pptTransdermal drug delivery system ppt
Transdermal drug delivery system ppt
 
Transdermal drug delivery systems
Transdermal drug delivery systemsTransdermal drug delivery systems
Transdermal drug delivery systems
 
Strain gauge
Strain gaugeStrain gauge
Strain gauge
 
Transdermal drug delivery system
Transdermal drug delivery systemTransdermal drug delivery system
Transdermal drug delivery system
 

Semelhante a 3 Bash One-Liners para Exportar e Comprimir Dados

Falhando miseralvelmente com PHP
Falhando miseralvelmente com PHPFalhando miseralvelmente com PHP
Falhando miseralvelmente com PHPAugusto Pascutti
 
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...UFAM - Universidade Federal do Amazonas
 
O que mudou no Ruby 1.9
O que mudou no Ruby 1.9O que mudou no Ruby 1.9
O que mudou no Ruby 1.9Nando Vieira
 
Escrevendo modulos python com rust
Escrevendo modulos python com rustEscrevendo modulos python com rust
Escrevendo modulos python com rustBruno Rocha
 
Tornado mais do que um framework bonitinho
Tornado   mais do que um framework bonitinhoTornado   mais do que um framework bonitinho
Tornado mais do que um framework bonitinhoMarcel Nicolay
 
Três operações de boot
Três operações de bootTrês operações de boot
Três operações de bootSirene Monturil
 
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018Tchelinux
 
Shell Tips and Tricks for sysadmin
Shell Tips and Tricks for sysadminShell Tips and Tricks for sysadmin
Shell Tips and Tricks for sysadminMagno Torres
 
Seguranca informacao 1
Seguranca informacao 1Seguranca informacao 1
Seguranca informacao 1Nauber Gois
 
Python: a primeira mordida
Python: a primeira mordidaPython: a primeira mordida
Python: a primeira mordidaLuciano Ramalho
 
O Que é shell - bash
O Que é shell - bashO Que é shell - bash
O Que é shell - bashSérgio Silva
 
Palestra ganeti puppet
Palestra ganeti puppetPalestra ganeti puppet
Palestra ganeti puppetDaniel Sobral
 
Simpósio Unicruz: OpenCV + Python (parte 1)
Simpósio Unicruz: OpenCV + Python (parte 1)Simpósio Unicruz: OpenCV + Python (parte 1)
Simpósio Unicruz: OpenCV + Python (parte 1)Cristiano Rafael Steffens
 
Puppet – Torne Seu Datacenter áGil
Puppet – Torne Seu Datacenter áGilPuppet – Torne Seu Datacenter áGil
Puppet – Torne Seu Datacenter áGiljefesrodrigues
 
Gentoo por marcelo rocha
Gentoo por marcelo rochaGentoo por marcelo rocha
Gentoo por marcelo rochaSENAC RIO
 

Semelhante a 3 Bash One-Liners para Exportar e Comprimir Dados (20)

Falhando miseralvelmente com PHP
Falhando miseralvelmente com PHPFalhando miseralvelmente com PHP
Falhando miseralvelmente com PHP
 
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...
Uma abordagem BigData para Encontrar Sub-conjuntos de itens frequentes median...
 
O que mudou no Ruby 1.9
O que mudou no Ruby 1.9O que mudou no Ruby 1.9
O que mudou no Ruby 1.9
 
Escrevendo modulos python com rust
Escrevendo modulos python com rustEscrevendo modulos python com rust
Escrevendo modulos python com rust
 
Tornado mais do que um framework bonitinho
Tornado   mais do que um framework bonitinhoTornado   mais do que um framework bonitinho
Tornado mais do que um framework bonitinho
 
(2013-05-03) AudioLazy - Slides
(2013-05-03) AudioLazy - Slides(2013-05-03) AudioLazy - Slides
(2013-05-03) AudioLazy - Slides
 
Três operações de boot
Três operações de bootTrês operações de boot
Três operações de boot
 
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018
Introdução ao SystemTap - João Avelino Bellomo Filho - Tchelinux Caxias 2018
 
Inpe
InpeInpe
Inpe
 
Shell Tips and Tricks for sysadmin
Shell Tips and Tricks for sysadminShell Tips and Tricks for sysadmin
Shell Tips and Tricks for sysadmin
 
Seguranca informacao 1
Seguranca informacao 1Seguranca informacao 1
Seguranca informacao 1
 
Python: a primeira mordida
Python: a primeira mordidaPython: a primeira mordida
Python: a primeira mordida
 
O Que é shell - bash
O Que é shell - bashO Que é shell - bash
O Que é shell - bash
 
Asterisk
AsteriskAsterisk
Asterisk
 
Palestra ganeti puppet
Palestra ganeti puppetPalestra ganeti puppet
Palestra ganeti puppet
 
Simpósio Unicruz: OpenCV + Python (parte 1)
Simpósio Unicruz: OpenCV + Python (parte 1)Simpósio Unicruz: OpenCV + Python (parte 1)
Simpósio Unicruz: OpenCV + Python (parte 1)
 
Ccatt manual
Ccatt manualCcatt manual
Ccatt manual
 
Minicurso Ruby on Rails
Minicurso Ruby on RailsMinicurso Ruby on Rails
Minicurso Ruby on Rails
 
Puppet – Torne Seu Datacenter áGil
Puppet – Torne Seu Datacenter áGilPuppet – Torne Seu Datacenter áGil
Puppet – Torne Seu Datacenter áGil
 
Gentoo por marcelo rocha
Gentoo por marcelo rochaGentoo por marcelo rocha
Gentoo por marcelo rocha
 

3 Bash One-Liners para Exportar e Comprimir Dados

  • 1. 3 bash one-liners ccaputo@sp.r7.com carlo.caputo@gmail.com onliners: sao codigos escritos todos em uma linha bash: eh um shell muito popular, provavelmente quando vc abre o seu terminal vc estah usando o bash entao, vou contar 3 histórias.
  • 2. 1
  • 3. To lá eu fazendo um dump de banco... $ mongoexport -d sambatech -c portal | gzip > dump.json.gz connected to: 127.0.0.1
  • 4. Demorou mais de 10 segundos... $ mongoexport -d sambatech -c portal | gzip > dump.json.gz connected to: 127.0.0.1 ^C
  • 5. Tá, preciso mostrar o progresso... $ mongoexport -d sambatech -c portal | gzip | pv > dump.json. gz connected to: 127.0.0.1 7.83MB 0:00:05 [1.75MB/s] [ <=> ] vermelho: pv simples mostrando a taxa de transferencia
  • 6. Hunnn... Ainda não sei quanto falta. $ mongoexport -d sambatech -c portal | pv -c -N raw | gzip | pv -c -N gz > dump.json.gz connected to: 127.0.0.1 raw: 54.5MB 0:00:07 [8.28MB/s] [ <=> ] gz: 11.1MB 0:00:07 [1.72MB/s] [ <=> ] vermelho: pv mostrando o ratio de compressao on-the-fly
  • 7. Ah, eu sei quantas linhas são... $ ( export H=localhost D=sambatech C=portal; export F=" dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)"; mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c -N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" | mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 | cut -c 2- )" | gzip | pv -c -N gz | tee $F.gz | md5sum > $F.md5 ) raw: 78.4MB 0:00:10 [7.97MB/s] [ <=> ] gz: 16.1MB 0:00:10 [1.69MB/s] [ <=> ] rows: 46.7k 0:00:10 [4.59k/s] [> ] 63% ETA 0:00:05 vermelho: novo pv contando porcentagem de linhas, calculadas por um count() do mongo azul: variaveis para nao repetir as informacoes
  • 8. Mas tá muito idle ainda.... top - 23:19:46 up 53 days, 17 min, 24 users, load average: 0.15, 0.12, 0.09 Tasks: 334 total, 2 running, 331 sleeping, 0 stopped, 1 zombie Cpu0 : 33.3%us, 8.8%sy, 0.0%ni, 57.9%id, 0.0%wa, 0.0%hi, 0.0%si, Cpu1 : 2.3%us, 1.0%sy, 0.3%ni, 94.2%id, 2.3%wa, 0.0%hi, 0.0%si, Cpu2 : 85.2%us, 4.6%sy, 0.0%ni, 10.2%id, 0.0%wa, 0.0%hi, 0.0%si, Cpu3 : 2.6%us, 1.0%sy, 0.3%ni, 94.1%id, 2.0%wa, 0.0%hi, 0.0%si, Mem: 4107620k total, 3687764k used, 419856k free, 278800k buffers Swap: 4192960k total, 363512k used, 3829448k free, 658816k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 24448 zed 20 0 17688 4300 2880 R 98 0.1 0:10.45 mongoexport 24451 zed 20 0 2052 504 344 S 31 0.0 0:03.20 gzip 24449 zed 20 0 4056 644 568 S 10 0.0 0:01.04 pv 24450 zed 20 0 4056 656 576 S 8 0.0 0:00.81 pv 3587 mongodb 20 0 804m 120m 119m S 1 3.0 0:08.59 mongod 24456 zed 20 0 3892 508 440 S 1 0.0 0:00.07 md5sum 24454 zed 20 0 3884 548 480 S 1 0.0 0:00.05 tee 24452 zed 20 0 4056 660 568 S 0 0.0 0:00.03 pv vermelho: cpu idle, mongoexport eh gargalo
  • 9. Vamos ver... $ time --help --help: command not found $ type time time is a shell keyword $ help time time: time [-p] pipeline Report time consumed by pipeline's execution. $ /usr/bin/time --help Usage: /usr/bin/time [-apvV] [-f format] [-o file] [--append] [--verbose] [--portability] [--format=format] [--output=file] [--version] [--quiet] [--help] command [arg...] tem dois times no sistema, cuidado com aliases e built-ins
  • 10. Ei, espera aí, que tal usar xz?! $ mongoexport -d sambatech -c portal | head -c 10000 | tee >( ( /usr/bin/time -f 'gz: %E real, %U user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) | /usr/bin/time -f 'xz: %E real, %U user, % S sys' xz -9 | wc -c | sed 's/^/xz: /' connected to: 127.0.0.1 gz: 0:00.24 real, 0.00 user, 0.00 sys gz: 2078 xz: 0:00.31 real, 0.02 user, 0.04 sys xz: 2012 vemelho: limitei os bytes de entrada em um tamanho arbitrário, mas ele foi insuficiente para chegar a alguma conclusão.
  • 11. Melhor limitar por tempo do que por bytes. Mas como parar?... $ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed 's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) | /usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c | sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT %% [1] 12289 connected to: 127.0.0.1 $ raw: 14623779 gz: 0:08.99 real, 0.75 user, 0.00 sys gz: 2706591 Command terminated by signal 2 xz: 0:09.02 real, 8.78 user, 0.17 sys vermelho: os 3 wc(raw, gz, xz) e os seus respectivos resultados (note que falta um resultado) azul: a causa; tentei matar o pipe inteiro, mas o resultado ficou cortado.
  • 12. Ahh, aí está o número perdido! $ { ( mongoexport -d sambatech -c portal | tee >(wc -c | sed 's/^/raw: /' 1>&2) | tee >( ( /usr/bin/time -f 'gz: %E real, %U user, %S sys' gzip -9 | wc -c | sed 's/^/gz: /' ) 1>&2) | /usr/bin/time -f 'xz: %E real, %U user, %S sys' xz -9 | wc -c | sed 's/^/xz: /' ) & } ; sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/. *mongoexport(([0-9]*)).*/1/p') [1] 12724 connected to: 127.0.0.1 $ raw: 14497402 gz: 0:09.10 real, 0.73 user, 0.01 sys gz: 2695783 xz: 0:09.18 real, 8.73 user, 0.16 sys xz: 2021928 vermelho: o wc e sue resultado ,(que estava perdido) azul: o que fiz pra resolver, matei o processo inicial do pipe e não o pipe todo roxo: pid do ultimo processo que foi pra background
  • 13. Mas deixa eu formatar isso melhor... $ { ( { mongoexport -d sambatech -c portal 2>/dev/null | tee > (wc -c | sed 's/^/s_raw=/' 1>&2) | tee >( ( /usr/bin/time -f 't_gz=%U+%S' gzip -9 | wc -c | sed 's/^/s_gz=/') 1>&2) | /usr/bin/time -f 't_xz=%U+%S' xz -9 | wc -c | sed 's/^/s_xz=/' ; echo 'scale=2; print "nxz is ", t_xz/t_gz; r_sz=s_gz/s_xz; r_gz=s_gz/s_raw; r_xz=s_xz/s_raw; scale=0; print " times slower and ", (r_sz-1)/.01,"% smallerncompression ratios: gz=",(1-r_gz)/.01,"%, xz=",(1-r_xz)/.01,"%n"' ; } 2>&1 | bc ) & } ; sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.*mongoexport(([0-9] *)).*/1/p') xz is 12.02 times slower and 33% smaller compression ratios: gz=82%, xz=87% vermelho: contas em bc pra calcular as taxas azul: variaveis usadas no bc fundo verde: bloco de shell script cujo output eh codigo para o bc
  • 14. Ai... :/ será que tem algum outro nível de compressão que valha a pena? $ { ( { { echo -n 'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for z in 0 1 ; do for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " > (/usr/bin/time -f 't[$z$l]=%U+%S' $(test $z == 1 && echo gzip || echo xz) -$l | wc -c | sed 's/^/s[$z$l]=/' 1>&2)" ; done ; done ; echo -n ' | cat > /dev/null' ; } | bash 2>&1 ; echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0; x<=1;x++) { for(l=x;l<=9;l++) { i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print 1/s^p/t," (1/s^",p,"/t) "; if(x==1) print "gzip" else print "xz"; print" -",l," is ", t," time and ",s," size of gzip -6n" } } }' ; } | bash <( echo -n 'bc | tee ' ; for p in $(seq 1 6 13) ; do echo -n " >(grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)" ; done ; echo -n ' > /dev/null' ) 2>&1 ) & } ; sleep 9 ; kill -INT $(pstree -p $! | sed -n 's/.*mongoexport(([0-9]*)).*/1/p'); fg (1/s^1/t) gzip -2 is .62 time and 1.12 size of gzip -6 (1/s^7/t) gzip -6 is 1.00 time and 1.00 size of gzip -6 (1/s^13/t) xz -6 is 14.00 time and .76 size of gzip -6 enfase na metaprogramacao vermelho: interpretadores, o bash do meio teve que receber o script como arquivo e nao como stdin pq o bc dentro dele precisava da stdin pra ele. azul: geracao dinamica dos scripts fundo vermelho: primeiro bash, que vai gerar um bash script que, finalmente, vai escrever as variaveis para o bc fundo verde: codigo estatico para o bc fundo azul: script shell que vai receber com stdin um codigo bc e vai parsear a saida do bc verde: o cat eh necessario pq os redirects se comportam diferentes do pipe, sem ele as variaveis viriam depois do codigo bc que usa elas branco com fundo vermelho: tem um race-condition na geracao paralela das variaveis do bc, tive que rodar varias vezes pra conseguir
  • 16. pstree dos comandos no slide anterior bash─┬─bash───bash─┬─cat │ ├─mongoexport │ └─tee─┬─10*[bash─┬─sed] │ │ ├─time───xz] │ │ └─wc] │ └─9*[bash─┬─sed] │ ├─time───gzip] │ └─wc] └─bash─┬─bc └─tee───3*[bash─┬─cut] ├─grep] ├─sort] └─tail] vermelho: os pontos chaves de processamento azul: o unico input que flui por toda essa arvore verde: tees que forkam os processos e pipes
  • 17. código bash, resultado do primeiro trecho mongoexport -d sambatech -c portal 2>/dev/null | tee >(/usr/bin/time -f 't[00]=%U+%S' xz -0 | wc -c | sed 's/^/s[00]=/' 1>&2) >(/usr/bin/time -f 't[01]=%U+%S' xz -1 | wc -c | sed 's/^/s[01]=/' 1>&2) >(/usr/bin/time -f 't[02]=%U+%S' xz -2 | wc -c | sed 's/^/s[02]=/' 1>&2) >(/usr/bin/time -f 't[03]=%U+%S' xz -3 | wc -c | sed 's/^/s[03]=/' 1>&2) >(/usr/bin/time -f 't[04]=%U+%S' xz -4 | wc -c | sed 's/^/s[04]=/' 1>&2) >(/usr/bin/time -f 't[05]=%U+%S' xz -5 | wc -c | sed 's/^/s[05]=/' 1>&2) >(/usr/bin/time -f 't[06]=%U+%S' xz -6 | wc -c | sed 's/^/s[06]=/' 1>&2) >(/usr/bin/time -f 't[07]=%U+%S' xz -7 | wc -c | sed 's/^/s[07]=/' 1>&2) >(/usr/bin/time -f 't[08]=%U+%S' xz -8 | wc -c | sed 's/^/s[08]=/' 1>&2) >(/usr/bin/time -f 't[09]=%U+%S' xz -9 | wc -c | sed 's/^/s[09]=/' 1>&2) >(/usr/bin/time -f 't[11]=%U+%S' gzip -1 | wc -c | sed 's/^/s[11]=/' 1>&2) >(/usr/bin/time -f 't[12]=%U+%S' gzip -2 | wc -c | sed 's/^/s[12]=/' 1>&2) >(/usr/bin/time -f 't[13]=%U+%S' gzip -3 | wc -c | sed 's/^/s[13]=/' 1>&2) >(/usr/bin/time -f 't[14]=%U+%S' gzip -4 | wc -c | sed 's/^/s[14]=/' 1>&2) >(/usr/bin/time -f 't[15]=%U+%S' gzip -5 | wc -c | sed 's/^/s[15]=/' 1>&2) >(/usr/bin/time -f 't[16]=%U+%S' gzip -6 | wc -c | sed 's/^/s[16]=/' 1>&2) >(/usr/bin/time -f 't[17]=%U+%S' gzip -7 | wc -c | sed 's/^/s[17]=/' 1>&2) >(/usr/bin/time -f 't[18]=%U+%S' gzip -8 | wc -c | sed 's/^/s[18]=/' 1>&2) >(/usr/bin/time -f 't[19]=%U+%S' gzip -9 | wc -c | sed 's/^/s[19]=/' 1>&2) | cat > /dev/null fundo vermelho: codigo de bash que vai gerar codigo de bc
  • 18. t[09]=2.17+0.09 s[09]=500800 t[08]=2.21+0.07 s[08]=500800 t[07]=2.12+0.06 s[07]=500800 t[06]=2.12+0.04 s[06]=500800 t[05]=1.60+0.03 s[05]=518820 t[04]=1.56+0.01 s[04]=520052 t[03]=1.44+0.02 s[03]=524920 t[02]=0.50+0.02 s[02]=603160 t[01]=0.35+0.01 s[01]=634096 t[00]=0.32+0.02 s[00]=671448 scale=2; for(p=1;p<=13;p+=6) { for(x=0;x<=1;x++) { for(l=x;l<=9;l++) { i=x*10+l; s=s[16]/s[i]; t=t[i]/t[16]; print s^p/t," (s^",p,"/t) "; if(x==1) print "gzip" else print "xz"; print" -",l," is ",t," slower and ",s," smaller than gzip -6 n" } } } t[19]=0.20+0.00 s[19]=639917 t[18]=0.20+0.00 s[18]=640335 t[17]=0.16+0.00 s[17]=649143 t[16]=0.14+0.00 s[16]=652085 t[15]=0.13+0.01 s[15]=663714 t[14]=0.12+0.01 s[14]=690369 t[13]=0.09+0.01 t[12]=0.08+0.00 s[13]=722305 s[12]=734534 t[11]=0.09+0.00 s[11]=752727 código bc, resultado do primeiro e segundo trechos fundo vermelho: codigo de bc gerado pelo primeiro trecho fundo verde: codigo de bc que vai processar as variaveis mostradas no fundo vermelho
  • 19. código bash, resultado do terceiro trecho bc | tee >(grep 's^1/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2) >(grep 's^7/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2) >(grep 's^13/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2) > /dev/null fundo azul: ultimo script bash que recebe o stdin com o bc e faz o pos- processamento do seu output
  • 20. Como tirar o race-condition? $ while true ; do { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { { echo -n "$d: " ; ls /dev/fd/ ; } 1>&2 ; " ; done ; echo -n 'seq 2000 2>/dev/null | tee' ; for z in 0 1 ; do for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't[$z$l]=%U+%S' 2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc -c | sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n ' >/dev/null' ; for d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 | grep '' " ; done ; } | tee /dev/fd/2 | bash ; echo 123 ; } | sed 's/^/b: /' | grep =.*= ; } 2>/dev/null ; done vermelho: grep "" line buffered dos varios file descriptors; o ultimo grep testa por linhas encavaladas azul: debug supimido pelo 2>/dev/null no final mas isso eh uma outra história...
  • 21. Mais consistência no teste dos parâmetros de compressão $ { { { { { for d in {1,2}{0,1}{0..9} ; do echo -n " { " ; done ; echo -n 'mongoexport -d sambatech -c portal 2>/dev/null | tee' ; for z in 0 1 ; do for l in $(seq 0 9); do test $z = 1 -a $l = 0 || echo -n " >(/usr/bin/time -f 't [$z$l]=%U+%S' 2>&2$z$l $(test $z == 1 && echo gzip || echo xz) -$l | wc -c | sed 's/^/s[$z$l]=/' 1>&1$z$l)" ; done ; done ; echo -n ' > /dev/null' ; for d in {1,2}{0,1}{0..9} ; do echo -n " ; } $d>&1 | sed '' " ; done ; } | bash ; echo 'scale=2; for(p=1;p<=13;p+=6) { for(x=0;x<=1;x++) { for(l=x;l<=9;l++) { i=x*10+l; s=s[i]/s[16]; t=t[i]/t[16]; print 1/s^p/t," (1/s^",p,"/t) "; if(x==1) print "gzip" else print "xz"; print" -",l," is ",t," time and ",s," size than gzip -6n" } } }' ; } | bash <( echo -n 'bc | { tee ' ; for p in $(seq 1 6 13) ; do echo -n " > (grep 's^$p/t' | sort -n | tail -1 | cut -d ' ' -f 2- 1>&2)" ; done ; echo -n ' >/dev/null ; } 2>&1 | grep "" ' ) ; } & } ; A=$! ; { { sleep 9 ; kill -INT $(pstree -laAp $A | sed -n 's/^[^,]*<mongoexport,([0-9][0-9]*)>.*/1/p') ; } & } ; B=$! ; fg %- || { echo "finishing..." ; kill -INT $( { pstree -laAp $A ; pstree - laAp $B ; } | sed -n 's/^[^,]*,([0-9][0-9]*)>.*/1/p') ; } ; } #xz-vs-gz vermelho: separacao dos streams em multiplos file descriptors azul: limpando rodadas incompletas cinza: tag
  • 22. Bom, mas voltando ao dump do banco, com xv fica assim: $ ( export H=localhost D=sambatech C=portal; export F=" dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)"; mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c -N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" | mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 | cut -c 2- )" | xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 ) raw: 54.7MB 0:00:38 [ 1.4MB/s] [ <=> ] xz: 7.85MB 0:00:38 [ 176kB/s] [ <=> ] rows: 32.7k 0:00:38 [ 818/s ] [=> ] 44% ETA 0:00:47 vermelho: o xz na linha do dump de bando
  • 23. Como está idle e o xv é o gargalo, o negócio é paralelizar de algum jeito top - 12:15:16 up 53 days, 13:13, 27 users, load average: 0.69, 0.24, 0.29 Tasks: 338 total, 3 running, 334 sleeping, 0 stopped, 1 zombie Cpu0 : 99.7%us, 0.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, Cpu1 : 2.3%us, 1.3%sy, 0.0%ni, 96.4%id, 0.0%wa, 0.0%hi, 0.0%si, Cpu2 : 18.3%us, 1.7%sy, 0.0%ni, 79.4%id, 0.7%wa, 0.0%hi, 0.0%si, Cpu3 : 2.0%us, 1.3%sy, 0.0%ni, 96.7%id, 0.0%wa, 0.0%hi, 0.0%si, Mem: 4107620k total, 1334364k used, 2773256k free, 24868k buffers Swap: 4192960k total, 2373236k used, 1819724k free, 259280k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 20507 zed 20 0 99420 93m 736 R 100 2.3 1:06.74 xz 20504 zed 20 0 17688 4304 2880 S 17 0.1 0:11.88 mongoexport 20506 zed 20 0 4056 780 576 S 2 0.0 0:01.45 pv 20505 zed 20 0 4056 732 576 S 0 0.0 0:00.25 pv vermelho: xv como gargalo, no lugar do mongoexport; processos single thread
  • 24. pixz ou parallel? $ git clone https://github.com/vasi/pixz.git $ cd pixz/ $ make pixz.h:1: fatal error: lzma.h: No such file or directory $ sudo apt-get install lzma-dev Setting up lzma-dev (4.43-14ubuntu2) ... $ make pixz.h:1: fatal error: lzma.h: No such file or directory $ grep lzma README * liblzma 4.999.9-beta-212 or later (from the xz distribution) vermelho: muitos poblemas para instalar um xz paralelo
  • 25. parallel? não?.. $ parallel The program 'parallel' is currently not installed. You can install it by typing: sudo apt-get install moreutils $ apt-get install moreutils ... $ parallel --help parallel: invalid option -- '-' parallel [OPTIONS] command -- arguments for each argument, run command with argument, in parallel parallel [OPTIONS] -- commands run specified commands in parallel vermelho: o parallel do ubuntu parece muito velho
  • 26. Bem vindo ao mundo real... $ wget http://ftp.gnu.org/gnu/parallel/parallel-20110822.tar.bz2 $ tar xvf parallel-20110822.tar.bz2 $ cd parallel-20110822 $ ./configure && make -j 4 $ ./src/parallel --help Usage: parallel [options] [command [arguments]] < list_of_arguments parallel [options] [command [arguments]] (::: arguments|:::: argfile(s))... cat ... | parallel --pipe [options] [command [arguments]] -j n Run n jobs in parallel -k Keep same order vermelho: parallel sendo buildado em paralelo :)
  • 27. Quantos jobs pra compressão?... $ uptime ; for j in $(seq $(( $(grep -c ^bogomips /proc/cpuinfo) * 4 )) ) ; do echo "$j: $( ( time ( mongoexport -d sambatech -c portal 2>/dev/null | head -20000 | parallel -j $j --pipe nice xz | xz -t ) ) 2>&1 | tr "n" "t" )" ; done ; uptime 14:46:08 up 53 days, ... load average: 0.07, 1.29, 2.23 1: real 0m18.759s user 0m26.602s sys 0m2.448s 2: real 0m11.673s user 0m24.746s sys 0m1.916s 3: real 0m9.033s user 0m23.553s sys 0m1.460s 4: real 0m8.561s user 0m23.093s sys 0m1.468s ... 8: real 0m7.645s user 0m23.173s sys 0m1.356s ... 16: real 0m7.087s user 0m22.153s sys 0m1.260s 14:48:27 up 53 days, ... load average: 6.49, 2.88, 2.66 vermelho: parallel configurado para fazer split de um pipe para varios xz azul: numero de jogs testados: de 1 ateh 4 vezes o numero de cores verde: o nice eh importante senao o mongoexport nao tem cpu o suficiente para alimentar todos os xz laranja: note que usei o time built-in, porque estou fazendo time de um parenteses (sub-shell)
  • 28. Achievement Unlocked $ ( export H=localhost D=sambatech C=portal; export F=" dump-$H-$D-$C-$(date +%Y%m%d_%H%M%S)"; mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c -N rows -l -s "$( echo -e "use $D;ndb.$C.find().count();" | mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 | cut -c 2- )" | parallel --pipe nice xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 ) #dump-mongo-collection raw: 48.9MB 0:00:10 [5.64MB/s] [ <=> ] xz: 6.38MB 0:00:10 [ 678kB/s] [ <=> ] rows: 29.6k 0:00:10 [3.27k/s] [==> ] 40% ETA 0:00:14 vermelho: o parallel na linha de dump do banco; o default do parallel eh um para cada core cinza: tag, pra lembrar do oneliner
  • 29. 2
  • 30. E se eu quiser mandar pra net? $ scp dump.xz example.com: dump.xz 100% 97MB 32.3MB/s 00:03
  • 31. Putz, mas e a integridade? $ ssh example.com "xz -vd > dump" < dump-*.xz (stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10 vermelho: descomprime do lado remoto para verificar integridade azul: arquivo armazenado estah descomprimido fundo amarelo: roda remoto
  • 32. Mas não queria descomprimir lá... $ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F" vermelho: tee para testar apenas azul: armazena o arquivo comprimido fundo amarelo: roda remoto
  • 33. Ueh?.. kd o output? $ F="dump.xz"; ssh example.com "tee >(xz -tv) > '$F' " < "$F" $ vermelho: xz -tv normalmente imprime na stderr fundo amarelo: roda remoto
  • 34. Aff, a stderr nao passou pelo ssh, claro... mas tudo bem. $ time ( F="dump.xz"; ssh example.com "{ tee >(xz -vt) > '$F' ; } 2>&1" < "$F" ) (stdin): 96.9 MiB / 228.5 MiB = 0.424, 22 MiB/s, 0:10 real 0m10.580s user 0m2.963s sys 0m0.243s vermelho: redirecionando a stderr para stdout antes de sair do ssh azul: xz e seu output verde: stdout dentro das chaves precisa ser preservada para o arquivo laranja: tempo total do processo fundo amarelo: roda remoto note que ssh -t nao funciona: Pseudo-terminal will not be allocated because stdin is not a terminal.
  • 35. Soh que descomprimir o xz pra validar eh overkill... que tal usar o md5sum? $ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) > (echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp - d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee >(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n "s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee '"$F"' | md5sum -c $FIFO/b | cut -c 4-' vermelho: multiplexa arquivo, seu md5 e tamanho azul: demultiplexa do outro lado verde: fifo a carrega o tamanho do arquivo e o fifo b o md5 roxo: trap para apagar os fifos quando sair, mesmo se cancelado fundo amarelo: roda remoto
  • 36. Droga, deadlock! E agora?... $ F=dump.xz; pv "$F" | tee >(printf 'size:%-10dn' $(wc -c) ) > (echo "md5:$(md5sum)") | ssh example.com 'FIFO=$(mktemp - d); mkfifo $FIFO/{a,b}; trap "rm $FIFO/[ab]; rmdir $FIFO" 0; tee >(tail -c 56 | tee >( sed -n "s/size:(.*)/1/p" >$FIFO/a ) | sed -n "s/md5:(.*)/1/p" >$FIFO/b ) | head -c $(cat <$FIFO/a) | tee '"$F"' | md5sum -c $FIFO/b | cut -c 4-' ^C vermelho: multiplexa arquivo, seu md5 e tamanho azul: demultiplexa do outro lado verde: fifo a carrega o tamanho do arquivo e o fifo b o md5 roxo: trap para apagar os fifos quando sair, mesmo se cancelado fundo amarelo: roda remoto o deadlock causado por arquivos maiores que o buffer do pipe porque os metadados estao no final (nao tem como saber o md5 no inicio), mas para comecar a gravar o arquivo no disco, e permitir que o pipe avance, preciso do size e do md5 nos fifos
  • 37. kkkk, tem um jeito bem mais simples! $ time ( F=dump.xz; pv "$F" | { tee >(md5sum 1>&2) | ssh example.com 'tee '"$F"' | md5sum' ; } 2>&1 | sort -u | wc -l | grep -qx 1 && echo OK || echo ERR ) 96.9MB 0:00:03 [28.7MB/s] [===========>] 100% OK real 0m3.212s user 0m2.373s sys 0m0.307s $ ### time ssh + xz: $ #real 0m10.580s $ #user 0m2.963s $ #sys 0m0.243s vermelho: o md5 eh rodado local (para stderr) e remoto (stdout) azul: no final verificamos se tem apenas uma variedade de arquivo laranja: o md5sum eh bem mais rapido que o xz -t fundo amarelo: roda remoto
  • 38. Jah tah bem mais leve; que tal tirar o ssh tb? $ { F="dump.xz" ; H="example.com" ; P=9999 ; pv "$F" | tee > (md5sum 1>&2) >(wc -c 1>&2) >( sed -n '' | ssh "$H" "{ netcat $(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee '$F' >(wc -c 1>&2) | md5sum ; } 2>&1" 1>&2 ) >(netcat -l $P) >/dev/null ; } 2>&1 | tee >( sort -u | wc -l | grep -qx 2 && echo OK || echo ERR ) 19405636 a84a3cd315f4ee235c5e15d82872ab5d - 19335168 85d99c783b541885b05ef263c1f35cdc - ERR azul: todo o fluxo foi controlado por um unico pipe, mas o ssh que nao deveria receber os dados teve seu input editado vermelho: deu erro na transmissao porque o pipe produtor fechou antes do netcat receptor terminar; esse erro eh intermitente verde: note que a seguranca eh fraca, se alguem estiver tentando pegar o arquivo dessa porta vai conseguir, isso nao eh pra redes desprotegidas fundo amarelo: roda remoto
  • 39. Morreu antes de terminar o envio, o receptor vai ter que autorizar o fim $ { F="dump.xz" ; H="example.com" ; P=$(printf "39%03d" $RANDOM | cut -c 1-5) ; { ssh "$H" "{ netcat $(set | grep [S] SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") $P | tee '$F' >(wc -c 1>&2) | md5sum ; } 2>&1" 1>&2 ; } | { < "$F" tee >(md5sum 1>&2) >(wc -c 1>&2) >(netcat -l $P) >/dev/null ; } ; } 2>&1 | tee >( sort -u | wc -l | grep -qx 2 && echo OK || echo ERR ) 19405636 a84a3cd315f4ee235c5e15d82872ab5d - 19405636 a84a3cd315f4ee235c5e15d82872ab5d - OK vermelho: o ssh estah dominando o pipe azul: o pipe soh morre quando o ssh morrer roxo: o produtor vem depois verde: um pouco mais de segurança por obscuridade fundo amarelo: roda remoto isso funcionou no ubuntu 10.10, mas nao funcionou no archlinux 2011- 09
  • 40. Cuidados com o netcat $ { F="dump.xz" ; H=example.com ; P=$(printf "39%03d" $RANDOM | cut -c 1-5) ; S=$(stat -c %s "$F") ; { { pv "$F" | { tee >(md5sum 1>&2) | { netcat -l -p $P 2>/dev/null || netcat -l $P ; } ; } 2>&1 ; } & } ; A=$! ; ssh $H "{ H=$(set | grep [S]SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") ; { netcat -w 5 -c $H $P 2>/dev/null || netcat -w 5 $H $P ; } | tee '$F' > (md5sum 1>&2) | pv -n -s $S 2>&1 >/dev/null | while read X ; do test " $X" -ge 100 && kill -INT $(pstree -laAp $$ | sed -n 's/^[^,]*<netcat, ([0-9][0-9]*)>.*/1/p') &>/dev/null ; done ; } 2>&1" ; kill -INT $(pstree - laAp $A | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; } | uniq | wc -l | grep -qx 1 && echo OK || echo ERR #netcat-send 537MB 0:00:04 [ 110MB/s] [=================>] 100% OK vermelho: por causa das inconsistencias de terminacao do netcat, tive que monitorar o progresso da transmissao e matar explicitamente o netcat azul: alternativas de parametros do netcat em sistemas diferentes verde: melhoria de tempo a inversao do consumidor com o produtor acrescentou um race- condition, o ssh podia tentar conectar no netcat local antes dele existir (teoricamente) e pior, em alguns sistemas o netcat precisa de um "-p" alem do "-l" e, pior, algumas versões não fecham, sem o "-c", quando o pipe fecha, o que faz o script anterior travar. nesse ultimo caso o ideal é usar a mesma técnica do mongoexport; por em background e matar com pstree requisitos: 1) netcat escutando tem que ser criado primeiro; 2) logo o netcat do ssh tem que conectar depois,
  • 41. 3) ateh porque ele que tem a autoridade de terminar pq o receptor terminar depois. 4) foi preciso fazer fallback entre os dois estilos de netcat; 5) soh o netcat que conecta fecha consistentemente com o pipe, mas, como o ssh precisa conectar ./ fechar e ele nao tem o arquivo, foi preciso monitorar a transmissao com pv e matar explicitamente o netcat melhor ter um netcat confiavel, mas se nao foi possivel o slide apresenta um contorno para o problema
  • 42. Mas serah que houve vantagem? $ time !?#netcat-send 537MB 0:00:04 [ 110MB/s] [============>] 100% real 0m5.298s user 0m0.063s sys 0m0.003s OK $ time !?scp dump.xz 100% 537MB 35.8MB/s 00:15 real 0m14.658s user 0m11.123s sys 0m1.130s vermelho: tempos do netcat e do ssh
  • 43. 3
  • 44. E se forem vários arquivos? $ find dumps/ -type f -printf "%10s %pn" 101632892 dumps/b/dump.xz.2 65536 dumps/b/a/dump.xz.1 0 dumps/a/3688 0 dumps/a/8958 (... total de 10000 arquivos no dumps/a/) 0 dumps/a/4308 0 dumps/a/3338 $ scp -r dumps/* example.com:dump-backup/ (...) 3338 100% 0 0.0KB/s 00:00 dump.xz.2 100% 97MB 48.5MB/s 00:02 dump.xz.1 100% 64KB 64.0KB/s 00:00 vermelho: scp recursivo
  • 45. Mas quero selecionar melhor $ tar c -C dumps/ --exclude=./a . | ssh example.com "tar xv -C dump-backup/" ./ ./b/ ./b/dump.xz.2 ./b/a/ ./b/a/dump.xz.1 vermelho: tarpipe azul: um lista menor foi selecionada
  • 46. Progresso... $ TAR="tar c -C dumps/ --exclude=./a ." ; $TAR | pv -s $($TAR | wc -c) | ssh example.com "tar x -C dump-backup/" 97MB 0:00:02 [32.8MB/s] [================>] 100% vermelho: pv mostrando o progresso a partir de uma rodada previa do mesmo tar
  • 47. Não rola fazer o tar duas vezes... $ S="dumps" ; D="dump-backup" ; X="--exclude=a" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du -- apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' " 96.9MB 0:00:02 [33.5MB/s] [==============>] 100% $ S="dumps" ; D="dump-backup" ; X="" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --apparent-size --block- size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' " 102MB 0:00:03 [33.7MB/s] [================] 104% vermelho: du do tamanho dos arquivos, isso eh impreciso quando tem arquivos pequenos no meio azul: filtros para um arquivo grande e um arquivo grande com muitos pequenos, respectivamente
  • 48. Mas assim tem muita imprecisão... :/ $ S="dumps" ; D="dump-backup" ; X="--exclude=./a --exclude=. /b/d*" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du --apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' " 70kB 0:00:00 [ 544kB/s] [====> ] 29% $ S="dumps" ; D="dump-backup" ; X="--exclude=./b/d*" ; H=example.com ; tar c -C "$S" $X . | pv -s $(cd "$S" && du -- apparent-size --block-size=1 $X -s . | cut -f 1) | ssh $H "tar x -C '$D' " 4.95MB 0:00:00 [7.77MB/s] [================] 1267% vermelho: du do tamanho dos arquivos, isso eh impreciso especialmente se tiverem varios arquivos pequenos, por causa do overhead do tar, mas quebra um galho. azul: filtros para um arquivo muito pequeno e muitos arquivos pequenos, respectivamente
  • 49. Vou tentar incorporar o overhead do tar na conta do tamanho total $ for X in "('./a/*' './b/a/*')" "()" "('./a/*' './b/d*')" "('./b/d*')" ; do eval "X=$X" ; E=""; N=""; for x in "${X[@]}" ; do test -n "$N" && N="$N -and" ; N="$N -not -path '$x'" ; E="$E --exclude='$x'" ; done ; echo "${X[@]} => $E => $N"; S="dumps" ; D="dump-backup" ; H=example.com ; eval "tar c -C '$S' $E ." | pv -s $(cd "$S" && eval "find . $N -printf '%y %s % pn'" | awk 'BEGIN { sum=0 } { s=length($0)-length($2)-1+($1 ~ /f/ ? $2 : 0) ; c=512; r=s%c; s=s+(r?c-r:0) ; sum+=s } END { chunk=10240; remain=sum%chunk; print sum+(remain?chunk-remain:0) }') | ssh $H "tar x -C '$D' " ; done #tar-pipe-ssh 96.9MB 0:00:02 [34.7MB/s] [====================>] 100% 102MB 0:00:02 [41.5MB/s] [====================>] 100% 70kB 0:00:00 [ 835kB/s] [====================>] 100% 4.95MB 0:00:00 [21.2MB/s] [====================>] 100% vermelho: find + awk calculando, respectivamente os arquivos da mesma maneira que o tar vai fazer azul: todas as configurações de exceção mostradas antes passo pelos arquivos duas vezes, mas isso tem uma certa vantagem, que a primeira eh soh pela listagem de diretorios (curta e que deve ficar cacheada depois dessa passada) e a segunda pelo conteudo dos arquivos.. que ficam em lugares bem diferentes do disco, entao possivelmente fazendo isso estou evitando muito io-wait em seek
  • 50. 0
  • 51. Não tentem isso em casa Nesse fantástico mundo dos one-liners se pode usar o history como repositório de comandos; basta comentá-los com tags memoráveis e deixar o history bem, bem grande: $ export HISTSIZE=999999999 HISTFILESIZE=999999999 $ echo !! >> $(ls -S ~/.{,bash}{rc,{,_}profile} 2>/dev/null | head -1) $ ls -ltr #lista-recentes ... $ !?#lista-re ls -ltr #lista-recentes ...
  • 52. Referência GNU bash, version 4.1.5(1)-release (i686-pc-linux-gnu) Copyright (C) 2009 Free Software Foundation, Inc. (*) GNU bash, version 4.2.10(2)-release (i686-pc-linux-gnu) Copyright (C) 2011 Free Software Foundation, Inc. (+) License GPLv3+: GNU GPL version 3 or later <http://gnu. org/licenses/gpl.html> This is free software; you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. (*) Ubuntu 10.10 (+) Arch Linux 2011-09
  • 54. Obrigado ( export H=localhost D=sambatech C=portal; export F="dump-$H-$D-$C-$(date +%Y%m%d_%H%M% S)"; mongoexport -h $H -d $D -c $C 2> $F.log | pv -c -N raw | pv -c -N rows -l -s "$( echo -e "use $D; ndb.$C.find().count();" | mongo $H 2>> $F.log | grep "switched to db $D" -A1 | tail -1 | cut -c 2- )" | parallel --pipe nice xz | pv -c -N xz | tee $F.xz | md5sum > $F.md5 ) +++++++++ { F="dump.xz" ; H=example.com ; P=$(printf "39%03d" $RANDOM | cut -c 1-5) ; S=$(stat -c %s "$F") ; { { pv "$F" | { tee >(md5sum 1>&2) | { netcat -l -p $P 2>/dev/null || netcat -l $P ; } ; } 2>&1 ; } & } ; A=$! ; ssh $H "{ H=$(set | grep [S] SH_CLIENT | sed "s/.*'([^ ]*) .*/1/") ; { netcat -w 5 -c $H $P 2>/dev/null || netcat -w 5 $H $P ; } | tee '$F' >(md5sum 1>&2) | pv -n -s $S 2>&1 >/dev/null | while read X ; do test "$X" -ge 100 && kill -INT $(pstree -laAp $$ | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; done ; } 2>&1" ; kill -INT $(pstree -laAp $A | sed -n 's/^[^,]*<netcat,([0-9][0-9]*)>.*/1/p') &>/dev/null ; } | uniq | wc -l | grep -qx 1 && echo OK || echo ERR +++++++++ for X in "('./a/*' './b/a/*')" "()" "('./a/*' './b/d*')" "('./b/d*')" ; do eval "X=$X" ; E=""; N=""; for x in "${X[@]}" ; do test -n "$N" && N="$N -and" ; N="$N -not -path '$x'" ; E="$E -- exclude='$x'" ; done ; echo "${X[@]} => $E => $N"; S="dumps" ; D="dump-backup" ; H=example.com ; eval "tar c -C '$S' $E ." | pv -s $(cd "$S" && eval "find . $N -printf '%y %s %pn'" | awk 'BEGIN { sum=0 } { s=length($0)-length($2)-1+($1 ~ /f/ ? $2 : 0) ; c=512; r=s%c; s=s+(r?c-r:0) ; sum+=s } END { chunk=10240; remain=sum%chunk; print sum+(remain?chunk-remain:0) }') | ssh $H "tar x -C '$D' " ; done http://bash.org/ http://tldp.org/LDP/abs/ TODO linha que pega uma lista de arquivos do banco, seleciona quais não estão no destino e envia apenas esses por tar | netcat