Итак, я пишу приложение для анализа пакетов. По сути, я хотел, чтобы он прослушивал tcp-сессии, а затем анализировал их, чтобы узнать, являются ли они http, и если они имеют правильный тип содержимого и т. Д., Сохраните их в виде файла на моем жестком диске.
Итак, с этой целью я хотел, чтобы это было эффективно. Поскольку текущая http-библиотека основана на строках, и я буду иметь дело с большими файлами, а мне действительно нужно было только проанализировать http-ответы, я решил свернуть свои собственные в attoparsec.
Когда я закончил свою программу, я обнаружил, что когда я анализировал http-ответ 9 мегабайт с файлом wav в нем, когда я его профилировал, он выделял гигабайт памяти, когда пытался проанализировать тело http ответ. Когда я смотрю на HTTP.prof, я вижу несколько строк:
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
Так что, как вы можете видеть, где-то внутри httpbody, take вызывается 1201 раз, вызывая 500+ (+++) конкатенаций байтовых строк, что вызывает абсурдное выделение памяти.
Вот код. N - это длина содержимого HTTP-ответа, если он есть. Если его нет, он просто пытается взять все.
Я хотел, чтобы он возвращал ленивую байтовую строку из 1000 или около того символьных байтовых строк, но даже если я изменил его на просто взять n и вернуть строгую байтовую строку, он все еще имеет эти выделения (и он использует 14 гигабайт памяти) .
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)
Я читал блог парня, который делал комбинаторрент, и у него была такая же проблема, но я никогда не слышал о решении. Кто-нибудь когда-нибудь сталкивался с этой проблемой раньше или нашел решение?
Редактировать: Хорошо, я оставил это весь день и ничего не получил. После изучения проблемы я не думаю, что есть способ сделать это без добавления ленивого метода доступа к быстрым цепям в attoparsec. Я также просмотрел все остальные библиотеки, и в них отсутствовали строки или другие вещи.
Итак, я нашел обходной путь. Если вы думаете о HTTP-запросе, то это заголовки, новая строка, новая строка, тело. Так как тело является последним, и синтаксический анализ возвращает кортеж как с тем, что вы проанализировали, так и с тем, что осталось от тестовой строки, я могу пропустить синтаксический анализ тела внутри attoparsec и вместо этого извлечь тело прямо из оставшейся тестовой строки.
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 }
Это немного грязно, но в конечном итоге работает быстро и не выделяет ничего больше, чем я хотел. Таким образом, в основном вы сворачиваете всю строку, собирающую структуры данных http, затем между коллекциями я проверяю длину содержимого только что полученной структуры, вытягиваю соответствующее количество из оставшейся строки байтов и затем продолжаю, если остается какая-либо строка байтов.
Редактировать: Я фактически закончил этот проект. Работает как шарм. У меня нет кабализации, но если кто-то захочет просмотреть весь источник, вы можете найти его в https://github.com/onmach/Audio-Sniffer.