Получить линейный вывод из канала в режиме реального времени - PullRequest
0 голосов
/ 18 марта 2019

Я хочу читать построчно вывод подпроцесса tcpdump в (почти) режиме реального времени, но мне нужна опция, чтобы оценить, пуст ли канал (следовательно, очередь). Поток ожидает 0,5 секунды, получает все выходные строки в очереди, обрабатывает их (например, означает распределение пакетов за 0,5 секунды) и возвращает что-то.

Минимальный нерабочий пример:

millis = lambda: int(round(time.time() * 1000))
def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        print(millis())
        print(line)
        queue.put(line)
    out.close()

def infiniteloop1():
    p = Popen( [ 'sudo', 'tcpdump', '-i', 'wlan0', '-nn', '-s0', '-q', '-l', '-p', '-S' ], stdout=subprocess.PIPE, stderr=STDOUT)
    q = Queue()
    t = Thread(target=enqueue_output, args=(p.stdout, q))
    t.daemon = True # thread dies with the program
    t.start()

    while True:
        while True:
            # read line without blocking
            try: 
                row = q.get_nowait() # or q.get(timeout=.1)
            except Empty:
                print('empty')
                break
            else:
                pass
        time.sleep(0.5)
thread1 = threading.Thread(target=infiniteloop1)
thread1.daemon = True
thread1.start()

Вывод при захвате непрерывного потока пакетов:

[...]
1552905183422
10:33:03.334167 IP 192.168.1.2.36189 > a.b.c.d.443: tcp 437
1552905183422
10:33:03.357215 IP a.b.c.d.443 > 192.168.1.2.36189: tcp 0
1552905183423
10:33:03.385145 IP 192.168.1.2.36189 > a.b.c.d.443: tcp 437
empty
empty
1552905184438
10:33:03.408408 IP a.b.c.d.443 > 192.168.1.2.36189: tcp 0
1552905184439
10:33:03.428045 IP 192.168.1.2.36189 > a.b.c.d.443: tcp 437
1552905184439
10:33:03.451235 IP a.b.c.d.443 > 192.168.1.2.36189: tcp 0
[...]

Обратите внимание на два последовательных "пустых". Последний пакет перед первым «пустым» был захвачен tcpdump 10: 33: 03.385145 и доставлен в очередь в 1552905183423, что заняло 38 мс. Между двумя "пустыми" пакетами не доставляются в очередь. Первый пакет после второго «пустого» был захвачен в 10: 33: 03.408408 и доставлен 1552905184438, он был доставлен через 1 секунду после предыдущего пакета, но перехвачен между «emptys». Почему это не доставляется между "emptys"? Такое случается не часто, но каждый второй всплеск очереди приводит к тому, что пакеты не доставляются, почему?

1 Ответ

1 голос
/ 18 марта 2019

Первый пакет после второго "пустого" был захвачен в 10: 33: 03.408408 и доставлено 1552905184438, доставлено 1 секунда после предыдущего пакета, но перехвачен между "emptys".

С учетом вашего кода метки системного времени рассчитываются и печатаются только в том случае, если этот итератор в for line in iter(out.readline, b'') возвращает новый элемент, поэтому отсюда и возникает задержка.

Я подозреваю, что виновата буферизация stdio. В Linux (то есть libc / glibc), если дескриптор STDOUT ссылается на TTY, буферизация строки включена. Если он ссылается на что-то другое (например, канал), дескриптор STDOUT полностью буферизуется; ваш процесс должен заполнить 4096 байт (по умолчанию в Linux) перед вызовом системного вызова write.
Очень грубо подсчитано, основываясь на выводе, который вы показываете здесь, ваш подпроцесс, похоже, генерирует ~ 65 байт каждые ~ 0,025 секунды. При наличии буфера 4 КБ, его заполнение и запуск сброса / записи потребуют ~ 1,625 секунды.

Чтение из этого subprocess.PIPE и отправка вывода в стандартный вывод вашего основного процесса занимает намного меньше, следовательно, вы видите пакеты с выводом tcpdump, которые находятся на расстоянии ~ 25 мс при печати (полученной из итератора stdout) внутри несколько микросекунд, и ваша программа впоследствии ожидает сброса следующих 4 КБ.

Если у вас есть возможность установить сторонние пакеты (и использовать Python> = 2.7), вы можете посмотреть pexpect . Дочерние элементы этого пакета подключаются к PTY, заставляя систему обращаться с ними как с интерактивными программами, поэтому их дескриптор stdout буферизуется по строке.

...