2010-11-11 11 views
19

Tak więc piszę aplikację do wąchania pakietów. Zasadniczo chciałem go powąchać dla sesji tcp, a następnie przeanalizować je, aby sprawdzić, czy są one http, a jeśli są, i czy mają odpowiedni typ zawartości, itp., Zapisz je jako plik na moim dysku twardym.Attoparsec przydziela masę pamięci na dużą rozmowę "weź"

W tym celu chciałem, aby był wydajny. Ponieważ obecna biblioteka http jest oparta na łańcuchach i będę zajmować się dużymi plikami, a ja naprawdę potrzebowałem tylko parsować odpowiedzi HTTP, postanowiłem przetasować własne w attoparsec.

Kiedy skończyłem program, znalazłem, że kiedy analizowałem 9 megabajtową odpowiedź http z plikiem wav, kiedy profilowałem to przydzielanie gig pamięci podczas próby sparsowania ciała odpowiedzi http. Kiedy patrzę na HTTP.prof widzę pewne linie:

 
httpBody    Main             362   1 0.0 0.0 93.8 99.3 

take     Data.Attoparsec.Internal        366  1201 0.0 0.0 93.8 99.3 
    takeWith   Data.Attoparsec.Internal        367  3603 0.0 0.0 93.8 99.3 
     demandInput  Data.Attoparsec.Internal        375   293 0.0 0.0 93.8 99.2 
     prompt   Data.Attoparsec.Internal        378   293 0.0 0.0 93.8 99.2 
     +++    Data.Attoparsec.Internal        380   586 93.8 99.2 93.8 99.2 

tak widać, gdzieś w httpbody, wziąć nazywa się 1201 razy, powodując ponad 500 (+++) powiązań, o bytestrings, co powoduje absurdalna ilość przydziału pamięci.

Oto kod. N to tylko długość treści odpowiedzi http, jeśli taka istnieje. Jeśli go nie ma, po prostu próbuje zabrać wszystko.

Chciałem, aby zwrócił on leniwy test składający się z 1000 lub więcej znaków bytowych, ale nawet jeśli zmienię go tak, by po prostu wziął n i zwrócił surowy test, wciąż ma w sobie te alokacje (i wykorzystuje 14 gig pamięci).


httpBody n = do 
    x <- if n > 0 
    then AC.take n 
    else AC.takeWhile (\_ -> True) 
    if B.length x == 0 
    then return Nothing 
    else return (Just x) 

Czytałem bloga przez faceta, który był kombinatorem i miał ten sam problem, ale nigdy nie słyszałem o rezolucji. Czy ktokolwiek kiedykolwiek napotkał ten problem wcześniej lub znalazł rozwiązanie?

Edycja: OK, cóż, zostawiłem to cały dzień i nic nie dostałem. Po zbadaniu problemu nie sądzę, że istnieje sposób, aby to zrobić bez dodawania leniwego bytestringa do attoparsec. Spojrzałem też na wszystkie inne biblioteki i albo brakowało im świadectw testowych, albo innych rzeczy.

Więc znalazłem obejście. Jeśli myślisz o żądaniu http, to nagłówek, nowa linia, nowa linia, treść. Ponieważ ciało jest ostatnie, a parsowanie zwraca krotkę zarówno z tego, co przeanalizowałeś, jak i tego, co pozostało z analizy, mogę pominąć analizowanie ciała wewnątrz attoparsec i zamiast tego wyskubać ciało z pominiętego testu bytowego.


parseHTTPs bs = if P.length results == 0 
    then Nothing 
    else Just results 
    where results = foldParse(bs, []) 

foldParse (bs,rs) = case ACL.parse httpResponse bs of 
    ACL.Done rest r -> addBody (rest,rs) r 
    otherwise -> rs 

addBody (rest,rs) http = foldParse (rest', rs') 
    where 
    contentlength = ((read . BU.toString) (maybe "0" id (hdrContentLength (rspHeaders http)))) 
    rest' = BL.drop contentlength rest 
    rs' = rs ++ [http { rspBody = body' }] 
    body' 
     | contentlength == 0 = Just rest 
     | BL.length rest == 0 = Nothing 
     | otherwise   = Just (BL.take contentlength rest) 
httpResponse = do 
    (code, desc) <- statusLine 
    hdrs <- many header 
    endOfLine 
-- body <- httpBody ((read . BU.toString) (maybe "0" id (hdrContentLength parsedHeaders))) 

    return Response { rspCode = code, rspReason = desc, rspHeaders = parseHeaders hdrs, rspBody = undefined } 

Jest trochę brudny, ale ostatecznie działa szybko i nie przydziela niczego więcej, niż chciałem. Zasadniczo więc spasujesz na podstawie zbierania danych, zbierając struktury danych http, a następnie pomiędzy kolekcjami, sprawdzam długość zawartości struktury, którą właśnie dostałem, odejmuję odpowiednią ilość z pozostałego testu bytestring, a następnie kontynuuję, jeśli istnieje jakaś próba z lewej strony.

Edytuj: Skończyłem ten projekt. Działa jak marzenie. Nie jestem odpowiednio kabalizowany, ale jeśli ktoś chce zobaczyć całe źródło, możesz go znaleźć pod adresem https://github.com/onmach/Audio-Sniffer.

+0

Co to jest AC? (wypełniacz) –

+0

Czy używasz [defragmentacji operacji blaze-buildera] (http://lambda-view.blogspot.com/2010/11/defragmenting-lazy-bytestrings.html) to naprawia? –

+0

AC jest kwalifikowanym importem dla Data.Attoparsec.Char8 –

Odpowiedz

5

combinatorrent facet tutaj :)

Jeśli mnie pamięć nie myli, problem z attoparsec jest to, że wymaga wejście A trochę w czasie, budowanie leniwe bytestring który ostatecznie łączone. Moje "rozwiązanie" polegało na samodzielnym uruchomieniu funkcji wprowadzania danych. Oznacza to, że otrzymuję strumień wejściowy dla attoparsec z gniazda sieciowego i wiem, ile bajtów można się spodziewać w wiadomości.Zasadniczo dzieli się na dwa przypadki:

  • Przesłanie jest mały: Czytaj do 4k z gniazda i jeść Bytestring trochę naraz (plastry bytestrings są szybkie i wyrzucić 4k po został wyczerpany).

  • Wiadomość jest "duża" (duża oznacza tutaj około 16 kilobajtów w bittorrent): obliczamy, ile może mieć porcja 4k, a następnie po prostu żądamy, aby podstawowe gniazdo sieciowe było wypełnione. teraz masz dwa bytestrings, pozostałą część kawałka 4k i dużą porcję. Mają wszystkie dane, więc łączenie ich i analizowanie ich jest tym, co robimy.

    Być może uda się zoptymalizować etap konkatenacji.

Wersja TL; DR: Obsługuję ją poza attoparsec i przenoszę pętlę, aby uniknąć problemu.

Odpowiedni combinatorrent popełnić jest fc131fe24 patrz

https://github.com/jlouis/combinatorrent/commit/fc131fe24207909dd980c674aae6aaba27b966d4

o szczegóły.

Powiązane problemy