Улучшенный TCP/IP proxy на Erlang'e

Писал я про мое освоение Эрганга через написание программы для перехвата и удобного логирования TCP/IP соединений.

B итоге я окончательно допилил программу, и теперь она заменила мне версию на Питоне.

Что программа умеет особенно удобного (как мне кажется):

  • удобный вид лога, в котором отображается шестнадцатеричный дамп, и символьного представление для видимых кодов
  • в дампе отображается номер соединения (в случае смешивания выводов нескольких параллельных соединений)
  • для каждого соединения вычисляется длительность
  • ведутся дополнительные двоичные логи для каждой из сторон в соединении (для повторного “проигрывания” данных)

Про Эрланг. Меня начинает реально вставлять. Я почувствовал (для многих это и не новость), что тут можно написать что-то реальное, особенно связаное с сетью и многозадачностью.

Из насущных проблем:

  • Пока нет чувства разумного дробления на модули и даже функции. При общей тотальной иммутабельности сложно что-то напортачить, но когда количество функций разрастается, хочется их как-то группировать.
  • Нет чувства правильного форматирования кода. Вроде как 80-ти символьные строки и пробелы вместо табуляций меня пока никогда не подводили, но при функциональном стиле кода часто получаются длинные “лесенки”.

Пузомерка. Я сделал тест на прокач шестидесятимегового файла через питоновскую и эрланговскую версию. Результаты интересные.

Кач напрямую:

curl http://www.erlang.org/download/otp_src_R14B04.tar.gz >direct

Через Питон:

Window 1: python pyspy.py -l 50000 -a www.erlang.org -p 80 -L log

Window 2: curl http://localhost:50000/download/otp_src_R14B04.tar.gz >via-proxy-python

Через Эрланг:

Window 1: escript tcp_proxy.erl 50000 www.erlang.org 80

Window 2: curl http://localhost:50000/download/otp_src_R14B04.tar.gz >via-proxy-erlang

Файл напрямую качается, условно, минуту. Питоновская версия прокачала файл за шесть минут при включенном логе на экран и файл. Причем сброс лога и непосредственно прокач заканчивались приблизительно в одно время (данные задачи выполняются параллельно, общаясь через очередь, и технически не обязаны завершаться одновременно, так как очередь надо выгрести).

На Эрланге картина иная. Файл прокачался практически за то же время, что и напрямую! Но вот полного сброса лога я так и дождался. Через шесть минут он успел сбросить где-то 10% лога.

Выводы: Видимо, поведение питоновской версии обусловлено тем, что поток лога и потока-качалка работаются примерно с одной скоростью, поэтому в среднем очеред обмена постоянно выгребается. Фактически, скорость программы ограничена пропускной способностью потока логирования, но так как визуально не видно, что поток качания заканчиватся значительно раньше, то можно предположить, что он работается примерно с такой же скоростью (напомню, ~6 минут).

На Эрланге же качалка работает, как мне показалось, очень быстро. Данные перекачиваются и параллельно загоняются в очередь на логирование. А вот производительность логирования оставляет желать лучшего. Ради эксперимента я закомментировал вызов функции создания шестнадцатеричного дампа, и время сброса лога также упало до минуты. Поэтому, как мне кажется, корень зла в моей кривой работе со строками и списками при создании дампа (возможно что-то где-то постоянно копируются, а в мире рекурсии и изменения данных только через копирование ошибки подобного рода дорого отражаются на производильности). А вот работа с сокетами и посылкой/приемом сообщений между потоками в Эрланге очень эффективная.

Я вообще заметил, что в Эрланге ты подсознательно начинашь писать многопотоковые программы. Например, тут в принципе нет глобальных объектов. И допустим, у тебя есть флаг, глобальная установка, которую хочется иметь везде. Так как глобально ее объявить нельзя, приходится таскать как параметр функций там и сям. А как вариант “навязанного конструктивного мышления”, думаешь - а давай-ка я запущу этот кусок как поток и буду вызывать его функционал через посылку сообщений. В этом случае я могу передать мне нужный параметр один раз в начале при создании потока, тем самым сделав его типа глобальным для этого потока.

Наверное пример вышел немного скомканным, но общая идея такова - так как ты обязан передавать в функцию все ее параметры каждый раз, то начинаешь думать об максимальной независимости и дроблении функционала, что как следствие, позволяет запускать их в разных потоках.

Для интересующихся - исходник доступен.

Посты по теме:


Оригинальный пост | Disclaimer

Комментарии