Сообщить об ошибке.

Статистика ввода-вывода и использование диска в Python

Материал содержит описание функций модуля psutil с примерами, которые возвращают статистику использования жесткого диска, такую как: разделы диска, использование диска для конкретного раздела, статистика дискового ввода-вывода.

Содержание:


psutil.disk_partitions(all=False):

Функция psutil.disk_partitions() возвращает все смонтированные разделы диска в виде списка именованных кортежей, включая устройство, точку монтирования и тип файловой системы, аналогично команде df в UNIX.

Если для аргумента all установлено значение False, то функция будет пытаться различать и возвращать только физические устройства (например, жесткие диски, приводы компакт-дисков, USB-накопители) и игнорировать все остальные (например, память, дубликаты, недоступные файловые системы). Обратите внимание, что это поведение может быть не надежным в некоторых системах (например, в BSD этот параметр игнорируется).

Возвращает список именованных кортежей со следующими полями:

  • device: путь к устройству (например, "/dev/hda1"). В Windows это буква диска (например, "C:\\").
  • mountpoint: путь к точке монтирования (например, "/"). В Windows это буква диска (например, "C:\\").
  • fstype: файловая система раздела (например, "ext3" в UNIX или "NTFS" в Windows).
  • opts: строка, разделенная запятыми, указывающая различные варианты подключения для диска/раздела (в зависимости от платформы).
  • maxfile: максимальная длина, которую может иметь имя файла.
  • maxpath: максимальная длина, которую может иметь имя пути (имя каталога + имя базового файла).
>>> import psutil
>>> psutil.disk_partitions()
# [
#   sdiskpart(device='/dev/sda3', mountpoint='/', fstype='ext4', 
#             opts='rw,errors=remount-ro', maxfile=255, maxpath=4096),
#   sdiskpart(device='/dev/sda7', mountpoint='/home', fstype='ext4', 
#             opts='rw', maxfile=255, maxpath=4096)
# ]

psutil.disk_usage(path):

Функция psutil.disk_usage() возвращает статистику использования диска для раздела, содержащего указанный путь path, в виде именованного кортежа, включая общее, используемое и свободное пространство, выраженное в байтах, а также использование в процентах.

Если путь не существует, то возникает исключение OSError.

Дополнительно смотрите похожую функцию стандартной библиотеки shutil.disk_usage().

>>> import psutil
>>> psutil.disk_usage('/')
# sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5)

Примечание. Системы UNIX обычно резервирует 5% всего дискового пространства для пользователя root. Поля total и used в UNIX относятся к общему и используемому пространству, тогда как free представляет пространство, доступное для пользователя, а percent представляет использование пользователем. Вот почему значение percent может выглядеть на 5% больше, чем ожидается. Также обратите внимание, что оба значения соответствуют команде bash - df.

psutil.disk_io_counters(perdisk=False, nowrap=True):

Функция psutil.disk_io_counters() возвращает общесистемную статистику дискового ввода-вывода в виде именованного кортежа, включающего следующие поля:

  • read_count: количество считываний;
  • write_count: количество записей;
  • read_bytes: количество прочитанных байт;
  • write_bytes: количество записанных байт.

Поля, специфичные для конкретной платформы:

  • read_time: (все, кроме NetBSD и OpenBSD) время, затраченное на чтение с диска (в миллисекундах)
  • write_time: (все, кроме NetBSD и OpenBSD) время, затраченное на запись на диск (в миллисекундах)
  • busy_time: (Linux, FreeBSD) время, затраченное на фактический ввод-вывод (in milliseconds)
  • read_merged_count (Linux): количество объединенных чтений (системный вызов iostats)
  • write_merged_count (Linux): количество объединенных записей (системный вызов iostats)

Если аргумент perdisk имеет значение True, то возвращает ту же информацию для каждого физического диска, установленного в системе, в виде словаря с именами разделов в качестве ключей и именованным кортежем, описанным выше, в качестве значений.

В некоторых системах, таких как Linux, в очень загруженных или долгоживущих системах, числа, возвращаемые ядром, могут переполняться и переноситься (перезапускаться с нуля). Если аргумент nowrap имеет значение True, то модуль psutil обнаружит и скорректирует эти числа при вызовах функций и добавит "старое значение" к "новому значению", чтобы возвращаемые числа всегда увеличивались или оставались неизменными, но никогда не уменьшались.

Для аннулирования кеша nowrap можно использовать psutil.disk_io_counters.cache_clear(). В Windows, чтобы включить счетчики ввода-вывода, сначала может потребоваться выполнить команду diskperf -y. На бездисковых машинах, если perdisk имеет значение True, то эта функция вернет None или пустой словарь {}.

>>> import psutil
>>> psutil.disk_io_counters()
# sdiskio(read_count=8141, write_count=2431, read_bytes=290203, 
# write_bytes=537676, read_time=5868, write_time=94922)

>>> psutil.disk_io_counters(perdisk=True)
# {
#   'sda1': sdiskio(read_count=920, write_count=1, read_bytes=2933248, 
#                   write_bytes=512, read_time=6016, write_time=4),
#   'sda2': sdiskio(read_count=18707, write_count=8830, read_bytes=6060, 
#                   write_bytes=3443, read_time=24585, write_time=1572),
#   'sdb1': sdiskio(read_count=161, write_count=0, read_bytes=786432, 
#                   write_bytes=0, read_time=44, write_time=0)
# }

Примечание: возможно, сначала потребуется выполнить команду Windows diskperf -y, иначе эта функция не найдет ни одного диска.

Пример статистики использования смонтированных разделов диска.

import os
import sys
import psutil
from psutil._common import bytes2human

def main():
    templ = "%-17s %8s %8s %8s %5s%% %9s  %s"
    print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type",
                   "Mount"))
    for part in psutil.disk_partitions(all=False):
        if os.name == 'nt':
            if 'cdrom' in part.opts or part.fstype == '':
                # пропускаем приводы cd-rom, в которых нет диска; 
                # они могут вызвать ошибку графического интерфейса
                # Windows для неготового раздела или просто зависнуть
                continue
        usage = psutil.disk_usage(part.mountpoint)
        print(templ % (
            part.device,
            bytes2human(usage.total),
            bytes2human(usage.used),
            bytes2human(usage.free),
            int(usage.percent),
            part.fstype,
            part.mountpoint))

if __name__ == '__main__':
    sys.exit(main())

Вывод сценария:

$ python3 test.py
Device               Total     Used     Free  Use %      Type  Mount
/dev/sdb3            18.9G    14.7G     3.3G    77%      ext4  /
/dev/sda6           345.9G    83.8G   244.5G    24%      ext4  /home
/dev/sda1           296.0M    43.1M   252.9M    14%      vfat  /boot/efi
/dev/sda2           600.0M   312.4M   287.6M    52%   fuseblk  /media/Recovery

Пример статистики дискового ввода-вывода.

Данный пример представляет собой клон утилиты Linux iotop, которая показывает в реальном времени статистику дискового ввода-вывода.

  • Сценарий работает только на Linux (FreeBSD и macOS отсутствует поддержка счетчиков ввода-вывода).
  • Сценарий не работает в Windows, так как требуется модуль curses.
import sys
import time

try:
    import curses
except ImportError:
    sys.exit('Платформа не поддерживается')

import psutil
from psutil._common import bytes2human

win = curses.initscr()
lineno = 0

def printl(line, highlight=False):
    """Тонкая обертка вокруг `curses`."""
    global lineno
    try:
        if highlight:
            line += " " * (win.getmaxyx()[1] - len(line))
            win.addstr(lineno, 0, line, curses.A_REVERSE)
        else:
            win.addstr(lineno, 0, line, 0)
    except curses.error:
        lineno = 0
        win.refresh()
        raise
    else:
        lineno += 1

def poll(interval):
    """Расчет использования операций ввода-вывода, сравнив 
    данные до и после интервала `interval` (аргумент функции).
    Возвращает кортеж, включающий все запущенные в данный 
    момент процессы, отсортированные по активности ввода-вывода 
    и общей активности дискового ввода-вывода.
    """
    # получаем список всех процессов и счетчиков ввода-вывода с диска
    procs = [p for p in psutil.process_iter()]
    for p in procs[:]:
        try:
            p._before = p.io_counters()
        except psutil.Error:
            procs.remove(p)
            continue
    disks_before = psutil.disk_io_counters()

    # немного спим
    time.sleep(interval)

    # затем снова вытаскиваем ту же информацию
    for p in procs[:]:
        with p.oneshot():
            try:
                p._after = p.io_counters()
                p._cmdline = ' '.join(p.cmdline())
                if not p._cmdline:
                    p._cmdline = p.name()
                p._username = p.username()
            except (psutil.NoSuchProcess, psutil.ZombieProcess):
                procs.remove(p)
    disks_after = psutil.disk_io_counters()

    # рассчитываем результаты, сравнив данные до и после `interval`
    for p in procs:
        p._read_per_sec = p._after.read_bytes - p._before.read_bytes
        p._write_per_sec = p._after.write_bytes - p._before.write_bytes
        p._total = p._read_per_sec + p._write_per_sec

    disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes
    disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes
    # сортируем процессы по общему объему ввода-вывода с диска, 
    # более интенсивные будут первыми
    processes = sorted(procs, key=lambda p: p._total, reverse=True)
    return (processes, disks_read_per_sec, disks_write_per_sec)

def refresh_window(procs, disks_read, disks_write):
    """Вывод результатов на экран с помощью curses."""
    curses.endwin()
    templ = "%-5s %-7s %11s %11s  %s"
    win.erase()

    disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \
                % (bytes2human(disks_read), bytes2human(disks_write))
    printl(disks_tot)

    header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND")
    printl(header, highlight=True)

    for p in procs:
        line = templ % (
            p.pid,
            p._username[:7],
            bytes2human(p._read_per_sec),
            bytes2human(p._write_per_sec),
            p._cmdline)
        try:
            printl(line)
        except curses.error:
            break
    win.refresh()

def setup():
    curses.start_color()
    curses.use_default_colors()
    for i in range(0, curses.COLORS):
        curses.init_pair(i + 1, i, -1)
    curses.endwin()
    win.nodelay(1)

def tear_down():
    win.keypad(0)
    curses.nocbreak()
    curses.echo()
    curses.endwin()

if __name__ == '__main__':
    setup()
    try:
        interval = 0
        while True:
            if win.getch() == ord('q'):
                break
            args = poll(interval)
            refresh_window(*args)
            lineno = 0
            interval = 0.5
            time.sleep(interval)
    except (KeyboardInterrupt, SystemExit):
        pass
    finally:
        tear_down()

Вывод сценария:

$ python3 test.py
Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s
PID   USER      DISK READ  DISK WRITE  COMMAND
13155 giampao    0.00 B/s  428.00 K/s  /usr/bin/google-chrome-beta
3260  giampao    0.00 B/s    0.00 B/s  bash
3779  giampao    0.00 B/s    0.00 B/s  gnome-session --session=ubuntu
3830  giampao    0.00 B/s    0.00 B/s  /usr/bin/dbus-launch
3831  giampao    0.00 B/s    0.00 B/s  //bin/dbus-daemon --fork --print-pid 5
3841  giampao    0.00 B/s    0.00 B/s  /usr/lib/at-spi-bus-launcher
3845  giampao    0.00 B/s    0.00 B/s  /bin/dbus-daemon
3848  giampao    0.00 B/s    0.00 B/s  /usr/lib/at-spi2-core/at-spi2-registryd
3862  giampao    0.00 B/s    0.00 B/s  /usr/lib/gnome-settings-daemon