Облачная кухня: рецепт приготовления тестов производительности дисковой подсистемы сервера

от автора

Хабр, приветствую! Сегодня с вами Дмитрий Михайлов, ИТ-инженер Cloud4Y, и в этой статье я поделюсь с вами опытом проверки производительности виртуальных дисков. Расскажу и покажу, что тут и как, а пощупать всё вживую вы сможете, взяв данные из папки, которую я положил в Nextcloud.

Зачем проверять?

Во-первых, это одна из частых тем обращений в техподдержку. Иногда причина лежит на поверхности: некоторые клиенты запускают что-нибудь из серии CrystalMark и присылают скриншоты замеров, сопровождая их своей интерпретацией. Иные обращаются с общей жалобой на недостаточную производительность ВМ в целом или своего ПО/БД. В этом случае, разумеется, потребуется более глубокая диагностика. Во-вторых, самим интересно :). Так мы можем проверить новые и текущие тома СХД и сравнить системы между собой. Как бы там ни было, методика плюс-минус подходит в обоих случаях.

Что проверяется?

Методика оценивает дисковую подсистему именно в гостевой ОС виртуальной машины. Если в самой ОС всё в порядке, это косвенно позволяет оценить с точки зрения клиента выбранный для диска профиль на соответствие как заявленным параметрам, так и своим требованиям. И можно либо оптимизировать ПО/ОС, либо перейти на более производительный дисковый профиль.

Как проверяется?

Идея заключается в серии замеров утилитой iostat в течении всего времени проверки с периодическим запуском тестовой нагрузки в виде fio. Это позволит сравнить между собой показания «холостого хода» и под нагрузкой. Очень удобно, что iostat умеет сохранять показания в формате json. Это избавляет от костылификации регулярок для парсинга консольного вывода. Полученные json-файлы обрабатываются при помощи библиотек Pandas и MatPlotLib: в JupyterLab строятся графики для дальнейшего анализа.

Работа утилит в командной строке и результаты в виде json позволили оптимизировать проведение диагностики. С другой стороны, это сужает применяемость методики до круга *nix-систем.

Практика №1

Тестовый стенд представляет собой ВМ с Ubuntu 20.04 в качестве гостевой ОС, CPU 2 ядра, RAM 4 ГБ.

Cloud4Y предлагает несколько дисковых политик на выбор, мы попрактикуемся на наиболее популярной из них, vcd-type-med. В конфиге runfio.cfg (можно найти в Приложении к этой статье) отражены её параметры из стандартного договора:

  • гарантируется, в зависимости от размера диска, до 1000 iops на диск с latency менее 30 мс при нагрузке

  • 70% на чтение

  • 30% на запись

  • блок 4 КБ

  • при 50% random’е

Т. е. в данном случае мы проверим соответствие реальных значений заявленным. После запуска и обработки тестов результат выглядит так:

На графике представлены все ключевые метрики, собранные в гостевой ОС. Тестовая нагрузка похожа на два «верблюжьих горба» и составила 700 операций на чтение и 300 на запись при latency около 1 мс, что ещё очень далеко до оговорённых 30 мс. Утилизация диска ожидаемо согласуется с тестами — 100% при нагрузке и около 0% без неё. Длина очереди под нагрузкой была на уровне 5 КБ.

Тест под 100% нагрузкой пройдён. Интересно, а что произойдёт при перегрузке и почему?

Практика №2

Под спойлером графики для нагрузки в 50%, 100%, 200%, 500% и 1000% от номинала
fio rate 50%  graphs
fio rate 50% graphs
fio rate 100% graphs
fio rate 100% graphs
fio rate 200% graphs
fio rate 200% graphs
fio rate 500% graphs
fio rate 500% graphs
fio rate 1000% graphs
fio rate 1000% graphs

x10 означает 1000% от предусмотренного политикой номинала

т.е. fio запускалась с конфигом rate_iops=7000,3000, вместо обычных 700,300,

Половинная нагрузка ничем принципиально не отличается от полной, интересное начинается при перегрузке. Последние три графика по сути одинаковы. Видно, что утилизация диска в моменты нагрузки также достигает 100%, как и ранее. IOPS’ы подросли немного, примерно на +20% от номинала, и составили 800 на запись и 400 на чтение. А вот длина очереди и latency выросли многократно.

Почему так происходит?

Дело в том, что когда гостевая ОС выходит из зоны комфорта за рамки дисковой политики, vSphere начинает троттлинг: притормаживает «лишние» IO-операции, помещая их в очередь перед отправкой к СХД так, чтобы вернуть ВМ обратно в заданные границы. Таким образом, попытки сэкономить на дисках приведут лишь к неожиданным значительным «тормозам» ПО by design.

Выводы

В итоге имеем вполне рабочий инструмент для анализа и диагностики дисковой подсистемы, который подойдёт не только для виртуальных машин, но и для «железных» серверов. Длительность измерений можно увеличить довольно сильно, а вместе или вместо тестовой fio-нагрузки включать/выключать боевое ПО. Так диагностика будет глубже и вы сможете точнее локализовать проблему производительности. Кроме того, в случае более сложной архитектуры дисковой системы, видно картину в целом: физические и логические устройства.

На практике у нас был кейс, когда клиент столкнулся с низкой производительностью софта по обсчёту кубов данных. Сервер был «железный», без виртуализации. Разработчики ПО отвечали клиенту, что проблема в производительности дисковой подсистемы. Мы провели диагностику, фиксируя метрики на протяжении 24 часов, и дали клиенту подробный отчёт, предоставив ему аргументацию для диалога с разработчиками ПО.

Под спойлером ниже — график из этого кейса, в т. ч. как иллюстрация более сложной архитектуры: физические диски были объединены в программный рейд, внутри которого организована LVM-структура томов.

График
complex disk subsystem graphs expample
complex disk subsystem graphs expample

Приложения

Ссылки на материалы

  1. Официальная документация VMWare

  2. Кратко о тротлинге при превышении лимита IOPS

  3. Статья VMWare о механизмах лимитирования

  4. Для чего нужны лимиты

  5. Статья в knowledge base Cloud4Y

Запуск тестов

cat ./runfio.cfg ./runfio.sh    --repeat 2 --time  60 >> runfio.sh.log     & ./runiostat.sh --repeat 1 --count 30 >> runiostat.sh.log  & 

Нагрузка включится дважды на 60 секунд с интервалом в 60 секунд, т.е. первая, третья и пятая минуты — холостой ход, а вторая и четвёртая — под нагрузкой.

За это время будет сделано 30 замеров с интервалом в 10 секунд, который задан в теле скрипта.

               minutes         | 1 | 2 | 3 | 4 | 5 | --------|---|---|---|---|---| iostat  | + | + | + | + | + | --------|---|---|---|---|---| fio     |   | + |   | + |   |

Исходный код скриптов запуска измерений и нагрузки на Bash

runfio.sh
#!/usr/bin/env bash # https://brianchildress.co/named-parameters-in-bash/ repeat=${repeat:-1} time=${time:-10} while [ $# -gt 0 ]; do if [[ * ]]; then         param="" class="formula inline">{1/--/}" declare " class="formula inline">2" # echo $1 $2  # Optional to see the parameter:value result fi shift done for i in </SPAN>seq <SPAN class=m>1</SPAN> <SPAN class=nv>$repeat</SPAN><SPAN class=sb>; do sleep <SPAN class=nv>$time</SPAN>  <SPAN class=c1># fio   --bs=4k --rw=randrw --percentage_random=50 --rwmixread=70 --rwmixwrite=30 --direct=1 --iodepth=256 --time_based --group_reporting --name=iops-test-job --eta-newline=1 --numjobs=1 --filename=runfio.sh.test --runtime=$time --size=1GB --ioengine=libaio --rate_iops=700,300,</SPAN>  fio --runtime<SPAN class=o>=</SPAN><SPAN class=nv>$time</SPAN> --eta-newline<SPAN class=o>=</SPAN><SPAN class=m>1</SPAN> runfio.cfg  done 

runfio.cfg
[c4y] bs=4k rw=randrw percentage_random=50 rwmixread=70 rwmixwrite=30 direct=1 iodepth=256 time_based group_reporting name=iops-test-job numjobs=1 filename=runfio.sh.test size=1GB ioengine=libaio rate_iops=700,300, 

runiostat.sh
#!/usr/bin/env bash # https://brianchildress.co/named-parameters-in-bash/ repeat=${repeat:-1} count=${count:-1} while [ $# -gt 0 ]; do if [[ * ]]; then         param="" class="formula inline">{1/--/}" declare " class="formula inline">2" # echo $1 $2  # Optional to see the parameter:value result fi shift done for i in </SPAN>seq <SPAN class=m>1</SPAN> <SPAN class=nv>$repeat</SPAN><SPAN class=sb>; do <SPAN class=nv>dt</SPAN><SPAN class=o>=</SPAN><SPAN class=sb>`</SPAN>date <SPAN class=s1>'+%Y-%m-%d_%H-%M-%S'</SPAN><SPAN class=sb>`</SPAN>  <SPAN class=nb>echo</SPAN> <SPAN class=s2>"</SPAN><SPAN class=nv>$i</SPAN><SPAN class=s2>    </SPAN><SPAN class=nv>$dt</SPAN><SPAN class=s2>"</SPAN>  ./sysstat/iostat -x -t -o JSON <SPAN class=m>10</SPAN> <SPAN class=nv>$count</SPAN> &gt; <SPAN class=s2>"./runiostat.sh-</SPAN><SPAN class=nv>$dt</SPAN><SPAN class=s2>.json"</SPAN>  done 

Исходный код построения графиков на Python3

conf.py
# маска для составления списка файлов в подпапке mask = '*.json' # корневая папка скрипта является рабочей папкой по умолчанию base_dir = '.' # исключающий список папок, которые функция get_folders пропускает exclude_dirs = ['pycache', '.idea', '.ipynb_checkpoints', '_shell-scripts', '__stuff'] # исключающий список устройств, для которых не нужно строить графики exclude_dev = [ 'fd0', 'sr0', 'loop0', 'loop1', 'loop2', 'loop3', 'loop4', 'loop5', 'loop6', 'loop7', 'loop8', 'loop9', 'loop10', 'loop11', 'loop12', 'loop13', 'loop14', 'loop15', ] # описания графиков: какую группу метрик выводить, как подписать ось Y и какие у неё единицы измерения figures = [ {  # 1й график 'labels':   'CPU utilization', 'units':    '%', 'metrics':  ['avg-cpu.idle', 'avg-cpu.system', 'avg-cpu.user', 'avg-cpu.nice', 'avg-cpu.iowait', 'avg-cpu.steal'], 'legends':  ['idle', 'system', 'user', 'nice', 'iowait', 'steal'], }, {  # 2й график 'labels':   'harddisk utilization', 'units':    '%', 'metrics':  ['rrqm', 'wrqm', 'util', 'drqm'] }, { 'labels':   'harddisk iops', 'units':    'iops', 'metrics':  ['r/s', 'w/s', 'rrqm/s', 'wrqm/s', 'd/s', 'drqm/s'] }, { 'labels':   'harddisk latency', 'units':    's', 'metrics':  ['r_await', 'w_await', 'd_await'] }, { 'labels':   'harddisk queue sizes', 'units':    'B', 'metrics':  ['rareq-sz', 'wareq-sz', 'aqu-sz', 'dareq-sz'] }, { 'labels':   'harddisk traffic', 'units':    'B/s', 'metrics':  ['rkB/s', 'wkB/s', 'dkB/s'] }, ] 

helper.py
import json import glob import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mpl_dates from matplotlib.ticker import EngFormatter import os # для проверки через isinstance import matplotlib import numpy import conf # возвращает список папок внутри conf.base_dir в виде объектов <DirEntry ...> со свойствами .name и .path def get_folders(base_dir=None, exclude_dirs=None): if base_dir is None: base_dir = conf.base_dir <SPAN class=k>if</SPAN> <SPAN class=n>exclude_dirs</SPAN> <SPAN class=ow>is</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>exclude_dirs</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>exclude_dirs</SPAN>  <SPAN class=k>return</SPAN> <SPAN class=p>[</SPAN><SPAN class=n>f</SPAN> <SPAN class=k>for</SPAN> <SPAN class=n>f</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>os</SPAN><SPAN class=o>.</SPAN><SPAN class=n>scandir</SPAN><SPAN class=p>(</SPAN><SPAN class=n>base_dir</SPAN><SPAN class=p>)</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>f</SPAN><SPAN class=o>.</SPAN><SPAN class=n>is_dir</SPAN><SPAN class=p>()</SPAN> <SPAN class=ow>and</SPAN> <SPAN class=n>f</SPAN><SPAN class=o>.</SPAN><SPAN class=n>name</SPAN> <SPAN class=ow>not</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>exclude_dirs</SPAN><SPAN class=p>]</SPAN>  # возвращает список файлов по маске conf.mask в указанной папке def get_files(folder, mask=None): if not isinstance(folder, str): folder = '' <SPAN class=n>files</SPAN> <SPAN class=o>=</SPAN> <SPAN class=p>[]</SPAN>  <SPAN class=k>if</SPAN> <SPAN class=n>mask</SPAN> <SPAN class=ow>is</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>files</SPAN> <SPAN class=o>+=</SPAN> <SPAN class=n>glob</SPAN><SPAN class=o>.</SPAN><SPAN class=n>glob</SPAN><SPAN class=p>(</SPAN><SPAN class=n>os</SPAN><SPAN class=o>.</SPAN><SPAN class=n>path</SPAN><SPAN class=o>.</SPAN><SPAN class=n>join</SPAN><SPAN class=p>(</SPAN><SPAN class=n>folder</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>mask</SPAN><SPAN class=p>))</SPAN> <SPAN class=k>else</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>files</SPAN> <SPAN class=o>+=</SPAN> <SPAN class=n>glob</SPAN><SPAN class=o>.</SPAN><SPAN class=n>glob</SPAN><SPAN class=p>(</SPAN><SPAN class=n>os</SPAN><SPAN class=o>.</SPAN><SPAN class=n>path</SPAN><SPAN class=o>.</SPAN><SPAN class=n>join</SPAN><SPAN class=p>(</SPAN><SPAN class=n>folder</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>mask</SPAN><SPAN class=p>))</SPAN>  <SPAN class=k>return</SPAN> <SPAN class=n>files</SPAN>  # def get_json(file=None): if file: with open(file) as f:  # импорт json из списка файлов js = json.load(f) return js # def normalize(df=None):  # нормализует колонки для корректного отображения приставок перед единицами измерения if 'r_await' in df.columns: df['r_await'] = df['r_await'] / 1000  # milliseconds -> seconds if 'w_await' in df.columns: df['w_await'] = df['w_await'] / 1000  # milliseconds -> seconds if 'd_await' in df.columns: df['d_await'] = df['d_await'] / 1000  # milliseconds -> seconds <SPAN class=k>if</SPAN> <SPAN class=s1>'rareq-sz'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'rareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'rareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes -&gt; Bytes</SPAN> <SPAN class=k>if</SPAN> <SPAN class=s1>'wareq-sz'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'wareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'wareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes -&gt; Bytes</SPAN> <SPAN class=k>if</SPAN> <SPAN class=s1>'dareq-sz'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'dareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'dareq-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes -&gt; Bytes</SPAN> <SPAN class=k>if</SPAN> <SPAN class=s1>'aqu-sz'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'aqu-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'aqu-sz'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes -&gt; Bytes</SPAN>  <SPAN class=k>if</SPAN> <SPAN class=s1>'rkB/s'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'rkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'rkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes/s -&gt; Bytes/s</SPAN> <SPAN class=k>if</SPAN> <SPAN class=s1>'wkB/s'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'wkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'wkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes/s -&gt; Bytes/s</SPAN> <SPAN class=k>if</SPAN> <SPAN class=s1>'dkB/s'</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'dkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'dkB/s'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>*</SPAN> <SPAN class=mi>1024</SPAN>  <SPAN class=c1># kBytes/s -&gt; Bytes/s</SPAN>  <SPAN class=k>return</SPAN> <SPAN class=n>df</SPAN>  # def merge_dataframes(gen_data=None, dev_data=None, device=None, datetime_format=None): if gen_data is None or dev_data is None or device is None: return None <SPAN class=n>df</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>pd</SPAN><SPAN class=o>.</SPAN><SPAN class=n>concat</SPAN><SPAN class=p>([</SPAN>     <SPAN class=n>dev_data</SPAN><SPAN class=p>[</SPAN><SPAN class=n>dev_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'disk_device'</SPAN><SPAN class=p>]</SPAN> <SPAN class=o>==</SPAN> <SPAN class=n>device</SPAN><SPAN class=p>]</SPAN><SPAN class=o>.</SPAN><SPAN class=n>reset_index</SPAN><SPAN class=p>(),</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'timestamp'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.user'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.nice'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.system'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.iowait'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.steal'</SPAN><SPAN class=p>],</SPAN>     <SPAN class=n>gen_data</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'avg-cpu.idle'</SPAN><SPAN class=p>],</SPAN> <SPAN class=p>],</SPAN> <SPAN class=n>join</SPAN><SPAN class=o>=</SPAN><SPAN class=s1>'inner'</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>axis</SPAN><SPAN class=o>=</SPAN><SPAN class=mi>1</SPAN><SPAN class=p>)</SPAN>  <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>drop</SPAN><SPAN class=p>([</SPAN><SPAN class=s1>'index'</SPAN><SPAN class=p>],</SPAN> <SPAN class=n>inplace</SPAN><SPAN class=o>=</SPAN><SPAN class=kc>True</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>axis</SPAN><SPAN class=o>=</SPAN><SPAN class=mi>1</SPAN><SPAN class=p>)</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>pd</SPAN><SPAN class=o>.</SPAN><SPAN class=n>to_datetime</SPAN><SPAN class=p>(</SPAN><SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'timestamp'</SPAN><SPAN class=p>],</SPAN> <SPAN class=nb>format</SPAN><SPAN class=o>=</SPAN><SPAN class=n>datetime_format</SPAN><SPAN class=p>)</SPAN>  <SPAN class=c1># '%d.%m.%Y %H:%M:%S'  # https://nbviewer.jupyter.org/gist/nipunbatra/6947228  # df['time'] = pd.to_datetime(df['timestamp'], format='%d.%m.%Y %H:%M:%S')</SPAN> <SPAN class=n>df</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>normalize</SPAN><SPAN class=p>(</SPAN><SPAN class=n>df</SPAN><SPAN class=o>=</SPAN><SPAN class=n>df</SPAN><SPAN class=p>)</SPAN>  <SPAN class=k>return</SPAN> <SPAN class=n>df</SPAN>  # def plot( files=None,             # список файлов, по которым нужно строить графики w=4,                    # ширина графика h=3,                    # высота графика datetime_format=None,   # формат отметок времени в json-файле first=0,                # первая точка last=None,              # последняя точка dev_desc=None,          # описания устройств, узнать можно командой ls -l /dev/mapper dev_placement='col',    # как располагать устройства, в колонках или строках ):  # непосредственно рисует графики <SPAN class=k>if</SPAN> <SPAN class=n>dev_desc</SPAN> <SPAN class=ow>is</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>:</SPAN>     <SPAN class=n>dev_desc</SPAN> <SPAN class=o>=</SPAN> <SPAN class=p>{}</SPAN>  <SPAN class=k>if</SPAN> <SPAN class=n>files</SPAN> <SPAN class=ow>is</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>:</SPAN>     <SPAN class=k>return</SPAN> <SPAN class=o>-</SPAN><SPAN class=mi>1</SPAN>  <SPAN class=k>if</SPAN> <SPAN class=nb>isinstance</SPAN><SPAN class=p>(</SPAN><SPAN class=n>files</SPAN><SPAN class=p>,</SPAN> <SPAN class=nb>str</SPAN><SPAN class=p>):</SPAN>     <SPAN class=n>files</SPAN> <SPAN class=o>=</SPAN> <SPAN class=p>[</SPAN><SPAN class=n>files</SPAN><SPAN class=p>]</SPAN>  <SPAN class=k>for</SPAN> <SPAN class=n>file</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>files</SPAN><SPAN class=p>:</SPAN>     <SPAN class=c1># обработка (импорт данных, построение графиков) по одному файлу за раз</SPAN>     <SPAN class=k>with</SPAN> <SPAN class=nb>open</SPAN><SPAN class=p>(</SPAN><SPAN class=n>file</SPAN><SPAN class=p>)</SPAN> <SPAN class=k>as</SPAN> <SPAN class=n>f</SPAN><SPAN class=p>:</SPAN>  <SPAN class=c1># импорт json из списка файлов</SPAN>         <SPAN class=n>j</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>json</SPAN><SPAN class=o>.</SPAN><SPAN class=n>load</SPAN><SPAN class=p>(</SPAN><SPAN class=n>f</SPAN><SPAN class=p>)</SPAN>      <SPAN class=c1># преобразование json в dataframe  https://towardsdatascience.com/all-pandas-json-normalize-you-should-know-for-flattening-json-13eae1dfb7dd</SPAN>     <SPAN class=n>general</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>pd</SPAN><SPAN class=o>.</SPAN><SPAN class=n>json_normalize</SPAN><SPAN class=p>(</SPAN><SPAN class=n>j</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'sysstat'</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'hosts'</SPAN><SPAN class=p>][</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'statistics'</SPAN><SPAN class=p>])</SPAN>  <SPAN class=c1># метки времени</SPAN>     <SPAN class=n>device</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>pd</SPAN><SPAN class=o>.</SPAN><SPAN class=n>json_normalize</SPAN><SPAN class=p>(</SPAN><SPAN class=n>j</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'sysstat'</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'hosts'</SPAN><SPAN class=p>][</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'statistics'</SPAN><SPAN class=p>],</SPAN> <SPAN class=n>record_path</SPAN><SPAN class=o>=</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'disk'</SPAN><SPAN class=p>])</SPAN>  <SPAN class=c1># статистика дисков</SPAN>      <SPAN class=n>devs</SPAN> <SPAN class=o>=</SPAN> <SPAN class=p>[</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'disk_device'</SPAN><SPAN class=p>]</SPAN> <SPAN class=k>for</SPAN> <SPAN class=n>dev</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>j</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'sysstat'</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'hosts'</SPAN><SPAN class=p>][</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'statistics'</SPAN><SPAN class=p>][</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>][</SPAN><SPAN class=s1>'disk'</SPAN><SPAN class=p>]</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>dev</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'disk_device'</SPAN><SPAN class=p>]</SPAN> <SPAN class=ow>not</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>exclude_dev</SPAN><SPAN class=p>]</SPAN>     <SPAN class=c1># devs.sort(reverse=True)</SPAN>      <SPAN class=k>if</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'col'</SPAN><SPAN class=p>:</SPAN>         <SPAN class=n>fig</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>axs</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>plt</SPAN><SPAN class=o>.</SPAN><SPAN class=n>subplots</SPAN><SPAN class=p>(</SPAN><SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>),</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>devs</SPAN><SPAN class=p>),</SPAN> <SPAN class=n>figsize</SPAN><SPAN class=o>=</SPAN><SPAN class=p>(</SPAN><SPAN class=n>w</SPAN> <SPAN class=o>*</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>devs</SPAN><SPAN class=p>),</SPAN> <SPAN class=n>h</SPAN> <SPAN class=o>*</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>)),</SPAN> <SPAN class=p>)</SPAN>  <SPAN class=c1># sharex='col',)  # sharey='row')  # , gridspec_kw=dict(hspace=0.1, wspace=0, ))</SPAN>     <SPAN class=k>elif</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'row'</SPAN><SPAN class=p>:</SPAN>         <SPAN class=n>fig</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>axs</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>plt</SPAN><SPAN class=o>.</SPAN><SPAN class=n>subplots</SPAN><SPAN class=p>(</SPAN><SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>devs</SPAN><SPAN class=p>),</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>),</SPAN> <SPAN class=n>figsize</SPAN><SPAN class=o>=</SPAN><SPAN class=p>(</SPAN><SPAN class=n>w</SPAN> <SPAN class=o>*</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>),</SPAN> <SPAN class=n>h</SPAN> <SPAN class=o>*</SPAN> <SPAN class=nb>len</SPAN><SPAN class=p>(</SPAN><SPAN class=n>devs</SPAN><SPAN class=p>)),</SPAN> <SPAN class=p>)</SPAN>  <SPAN class=c1># sharex='col',)  # sharey='row')  # , gridspec_kw=dict(hspace=0.1, wspace=0, ))</SPAN>      <SPAN class=k>for</SPAN> <SPAN class=n>dev</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>devs</SPAN><SPAN class=p>:</SPAN>         <SPAN class=c1># сборка рабочего набора данных</SPAN>         <SPAN class=n>df</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>merge_dataframes</SPAN><SPAN class=p>(</SPAN><SPAN class=n>gen_data</SPAN><SPAN class=o>=</SPAN><SPAN class=n>general</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>dev_data</SPAN><SPAN class=o>=</SPAN><SPAN class=n>device</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>device</SPAN><SPAN class=o>=</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>datetime_format</SPAN><SPAN class=o>=</SPAN><SPAN class=n>datetime_format</SPAN><SPAN class=p>)</SPAN>         <SPAN class=n>df</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=n>first</SPAN><SPAN class=p>:</SPAN><SPAN class=n>last</SPAN><SPAN class=p>]</SPAN>  <SPAN class=c1># print(df.info())    # print(df1.info(), df2.info())</SPAN>          <SPAN class=c1># определяем напр. заполнения</SPAN>         <SPAN class=k>if</SPAN> <SPAN class=nb>isinstance</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axs</SPAN><SPAN class=p>[</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>],</SPAN> <SPAN class=n>matplotlib</SPAN><SPAN class=o>.</SPAN><SPAN class=n>axes</SPAN><SPAN class=o>.</SPAN><SPAN class=n>_subplots</SPAN><SPAN class=o>.</SPAN><SPAN class=n>Axes</SPAN><SPAN class=p>):</SPAN>  <SPAN class=c1># 1-D набор рисунков</SPAN>             <SPAN class=n>axrow</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>axs</SPAN><SPAN class=p>[:]</SPAN>         <SPAN class=k>elif</SPAN> <SPAN class=nb>isinstance</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axs</SPAN><SPAN class=p>[</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>],</SPAN> <SPAN class=n>numpy</SPAN><SPAN class=o>.</SPAN><SPAN class=n>ndarray</SPAN><SPAN class=p>)</SPAN> <SPAN class=ow>and</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'col'</SPAN><SPAN class=p>:</SPAN>  <SPAN class=c1># 2-D набор рисунков</SPAN>             <SPAN class=n>axrow</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>axs</SPAN><SPAN class=p>[:,</SPAN> <SPAN class=n>devs</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>)]</SPAN>         <SPAN class=k>elif</SPAN> <SPAN class=nb>isinstance</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axs</SPAN><SPAN class=p>[</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>],</SPAN> <SPAN class=n>numpy</SPAN><SPAN class=o>.</SPAN><SPAN class=n>ndarray</SPAN><SPAN class=p>)</SPAN> <SPAN class=ow>and</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'row'</SPAN><SPAN class=p>:</SPAN>  <SPAN class=c1># 2-D набор рисунков</SPAN>             <SPAN class=n>axrow</SPAN> <SPAN class=o>=</SPAN> <SPAN class=n>axs</SPAN><SPAN class=p>[</SPAN><SPAN class=n>devs</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>),</SPAN> <SPAN class=p>:]</SPAN>          <SPAN class=c1># подписываем графики вверху и слева</SPAN>         <SPAN class=k>if</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'col'</SPAN><SPAN class=p>:</SPAN>             <SPAN class=p>[</SPAN><SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_ylabel</SPAN><SPAN class=p>(</SPAN><SPAN class=n>fd</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'labels'</SPAN><SPAN class=p>])</SPAN> <SPAN class=k>for</SPAN> <SPAN class=n>ax</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>fd</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=nb>zip</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axrow</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>)</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>devs</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>==</SPAN> <SPAN class=mi>0</SPAN><SPAN class=p>]</SPAN>  <SPAN class=c1># слева метрики</SPAN>             <SPAN class=n>axrow</SPAN><SPAN class=p>[</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>]</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_title</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev</SPAN><SPAN class=si>}</SPAN><SPAN class=s2> (</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev_desc</SPAN><SPAN class=o>.</SPAN><SPAN class=n>get</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>,</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>)</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>)"</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>dev_desc</SPAN> <SPAN class=k>else</SPAN> <SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN><SPAN class=p>)</SPAN>  <SPAN class=c1># верху устройства</SPAN>         <SPAN class=k>elif</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'row'</SPAN><SPAN class=p>:</SPAN>             <SPAN class=p>[</SPAN><SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_title</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>fd</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'labels'</SPAN><SPAN class=p>]</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN><SPAN class=p>)</SPAN> <SPAN class=k>for</SPAN> <SPAN class=n>ax</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>fd</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=nb>zip</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axrow</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>)</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>devs</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>==</SPAN> <SPAN class=mi>0</SPAN><SPAN class=p>]</SPAN>  <SPAN class=c1># верху метрики</SPAN>             <SPAN class=n>axrow</SPAN><SPAN class=p>[</SPAN><SPAN class=mi>0</SPAN><SPAN class=p>]</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_ylabel</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev</SPAN><SPAN class=si>}</SPAN><SPAN class=s2> (</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev_desc</SPAN><SPAN class=o>.</SPAN><SPAN class=n>get</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>,</SPAN> <SPAN class=kc>None</SPAN><SPAN class=p>)</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>)"</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>dev_desc</SPAN> <SPAN class=k>else</SPAN> <SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>dev</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN><SPAN class=p>)</SPAN>  <SPAN class=c1># слева устройства</SPAN>          <SPAN class=k>for</SPAN> <SPAN class=n>ax</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>fd</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=nb>zip</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axrow</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=p>):</SPAN>  <SPAN class=c1># при отладке можно либо скомандовать в консоли либо добавить в Watches команды fig.xxx и plt.show() чтобы наблюдать за заполнением графиков</SPAN>             <SPAN class=n>metric_exist</SPAN> <SPAN class=o>=</SPAN> <SPAN class=p>[</SPAN><SPAN class=n>c</SPAN> <SPAN class=k>for</SPAN> <SPAN class=n>c</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>fd</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'metrics'</SPAN><SPAN class=p>]</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>c</SPAN> <SPAN class=ow>in</SPAN> <SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>columns</SPAN><SPAN class=p>]</SPAN>  <SPAN class=c1># отрисовка только существующих в json метрик</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>plot</SPAN><SPAN class=p>(</SPAN><SPAN class=n>df</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=o>.</SPAN><SPAN class=n>values</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>df</SPAN><SPAN class=p>[</SPAN><SPAN class=n>metric_exist</SPAN><SPAN class=p>])</SPAN>              <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>grid</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axis</SPAN><SPAN class=o>=</SPAN><SPAN class=s1>'x'</SPAN><SPAN class=p>)</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>grid</SPAN><SPAN class=p>(</SPAN><SPAN class=n>axis</SPAN><SPAN class=o>=</SPAN><SPAN class=s1>'y'</SPAN><SPAN class=p>)</SPAN>              <SPAN class=c1># ax.xaxis.set_major_locator(mdates.DayLocator((1, 15)))</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>xaxis</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_major_formatter</SPAN><SPAN class=p>(</SPAN><SPAN class=n>mpl_dates</SPAN><SPAN class=o>.</SPAN><SPAN class=n>DateFormatter</SPAN><SPAN class=p>(</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>%d</SPAN><SPAN class=s2> %b %H:%M"</SPAN><SPAN class=p>))</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>yaxis</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_major_formatter</SPAN><SPAN class=p>(</SPAN><SPAN class=n>EngFormatter</SPAN><SPAN class=p>(</SPAN><SPAN class=n>fd</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'units'</SPAN><SPAN class=p>]))</SPAN>              <SPAN class=c1># номер рисунка</SPAN>             <SPAN class=n>n</SPAN> <SPAN class=o>=</SPAN> <SPAN class=sa>f</SPAN><SPAN class=s2>"#</SPAN><SPAN class=si>{</SPAN><SPAN class=n>files</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>file</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>+</SPAN> <SPAN class=mi>1</SPAN><SPAN class=si>}{</SPAN><SPAN class=n>devs</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>dev</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>+</SPAN> <SPAN class=mi>1</SPAN><SPAN class=si>}{</SPAN><SPAN class=n>conf</SPAN><SPAN class=o>.</SPAN><SPAN class=n>figures</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>fd</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>+</SPAN> <SPAN class=mi>1</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_title</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=si>{</SPAN><SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>get_title</SPAN><SPAN class=p>()</SPAN><SPAN class=si>}</SPAN><SPAN class=s2> </SPAN><SPAN class=se>\n</SPAN><SPAN class=si>{</SPAN><SPAN class=n>n</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN> <SPAN class=k>if</SPAN> <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>get_title</SPAN><SPAN class=p>()</SPAN> <SPAN class=k>else</SPAN> <SPAN class=sa>f</SPAN><SPAN class=s2>"</SPAN><SPAN class=se>\n</SPAN><SPAN class=si>{</SPAN><SPAN class=n>n</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>"</SPAN><SPAN class=p>)</SPAN>             <SPAN class=n>ax</SPAN><SPAN class=o>.</SPAN><SPAN class=n>legend</SPAN><SPAN class=p>(</SPAN><SPAN class=n>fd</SPAN><SPAN class=o>.</SPAN><SPAN class=n>get</SPAN><SPAN class=p>(</SPAN><SPAN class=s1>'legends'</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>fd</SPAN><SPAN class=p>[</SPAN><SPAN class=s1>'metrics'</SPAN><SPAN class=p>]),</SPAN> <SPAN class=n>fontsize</SPAN><SPAN class=o>=</SPAN><SPAN class=mi>8</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>loc</SPAN><SPAN class=o>=</SPAN><SPAN class=s1>'upper left'</SPAN><SPAN class=p>,</SPAN> <SPAN class=n>bbox_to_anchor</SPAN><SPAN class=o>=</SPAN><SPAN class=p>(</SPAN><SPAN class=mf>1.</SPAN><SPAN class=p>,</SPAN> <SPAN class=mf>1.</SPAN><SPAN class=p>,</SPAN> <SPAN class=mf>0.</SPAN><SPAN class=p>,</SPAN> <SPAN class=mf>0.</SPAN><SPAN class=p>))</SPAN>  <SPAN class=c1># 1я, основная легенда с расшифровкой графиков</SPAN>             <SPAN class=c1># ax.add_artist(ax.legend([f"{n}"], fontsize=8, numpoints=1000, loc='upper left', bbox_to_anchor=(1., 1., 0., 0.)))  # 2я легенда с номером картинки</SPAN>      <SPAN class=k>if</SPAN> <SPAN class=n>dev_placement</SPAN> <SPAN class=o>==</SPAN> <SPAN class=s1>'col'</SPAN><SPAN class=p>:</SPAN>         <SPAN class=n>fig</SPAN><SPAN class=o>.</SPAN><SPAN class=n>suptitle</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"Fig. </SPAN><SPAN class=si>{</SPAN><SPAN class=n>files</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>file</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>+</SPAN> <SPAN class=mi>1</SPAN><SPAN class=si>}</SPAN><SPAN class=s2> - '</SPAN><SPAN class=si>{</SPAN><SPAN class=n>file</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>'</SPAN><SPAN class=se>\n</SPAN><SPAN class=s2>"</SPAN><SPAN class=p>)</SPAN>     <SPAN class=k>else</SPAN><SPAN class=p>:</SPAN>         <SPAN class=n>fig</SPAN><SPAN class=o>.</SPAN><SPAN class=n>suptitle</SPAN><SPAN class=p>(</SPAN><SPAN class=sa>f</SPAN><SPAN class=s2>"Fig. </SPAN><SPAN class=si>{</SPAN><SPAN class=n>files</SPAN><SPAN class=o>.</SPAN><SPAN class=n>index</SPAN><SPAN class=p>(</SPAN><SPAN class=n>file</SPAN><SPAN class=p>)</SPAN> <SPAN class=o>+</SPAN> <SPAN class=mi>1</SPAN><SPAN class=si>}</SPAN><SPAN class=s2> - '</SPAN><SPAN class=si>{</SPAN><SPAN class=n>file</SPAN><SPAN class=si>}</SPAN><SPAN class=s2>'"</SPAN><SPAN class=p>)</SPAN>     <SPAN class=n>fig</SPAN><SPAN class=o>.</SPAN><SPAN class=n>patch</SPAN><SPAN class=o>.</SPAN><SPAN class=n>set_facecolor</SPAN><SPAN class=p>(</SPAN><SPAN class=s1>'white'</SPAN><SPAN class=p>)</SPAN>     <SPAN class=n>fig</SPAN><SPAN class=o>.</SPAN><SPAN class=n>autofmt_xdate</SPAN><SPAN class=p>(</SPAN><SPAN class=n>rotation</SPAN><SPAN class=o>=</SPAN><SPAN class=mi>45</SPAN><SPAN class=p>)</SPAN>     <SPAN class=n>fig</SPAN><SPAN class=o>.</SPAN><SPAN class=n>tight_layout</SPAN><SPAN class=p>()</SPAN>     <SPAN class=n>plt</SPAN><SPAN class=o>.</SPAN><SPAN class=n>show</SPAN><SPAN class=p>()</SPAN>  # bbox_to_anchor=(1., 0., 0., 0.)), loc='center left' #                  \   \   \   \            </SPAN> #                   \   \   \   \            - точка легенды, к которой будут применятся координаты #                    \   \   \   ------------- дельта loc по Y #                     \   \   ---------------- дельта loc по X #                      \   ------------------- координата loc по Y #                       ---------------------- координата loc по X # # Loc Number        Loc String #   0               best #   1               upper right #   2               upper left #   3               lower left #   4               lower right #   5               right #   6               center left #   7               center right #   8               lower center #   9               upper center #  10               center 

Поскольку приложить к статье json-файлы никак, поиграться и пощупать можно, взяв данные из папки, которую я положил в Nextcloud. Если есть вопросы — задавайте, постараюсь ответить.

Пример кода отрисовки графиков по json-файлам из папки ‘./log_h-art’
import helper params = { 'files': helper.get_files(folder='log_h-art'),              # список файлов в папке folder='<имя_вложенной_папки>', по которым нужно строить графики 'w': 5,                                                     # ширина графика 'h': 4,                                                     # высота графика 'datetime_format': None,                                    # формат отметок времени в json-файле 'first': 1,                                                 # первая точка 'last': -1,                                                 # последняя точка 'dev_desc': {'sda': 'physical drive', 'sr0': 'cd-rom',},    # названия устройств (сопоставление lvm-томов можно узнать командой ls -l /dev/mapper) 'dev_placement': 'row',                                     # col row расположение метрик отдельного устройства в столбик или в ряд } helper.plot(**params)


ссылка на оригинал статьи https://habr.com/ru/company/cloud4y/blog/696550/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *