Подпроцесс Python с /usr/bin/time: как записывать информацию о времени, но игнорировать все остальные данные?

Я пытаюсь измерить время выполнения в секундах исполняемой программы, вызванной через подпроцесс. Я не хочу, чтобы выводился исполняемый файл (либо stderr, либо stdout).

Я пробовал библиотеки timeit и ресурсов, но не точно фиксировал время процесса, похоже, он только фиксирует время в рабочем потоке Python.

Эта попытка ниже потеряет информацию о времени b/c перенаправления stderr. Однако, без перенаправления stderr, выдается команда 'f_cmd' stderr output.

def doWithTiming(f_cmd):
    DEVNULL = open(os.devnull, 'w')
    return subprocess.check_output([ "/usr/bin/time", "--format=%e seconds"] + f_cmd.split(), stderr=DEVNULL)

Как игнорировать весь вывод f_cmd, но сохранить вывод/usr/bin/time?

Ответ 1

%e /usr/bin/time формат:

Истекшее реальное (настенное время) время, используемое процессом, в секундах.

Чтобы запустить подпроцесс с подавленным stdout/stderr и получить прошедшее время:

#!/usr/bin/env python
import os
import time
from subprocess import check_call, STDOUT

DEVNULL = open(os.devnull, 'wb', 0)

start = time.time()
check_call(['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
print("{:.3f} seconds".format(time.time() - start))

timeit.default_timer является time.time в POSIX на Python 2, поэтому вы должны иметь действительное время, если ваше использование timeit неверно.


Информация, возвращаемая модулем resource, не включает "реальное" время, но вы можете использовать ее для получения "пользовательских" и "sys" раз, т.е. "Общее количество секунд процессора, которое процесс тратит на пользователя Режим." и "Общее количество CPU-секунд, потраченных в режиме ядра". соответственно:

#!/usr/bin/env python
import os
import time
from subprocess import Popen, STDOUT

DEVNULL = open(os.devnull, 'wb', 0)

start = time.time()
p = Popen(['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
ru = os.wait4(p.pid, 0)[2]
elapsed = time.time() - start
print(" {:.3f}real {:.3f}user {:.3f}system".format(
       elapsed, ru.ru_utime, ru.ru_stime))

Вы можете запустить подпроцесс с помощью psutil.Popen и получить, пока дочерний процесс запускает дополнительную информацию (процессор, память, сетевые подключения, потоки, fds, дети и т.д.) переносимым образом.

См. также Как получить максимальное использование памяти в программе с помощью psutil в Python.


Для тестирования (чтобы убедиться, что решение на основе time.time() дает те же результаты), вы можете записать вывод /usr/bin/time:

#!/usr/bin/env python
import os
from collections import deque
from subprocess import Popen, PIPE

DEVNULL = open(os.devnull, 'wb', 0)

time_lines_count = 1 # how many lines /usr/bin/time produces
p = Popen(['/usr/bin/time', '--format=%e seconds'] + 
          ['sleep', '1'], stdout=DEVNULL, stderr=PIPE)
with p.stderr:
    q = deque(iter(p.stderr.readline, b''), maxlen=time_lines_count)
rc = p.wait()
print(b''.join(q).decode().strip())

Или используя опцию -o с именованным каналом:

#!/usr/bin/env python
import os
from contextlib import contextmanager
from shutil     import rmtree
from subprocess import Popen, STDOUT
from tempfile   import mkdtemp

DEVNULL = open(os.devnull, 'wb', 0)

@contextmanager
def named_pipe():
    dirname = mkdtemp()
    try:
        path = os.path.join(dirname, 'named_pipe')
        os.mkfifo(path)
        yield path
    finally:
        rmtree(dirname)

with named_pipe() as path:
    p = Popen(['/usr/bin/time', '--format=%e seconds', '-o', path] + 
              ['sleep', '1'], stdout=DEVNULL, stderr=STDOUT)
    with open(path) as file:
        time_output = file.read().strip()
    rc = p.wait()
print(time_output)

Ответ 2

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

Я предлагаю вам перенаправить stderr на suprocess.PIPE, а затем проанализировать его. Это не должно быть слишком сложно.

В качестве альтернативы вы можете использовать -o со временем, чтобы записать информацию о времени в выходной файл.