poniedziałek, 24 maja 2010

rwące potoki

Każdy elementarz UNIX-a opisuje mechanizm potoków. Przypomnijmy, że potokiem łączymy 2 strumienie: standardowe wyjście stdout jednego programu ze standardowym wejściem stdin drugiego. Standardowe wyjście błędów stderr domyślnie nie trafia do potoku, co można zaobserwować w niektórych programach robiąc program --help | more i klnąc, że 3 strony pomocy przeleciały nam przez ekran niepowstrzymane. ;)

Istnieje kilka narzędzi, które można wykorzystać do zmierzenia i wizualizacji wolumenu danych płynących potokiem, a nawet do ograniczenia natężenia:
Są one często dostępne w standardowym repozytorium pakietów oprogramowania.

Przy ich pomocy można "zobaczyć", jak szybko przetwarzane są dane np. podczas obliczania sumy kontrolnej pliku. Można też zobaczyć na bieżąco, jak dane na wejściu kompresują się po przejściu przez archiwizer. Można wreszcie spowolnić przesyłanie danych, aby zmniejszyć wykorzystanie zasobów systemu (np. sieci, jeśli w potoku uczestniczy program sieciowy) lub... po prostu wyświetlać animację w ASCII. ;)

Przykłady? Dla testu wyślę do potoku dane pobrane dd z generatora liczb pseudolosowych urandom (o nieco niższej jakości, ale za to nieblokującego po wyczerpaniu entropii). Następnie spróbuję ograniczyć prędkość, z jaką pobieram pseudolosowe dane.


$ dd if=/dev/urandom bs=1M count=10 | pipebench > /dev/null
10+0 records in  9.37 MB    6.25 MB/second (Sun May 23 13:24:40 2010)
10+0 records out
10485760 bytes (10 MB) copied, 1,52052 s, 6,9 MB/s
Summary:
Piped   10.00 MB in 00h00m01.52s:    6.55 MB/second



$ dd if=/dev/urandom bs=512k count=1 | cpipe -vr -vt -vw -ngr -s 100 > /dev/null
in:  96.327ms at    1.3MB/s (   1.3MB/s avg)  128.0kB
out:   0.002ms at   61.0GB/s (  61.0GB/s avg)  128.0kB
thru: 1256.614ms at  101.9kB/s ( 101.9kB/s avg)  128.0kB
in:   0.266ms at  469.9MB/s (   2.6MB/s avg)  256.0kB
out:   0.002ms at   61.0GB/s (  61.0GB/s avg)  256.0kB
thru: 1257.576ms at  101.8kB/s ( 101.8kB/s avg)  256.0kB
in:   0.220ms at  568.2MB/s (   3.9MB/s avg)  384.0kB
out:   0.003ms at   40.7GB/s (  52.3GB/s avg)  384.0kB
thru: 1260.403ms at  101.6kB/s ( 101.7kB/s avg)  384.0kB
1+0 records in
1+0 records out
524288 bytes (524 kB) copied, 3,77087 s, 139 kB/s
in:   0.985ms at  126.9MB/s (   5.1MB/s avg)  512.0kB
out:   0.003ms at   40.7GB/s (  48.8GB/s avg)  512.0kB
thru: 1262.864ms at  101.4kB/s ( 101.6kB/s avg)  512.0kB
in:   0.005ms at       0B/s (   5.1MB/s avg)  512.0kB   (bsize=0)
out:   0.001ms at       0B/s (  44.4GB/s avg)  512.0kB
thru:   0.055ms at       0B/s ( 101.6kB/s avg)  512.0kB



$ dd if=/dev/urandom bs=1M count=1 | pv -L 100K > /dev/null
90kB 0:00:03 [ 101kB/s] [ <=> ]


Potoki mogą też być nazwane - widnieją wówczas w systemie plików jako plik typu FIFO i możemy do nich zapisywać dane jak do każdego innego pliku. Różnica jest taka, że zapis będzie blokujący tzn. będzie czekał, aż "z drugiej strony" do potoku nie podłączy się konsument.


$ mkfifo pipe1 pipe2
$ ls -las pipe*
0 prw-r--r-- 1 blog blog 0 maj 24 22:33 pipe1
0 prw-r--r-- 1 blog blog 0 maj 24 22:33 pipe2



$ echo "zapis do potoku w tle" > pipe1 &
[1] 2834
$ cat pipe1
zapis do potoku w tle
[1]+  Done                    echo "zapis do potoku w tle" > pipe1

$ cat pipe1 &
[1] 2836
$ echo "zapis do potoku czytanego w tle" > pipe1
zapis do potoku czytanego w tle
[1]+  Done                    cat pipe1


Tak uzbrojeni możemy sięgnąć po narzędzie do rozszczepiania potoku, czyli tee. tee przepuszcza swoje wejście na wyjście, ale robi kopię danych i wysyła do wskazanego pliku.


$ echo DANE TESTOWE | tee kopia-strumienia
DANE TESTOWE
$ cat kopia-strumienia
DANE TESTOWE


Przy pomocy tee można np. kompresować plik i jednocześnie liczyć sobie sumę kontrolną pliku wynikowego "w trakcie".

W szczególności plik podany tee może być potokiem nazwanym, czyli strumień trafi do 2 potoków jednocześnie.


$ echo DANE TESTOWE | tee pipe1 | sed s/DANE/POTOCZYSCIE/ &
[1] 2932
$ cat pipe1
POTOCZYSCIE TESTOWE
DANE TESTOWE
[1]+  Done                    echo DANE TESTOWE | tee pipe1 | sed s/DANE/POTOCZYSCIE/


Świetnie, a czy da się np. zamienić miejscami strumień stdout i stderr? Albo czy da się otworzyć więcej niż te dwa standardowe strumienie? Oczywiście. :)

Dla przypomnienia: proces ma listę otwartych deskryptorów. Standardowo mamy otwarte:
  • deskryptor 0, czyli stdin
  • deskryptor 1, czyli stdout
  • deskryptor 2, czyli stderr
Możemy otworzyć kolejne. W przypadku powłoki bash, robimy to np. następująco (do czytania):


$ echo DANE > myfile
$ cat <&3
DANE


No to zamieńmy miejscami stdout i stderr:


$ echo DANE | grep -v DANE
$ echo DANE 3>&1 1>&2 2>&3 | grep -v DANE
DANE


A jeśli naotwieramy więcej deskryptorów, to czy da się jakoś operować więcej niż na parze z nich? Z pomocą przychodzi narzędzie multitee.


$ echo DANE1>plik1
$ echo DANE2>plik2
$ echo DANE3>plik3
$ exec 3wyjscie1
$ exec 7>wyjscie2
$ exec 8>>wyjscie3
$ echo WEJSCIOWE | multitee 0:6,7,8 3:8 4:8 5:6
$ cat wyjscie1
DANE3
WEJSCIOWE
$ cat wyjscie2
WEJSCIOWE
$ cat wyjscie3
DANE2
DANE1
WEJSCIOWE



Na koniec pozostaje pozamykać deskryptory:


$ exec 3>&-
itd.

Brak komentarzy:

Prześlij komentarz