vladignatyev / majorka

0 stars 0 forks source link

proc Multiprocess usage #22

Open vladignatyev opened 5 years ago

vladignatyev commented 5 years ago

Не знаю куда сохранить. Просто пример использования класса Multiprocess и LogTrap из proc.

Мне был нужен класс/метод, позволяющий запустить параллельно серверные процессы (redis-server, majorka). Я хотел заюзать его в TestCase'е для «большого теста».

На практике оказалось, что: 1) если по-тупому использовать subprocess, то легко поймать зомби-процессы 2) сложно реализовать error handling этих процессов с subprocess 3) чтение из STDERR/STDOUT процесса блокирует основной процесс в Python.

Multiprocess позволяет запустить в фоне процессы, отследить их смерть если нужно и получить постмортэм логи. LogTrap позволяет читать логи процесса из STDERR/STDOUT без блокирования основного процесса с помощью запуска отдельной нити, которая может блокироваться.

Я заюзал psutil, которая даёт высокоуровневые абстракции и обертку над системными методами для процессов.

Внизу пример скрипта, который запускает redis-server и majorka в фоне, выводит дерево процессов, логирует происходящее и позволяет в интерактивном режиме выводить логи из каждого из под-процессов. В случае смерти одного из субпроцессов, в терминал выводится лог и осуществляется зачистка — убиваются остальные процессы, а в случае зависания им посылается SIGTERM, если в течение таймаута их не удаётся убить, в лог показывается сообщение с PID зомби-процессов.

import logging

from proc import Multiprocess
from proc.pipelog import LogTrap

logging.basicConfig(format='\33[92m[%(name)s] \33[0m\33[90m%(asctime)-15s\33[1m\33[37m %(message)s\33[0m')
logger = logging.getLogger(Multiprocess.LOGGER)
logger.setLevel(logging.DEBUG)

servers = (
    ('Redis Server', ['redis-server', '--port', '6399']),
    ('Majorka Server', ['../core/target/debug/majorka', '--redis', 'redis://localhost:6399', '--port', '8008', '--log', 'debug'])
)

with Multiprocess(logger=logger, proc=servers) as session:
    session.launch()

    redis_log_reader = session.proc_loggers['Redis Server'].get_reader()
    majorka_log_reader = session.proc_loggers['Majorka Server'].get_reader()

    while True: # main loop
        died = session.poll()
        if died is not None:
            break

        yn = raw_input("Kill them all and exit?\n")
        if yn.strip().lower() == 'y':
            break
        else:
            redis_logs = redis_log_reader.read()
            if redis_logs:
                logger.info("Redis log updated:\n\n\t" + '\t'.join(redis_logs))

            majorka_logs = majorka_log_reader.read()
            if majorka_logs:
                logger.info("Majorka log updated:\n\n\t" + '\t'.join(majorka_logs))
vladignatyev commented 5 years ago

Пример вывода

image

varuzhnikov commented 5 years ago

Мне кажется, что это велик. У supervisord вроде есть API http://supervisord.org/index.html

varuzhnikov commented 5 years ago

он как раз на python написан

vladignatyev commented 5 years ago

Глянул документацию по supervisord. Он реально монструозен. Как минимум из-за клиент/серверной архитектуры и RPC API.

То есть, supervisord из коробки ну прямо вообще нихуяшечки не предоставляет даже близко того, что надо. Из-за того, что у него нет возможности его использования как библиотеки. Он — сервер, который как-то надо запускать, как-то с ним коммуницировать и так далее.

RPC API это интерфейс для взаимодействия с этим самым сервером.

Получается, что воткнув supervisord я бы столкнулся с той же проблемой — мне в тесте нужно запускать параллельный процесс. Окей, пусть это будет супервайзерд. Но тогда мне нужно как-то выгребать из него логи и знать когда он упал, а когда закончились тесты, гарантированно прибить дочерние процессы. Вот эти штуки и решает Multiprocess.

Просто код в proc посмотри! Только ради этого тащить такие глобальные опердени как supervisord как-то чересчур!

Необходимо более легковесное решение. Пока psutil даже слишком тяжелое решение, хотя это просто враппер над subprocess из стандартной библиотеки + кучи неиспользуемого (но потенциально полезного стафа). Например, с помощью psutil тест проверяющий использование памяти системой может быть написан в пару строк буквально + это API сейчас открыто для пользователей proc.Multiprocess.

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

Настораживает только то, что subprocess, на котором построен щас мой код, находится в стандартной библиотеке Python'а. А все эти psutil и supervisord это уже охеренного размера библиотеки/фреймворки/сервера. Открутить psutil я точно могу.

vladignatyev commented 5 years ago

Так что пусть лучше это полежит здесь и подождёт часа когда мы это заменим на нормальное решение.