Краткое введение в rrdtool

В прошлом посте я упомянул, что collectd по-умолчанию сохраняет собранные данные через rrdtool. На выходе получаются несколько .rrd файлов — по одному для каждой метрики — и их потом можно отрисовать при помощи той же rrdtool. RRD файлы не так часто вплывают в разговорах у кофеварки, да и сама rrdtool та еще tool, так что не сразу понятно, почему collectd использует именно её, а не какой-нибудь CSV.

Оказывается, причин хватает.

Что такое rrdtool

RRDtool — это небольшая утилита, которая замечательно делает три вещи:

  1. Создаёт циклические базы данных (Round-Robin Databases, RRDs),
  2. записывает в них данные,
  3. и создаёт графики на основе того, что туда записала.

Что такое циклическая база данных (RRD)

RRD это такая специальная база данных, которая, начиная с какого-то момента, записывает новые значения поверх старых. Например, если базе полагается хранить 7 дней данных, то восьмой день запишется поверх первого, девятый — поверх второго, и так далее.

Для хранения системных метрик это просто находка. Например, мне редко интересно, как посекундно менялась загрузка процессора год назад. За сегодня и вчера — интересно. Какие-нибудь усредненные почасовые значения за последние пару недель — тоже вполне. Даже усредненную загрузку по дням за последний год иногда интересно глянуть. Но всё, что старше и подробнее — кому это нужно?

А rrdtool как раз под такие ограниченные во времени и точности архивы и заточена. К тому же, из-за того, что временные рамки и точность жёстко заданы, размер архивов известен заранее, так что сюрпризов с переполнением диска не произойдёт.

Создаём циклическую базу (RRD)

Для создания RRD с rrdtool нужно немного чёрной магии, но, если один раз разобраться, дальше всё пойдёт как по маслу. Думаю, стоит сразу начать с готовой команды, а потом разбираться, почему она работает:

Первая строка вполне понятна — создаём что-то по имени cpu.rrd. В нашем случае это циклическая база данных для хранения метрик процессора. Следующий параметр — --step 10  — задаёт, как часто мы будем записывать новую порцию данных. В данном случае — раз в десять секунд. Если данные придут раньше или позже, rrdtool интерполирует значения и выровняет их по десятисекундным границам.

Последние две строки описывают источник данных и архив, в котором они будут храниться.

Источник данных (Data source, DS)

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

Наш параметр —  DS:cpu:GAUGE:20:0:100 — буквально значит следующее:

  1. Создай источник данных (DS)
  2. под названием cpu
  3. и с типом GAUGE.
  4. Если в течение 20 секунд (heartbeat interval) новых данных не поступает — сохраняем UNDEFINED.
  5. Значения варьируются между 0
  6. и 100.

Тип GAUGE говорит rrdtool интерпретировать входящие значения «как есть». Есть еще типы COUNTER, DERIVE и ABSOLUTE, для которых rrdtool сохраняла бы не значение, а скорость, с которой они меняются (берем текущее значение, вычитаем предыдущее, делим на --step ). С шагом   --step , heartbeat-параметром и встроенной интерполяцией раз в 10 секунд в базе будет появляться новое значение, нравится нам это, или нет. Это значение называются первичной точкой данных (Primary Data Point, PDP)

Циклический архив (Round-robin archive, RRA)

RRA — это агрегированное временное окно с данными. Как и в случае с DS, в базе их может быть несколько. Наш единственный архив  RRA:AVERAGE:0.5:6:120  расшифровывается так:

  1. Создай циклический архив (RRA)
  2. длиной в 120 элементов,
  3. каждый из которых это среднее (AVERAGE)
  4. от 6-ти первичных точек данных.
  5. Если больше половины (>0.5) значений в шестерке — UNDEFINED, в архив так же идёт UNDEFINED.

В итоге получится архив, который будет хранить поминутные (10 секунд * 6) средние значения загрузки CPU за последние два часа (10 секунд  * 6 * 120). Один элемент архива называется консолидированной точкой данных (Consolidated Data Point, CDP), а AVERAGE — консолидирующей функцией (Consolidation Function, CF). Первичные и консолидированные точки связаны между собой вот так:

  • CDP1 = CF(PDP1, PDP2, …, PDPn)
  • CDP2 = CF(PDPn+1, …, PDP2n)
  • и т. д.

Кроме AVERAGE есть и другие консолидирующие функции: MIN, MAX, и LAST.

Добавляем данные в RRD

Добавление новых данных в циклическую базу тоже идёт через rrdtool. Так как время влияет на то, как и куда будут сохраняться значения, его нужно передавать вместе с ними. Например, можно добавить значение с меткой «сейчас»:

или с юниксовым временем (количеством секунд, прошедших с 1970-01-01):

или с количеством секунд «назад»:

Кроме цифровых значений в базу можно сохранить ‘U’ — UNDEFINED.

Хотя данные и можно добавлять руками через командную строку, будет разумнее, если это будет делать скрипт или другая программа.

Создание графиков из RRD

rrdtool умеет не только хранить данные, но и рисовать. Команда для создания графиков может принимать на вход миллион параметров, но для демки хватит всего нескольких основных.

На моей виртуальной Убунте уже несколько дней работает collectd, который успел сделать стайку RRD файлов: cpu-system.rrd, cpu-user.rrd и тому подобных. Чтобы сделать вменяемый график по первым двум файлам, достаточно такой команды:

Результат — вполне ничего:

rrdtool: cpu graph

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

DEF (Definition), по сути, это объявление переменной, а строка целиком —  'DEF:user_avg=cpu-user.rrd:value:AVERAGE' — означает: «начиная с этого момента под user_avg имеется ввиду циклический архив со средними для DS с именем value, что в cpu-user.rrd’. Так как в cpu-user.rrd может быть больше чем один архив, rrdtool выберет тот, который по временному диапазону больше подходит к данному графику.

CDEF (Calculated Definition), с другой стороны, это тоже объявление, но вычисляемое. Его значение — математическое или логическое выражение в обратной польской записи, через которое обычно пропускают ряд с данными. В нашем примере таких три. При помощи первых двух, вроде   user_clean=user_avg,UN,0,user_avg,IF , мы объявляем user_clean и system_clean, которым присваиваем значения из user_avg и system_avg, но нулями вместо UNDEFINED. Третье выражение — user_stack=system_clean,user_clean,+ — вводит новый ряд, который суммирует user_clean и system_clean, чтобы получить общую загрузку

Наконец, AREA и LINE1 отвечают за саму отрисовку. На вход им подаётся переменная с данными, цвет, и, опционально, имя для легенды. Есть еще LINE2 и LINE3, которые отличаются только толщиной линии.

Мораль

rrdtool приятная маленькая утилитка, которая умеет эффективно хранить временные ряды данных, а так же строить по ним графики. Размер создаваемых баз данных фиксирован, что тоже плюс. Так как она работает из командной строки, то её легко встраивать в другие утилиты, начиная от скриптов, и заканчивая системными процессами. На фоне этого совсем не удивительно, что collectd выбрал rrdtool для хранения своих данных по-умолчанию.

Краткое введение в rrdtool: 6 комментариев

  1. Спасибо, Павел!
    Я начинал читать блог из интереса к жизни в Канаде, а теперь больше жду технические статьи 🙂

  2. Привет.
    Помоги, пожалуйста, разобраться: получаю ошибку при использовании $res = rrd_graph($file, $options) -> «Call to undefined function rrd_graph()»? На rrd_error ошибки нет, rrd_fetch прекрасно отрабатывает, а вот именно rrd_graph не хочет работать. Можеь быть что для rrd_graph недоустановился какой-то пакет? Или права на папку с файлами rrd не те? Спасибо

    1. Привет!
      PHP не мой профиль, поэтому тут вряд ли помогу. Но я бы ставил на проблему передаваемыми в rrd_graph аргументами, а не на пропущенный пакет. В доках никаких дополнительных зависимостей нет, а на Stack Overflow у кого-то уже была точно такая же ошибка с rrd_xport, потому что $options были неправильного формата, так что «Call to undefined function» может бросаться по разным причинам. По идее, у fetch и graph должны быть одинаковые параметры, так что если fetch сработал, твои $options правильные и проблема должна быть где-то в $file. Может, не хватает прав на запись, или в пути какая промежуточная папка не создана. Можно ещё поэксперементировать с форматом выходной картинки: если PNG валится, может, SVG прокатит, и тогда мы возвращаемся к пропущенным неявным зависимостям.
      Как-то так.

      1. Привет. Может кому пригодится: у меня показывало ошибку Call to undefined function rrd_graph в рядке, где я вызывал данную функцию. В php есть функция get_defined_functions, так от если вызвать print_r(get_defined_functions); exit(); , например, в индексном файле, то покажутся все доступные php-фунтции и если в их списке нет rrd_graph, то беда в настройках на сервере. У меня были все, кроме rrd_graph 🙂 правда я не знаю что именно начальство делало на сервере — вроде как переустановили всю ту беду с rrdtool, ребутнули сервак и все заработало. Ну а если функция есть — то тогда действительно проблема в опциях.

        1. Мда, загадочно. Но спасибо, что поделился. Я уж было задумался о скриптовых языках и несовершенстве этого мира, как вспомнил, что недавно отлавливал ускользающий NuGet пакет Compression для .NET приложения: всё хорошо компилировалось, но при запуске приложение тут же выпадало с RuntimeExeption, Compresison.ZipFile не найден. Нигде порядка нет.

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

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