InfluxDB в Home Assistant (Часть 2)

В долгожданной второй части мы научимся писать запросы в InfluxDB, используем это знание для создания Continuous Queries, экономящих место на диске, а также установим программный агент telegraf для сбора и хранения различных метрик наших домашних систем в InfluxDB. При написании использовался Home Assistant версии 0.92.2 и hassio аддон InfluxDB версии 3.0.5 (InfluxDB 1.7.6).

Запросы из web-интерфейса InfluxDB

Создадим запрос для получения данных от одного из наших датчиков. Переходим на закладку Explore, создаём наш первый запрос (Query) и нажмём кнопку Submit Query:

select * from home_assistant.autogen.temp group by entity_id

Должно получиться что-то вроде такого:

1558471912377

На закладке Visualization->Visualization Type можно выбрать между линейным графиком (Line) или табличным представлением (Table) данных из запроса. Для графика также нужно включить легенду, чтобы понимать, какие линии к чему относятся, делается это в разделе Visualization->Customize->Static Legend. Чтобы увидеть легенду нужно переключиться на другой тип графика и обратно.

1558472482126

Теперь попробуем уменьшить количество точек путём усреднения (группировки) существующих значений. Например, возьмём среднее значение за каждый час.

select mean(value) from home_assistant.autogen.temperature group by time(1h), entity_id

Здесь mean это функция, применяемая к группам значений value, каждая группа представляет собой часовой срез данных. Как видим, на внешнем виде графика это никак не отразилось, зато позволило существенно уменьшить количество данных, требуемых для его построения.

1558472683228

InfluxQL также предлагает большое количество других функций кроме mean, которые можно использовать в своих запросах.

Теперь мы практически готовы к созданию т.н. Continuous Queries, которые будут автоматически выполнять подобные запросы с указанной периодичностью и сохранять результаты в другой measurement с нужной нам retention policy. Если эти слова вам ничего не говорят, рекомендую освежить терминологию из первой части

Важный момент 1

Если в запросе фигурирует дата и время, парсер InfluxDB ожидает, что дата указана во временной зоне UTC, так как данные в InfluxDB также хранятся в UTC. Однако при выводе информации клиент (в нашем случае - веб-интерфейс InfluxDB) будет услужливо конвертировать время в локальное, используя настройки браузера, поначалу это может запутать:

select value from home_assistant.year.temperature where time = '2019-05-21 21:00:00' group by entity_id 

1558536950676

Как видно, к временным меткам результата система добавила 3 часа, так как я нахожусь в часовом поясе UTC+3. Для указания правильной таймзоны можно добавить параметр tz в конец запроса:

select value from home_assistant.year.temperature where time = '2019-05-22 00:00:00' group by entity_id tz('Europe/Moscow')

1558539126451

Список возможных временных зон можно посмотреть здесь.

Важный момент 2

Важно понимать, что из-за архитектуры InfluxDB точки, имеющие одинаковые timestamps, будут сохранены как одна, если у них одинаковые теги (или теги отсутствуют). А точнее, из двух значений температуры, имеющих одинаковую временную метку и тэг, будет сохранено только то значение, которое пришло последним, так как оно перезатрёт предыдущее. Почему это важно: это правило работает и для любых запросов, в т.ч. Continuous Queries. Когда мы создаём CQ для получения значений (value) данных от двух и более сенсоров в одном measurement, важно указывать хотя бы один группирующий тэг, по которому InfluxDB сможет отделить значения одного датчика температуры от другого, иначе в результате мы получим список произвольных значений температуры, никак не привязанных к датчикам, например:

>select mean(value) from home_assistant.autogen.temperature group by time(1d)
name: temperature
time                 mean
----                 ----
2019-05-19T00:00:00Z 17.75294117647059
2019-05-20T00:00:00Z 19.30921052631579
2019-05-21T00:00:00Z 21.379761904761903
2019-05-22T00:00:00Z 19.62985074626866

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

>select mean(value) from home_assistant.autogen.temperature group by time(1d), entity_id
name: temperature
tags: entity_id=bedroom_temperature
time                 mean
----                 ----
2019-05-19T00:00:00Z 24.661538461538463
2019-05-20T00:00:00Z 24.804347826086957
2019-05-21T00:00:00Z 25.37941176470588
2019-05-22T00:00:00Z 25.353846153846156

name: temperature
tags: entity_id=yandex_weather_apparent_temperature
time                 mean
----                 ----
2019-05-19T00:00:00Z 12
2019-05-20T00:00:00Z 15.620689655172415
2019-05-21T00:00:00Z 18.653846153846153
2019-05-22T00:00:00Z 16.041666666666668

name: temperature
tags: entity_id=yandex_weather_temperature
time                 mean
----                 ----
2019-05-19T00:00:00Z 14.818181818181818
2019-05-20T00:00:00Z 18.5
2019-05-21T00:00:00Z 18.666666666666668
2019-05-22T00:00:00Z 15.941176470588236

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

Создаём Retention Policy

Первым делом создадим Retention Policy, которая будет определять, как долго InfluxDB будет хранить наши данные. Создадим политику для хранения данных в течение года:

CREATE RETENTION POLICY "year" ON "home_assistant" DURATION 52w REPLICATION 1

Не нужно пугаться сообщения “Your query is syntactically corect but returned no results”, наш запрос действительно не возвращает никаких данных, только создаёт сущность в InfluxDB.

Также можно создать политику, хранящую данные вечно:

CREATE RETENTION POLICY "infinite" ON "home_assistant" DURATION INF REPLICATION 1

Убедимся, что политики были созданы:

> show retention policies on home_assistant
name     duration  shardGroupDuration replicaN default
----     --------  ------------------ -------- -------
autogen  168h0m0s  168h0m0s           1        true
infinite 0s        168h0m0s           1        false
year     8736h0m0s 168h0m0s           1        false

Больше про создание Retention Policy можно узнать из официальной документации.

Создаём Continuous Query

Continuous query - механизм InfluxDB, позволяющий автоматически выполнять заданный запрос через указанные промежутки времени. Что более ценно, результат запроса может быть записан в другой measurement со своей retention policy. Именно так всё и работает, мы создаём запрос на получение агрегированных данных какого-нибудь датчика за последний час или полчаса и сохраняем его в новый measurement, с более продолжительным периодом хранения данных. Нет нужды говорить, что можно создать несколько measurements, например, хранить минутные данные в течение недели, часовые данные в течение месяца и, например, дневные данные - бесконечно. Схема ограничена только вашей фантазией.

CREATE CONTINUOUS QUERY "cq_1h_temp10" ON "home_assistant" 
BEGIN
  SELECT mean("value") AS value
  INTO "year"."temperature"
  FROM "autogen"."temperature"
  GROUP BY time(1h), entity_id
  FILL(previous)
END

Если за определённый час от какого-то датчика не было получено данных, по умолчанию continuous query вернёт пустую запись. Мы используем FILL(previous) чтобы вместо отсутствующего использовалось предыдущее известное значение.

Убедимся, что CQ была создана:

> show continuous queries
name: home_assistant
name          query
----          -----
cq_1h_temp10  CREATE CONTINUOUS QUERY cq_1h_temp10 ON home_assistant BEGIN SELECT mean(value) AS value INTO home_assistant.year.temperature FROM home_assistant.autogen.temperature GROUP BY time(1h), entity_id fill(none) END

Дело сделано, нужно подождать несколько часов и новый measurement year.temperature будет наполняться усреднёнными почасовыми данными температуры.

Больше про создание Continuous Query можно узнать из официальной документации.

Отображаем график для Continuous Query

Здесь всё просто, данные из measurement, созданного с помощью CQ не сильно отличаются от исходных данных от датчиков. Переходим в Grafana, выбираем дэшбоард или создаём новый, создаём новую панель (Add Panel->Add Query).

В поле From: 1558474408147

В поле GROUP BY: 1558475992633

В поле ALIAS BY:1558474995751

Последний параметр предписывает Grafana использовать значение тега entity_id в качестве меток для легенды графика, очень удобная функция. Получаем нужный нам график, который можно внедрить в Home Assistant способом, описанным в первой части статьи. Название данных в легенде можно переопределить, щелкнув по ним в режиме редактирования панели.

Пробуем Telegraf

Зачем: возможность сбора и анализа важных метрик со всех машин в домашней сети, возможность сбора метрик, не поддерживаемых Home Assistant, например, утилизацию процессора и памяти каждым из контейнеров hassio.

telegraf - микроскопическая утилита, состоящая из одного файла без всяких зависимостей, очень бережно относящаяся к ресурсам компьютера. Как, впрочем, любая программа, написанная на Go, в том числе и сама InfluxDB. Telegraf занимается тем, что собирает огромный список метрик и умеет передавать их в InfluxDB для хранения и анализа.

Естественно, с помощью конфигурации можно указать, какие метрики нас интересуют. К сожалению, до сегодняшнего момента никто не сделал аддона для hassio, поэтому единственная возможность установить эту замечательную утилиту - это доступ к ОС хоста. Возможны два варианта установки - установка вручную на нужный хост или установка в виде docker контейнера. Мы попробуем второй путь, как наиболее быстрый и удобный.

Установка telegraf в docker container

Для того, чтобы использовать telegraf, нам потребуется что-то из перечисленного ниже:

Ниже описан вариант, подходящий для пунктов 1, 2 и 3. Откроем командную строку ОС и убедимся, что докер доступен и работает:

user@host:~$ docker ps

Вывод этой команды должен содержать информацию о всех работающих контейнерах. Создадим папку, в которой будет храниться конфигурация docker конейнера telegraf:

user@host:~$ mkdir -p ~/docker/telegraf && cd ~/docker/telegraf

Загрузим последнюю версию конфигурационного файла telegraf:

user@host:~$ wget https://github.com/influxdata/telegraf/raw/master/etc/telegraf.conf

Создадим пользователя telegraf в InfluxDB аналогично тому, как мы делали в первой части с пользователем homeassistant:

1558988683556

Также создадим базу данных telegraf (см. первую часть статьи) и добавим в неё retention policy day с продолжительностью хранения данных один день:

1558988936151

Небольшое время хранения не позволит базе сильно разрастись из-за часто изменяющихся даных вроде загрузки процессора. Когда пользователь и БД созданы, нам нужно отредактировать конфигурационную секцию outputs.influxdb файла telegraf.conf как указано ниже:

[[outputs.influxdb]]
  urls = ["http://a0d7b954-influxdb:8086"] 
  database = "telegraf"
  retention_policy = "day"
  precision = "s"
  timeout = "5s"
  username = "telegraf"
  password = "password"

Теперь следует отредактировать конфигурационную секцию inputs. По умолчанию там уже активны метрики, относящиеся к использованию процессора/памяти и дисков. Их можно оставить как есть или закомментировать ненужное. А заодно добавим метрики докер-контейнеров, чтобы знать, например, когда один из контейнеров начал занимать избыточное количество ресурсов. Раскомментируем секцию inputs.docker и отредактируем следующие строки:

[[inputs.docker]]
  endpoint = "unix:///var/run/docker.sock"
  container_names = []
  timeout = "5s"

Запустим докер контейнер:

user@host:~$ docker run -d --name telegraf --restart=always --net="hassio" -v /etc/localtime:/etc/localtime:ro -v /home/user/docker/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro -v /var/run/docker.sock:/var/run/docker.sock telegraf:latest

Путь /home/user/docker/telegraf/telegraf.conf должен указывать на папку с файлом telegraf.conf. Данная команда подразумевает, что контейнер с telegraf запускается на той же машине, что и hassio, поэтому ключ --net="hassio" позволяет telegraf “видеть“ другие контейнеры во внутренней сети hassio. Если контейнер telegraf запущен на другой машине, в параметрах команды run нужно указать --net="host", а в конфигурационном файле telegraf вместо http://a0d7b954-influxdb:8086 указать имя или IP адрес машины, на которой установлен hassio с контейнером InfluxDB. Проверим логи контейнера telegraf:

user@host:~$ docker logs telegraf
# docker logs telegraf
2019-05-27T20:38:55Z I! Starting Telegraf 1.10.4
2019-05-27T20:38:55Z I! Using config file: /etc/telegraf/telegraf.conf
2019-05-27T20:38:55Z I! Loaded inputs: disk diskio kernel swap system cpu mem processes docker
2019-05-27T20:38:55Z I! Loaded aggregators:
2019-05-27T20:38:55Z I! Loaded processors:
2019-05-27T20:38:55Z I! Loaded outputs: influxdb
2019-05-27T20:38:55Z I! Tags enabled: host=c85cf92dfb59
2019-05-27T20:38:55Z I! [agent] Config: Interval:10s, Quiet:false, Hostname:"c85cf92dfb59", Flush Interval:10s

Всё в порядке, ошибок нет, можно идти в InfluxDB и проверять, что данные от telegraf сохраняются в базе. Как видим, на закладке Explore появилась база данных telegraf и набор measurements с метриками нашего хоста:

1558990070648

Ниже приведён пример InfluxDB запроса, с помощью которого можно построить график потребления ресурсов центрального процессора нашими docker контейнерами:

SELECT usage_percent FROM "telegraf"."day"."docker_container_cpu" where time > :dashboardTime: group by container_name

В качестве measurement здесь используется docker_container_cpu, а в качестве field - usage_percent. Этот запрос можно модифицировать путём замены этих двух параметров на другие из соответствующих колонок Measurement & Tags и Fields:

1558995518896

А вот так это можно сделать в Grafana. Первым делом нужно создать дополнительный источник данных для нашей свежесозданной БД telegraf аналогично тому, как мы делали в первой части статьи для БД home_assistant. Выбираем источник в выпадающем списке Queries to и формируем запрос как показано ниже:

1558991928466

На закладке Visualization нужно выбрать следующие параметры:

1558992214768

Если планируется хранить метрики, собираемые telegraf, в течение длительного времени, то можно (и даже нужно) создать Continuous Query, усредняющую результаты аналогично тому, как это было показано выше. Полученный график можно легко интегрировать в UI Lovelace, как это сделать было показано в первой части.

Метрики, собираемые telegraf

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

На сладкое: запросы InfluxQL из интерфейса командной строки (CLI)

Большинство запросов InfluxQL можно выполнить из веб-интерфейса InfluxDB (Chronograf). Иногда это не очень удобно, особенно когда нужно скопировать полученные данные куда-то ещё.

Но для упоротых юниксоидов у меня есть подарок: с базой данных InfluxDB можно работать из командной строки! Чтобы начать работу, нужно запустить утилиту influx внутри docker контейнера influxdb, это можно сделать двумя способами:

  1. Из командной строки операционной системы хоста (если hassio установлен на обычную linux машину)
  2. С помощью hassio аддона portainer (когда нет доступа к командой строки хоста, например, в hassos)

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

Запуск из командной строки хоста

user@host:~$ docker exec -it addon_a0d7b954_influxdb influx -precision rfc3339
Connected to http://localhost:8086 version 1.7.2
InfluxDB shell version: 1.7.2
Enter an InfluxQL query
>

Аутентифицируемся и подключаемся к базе данных home_assistant:

> auth
username: homeassistant
password:
> use home_assistant
Using database home_assistant

Важное отличие от запросов через веб-интерфейс InfluxDB (Capacitor). Так как мы указали БД, с которой будем работать, в будущих запросах её указывать не нужно. Получим список measurements:

> show measurements
name: measurements
name
----
binary
downsampled_temp
energy
humidity
temperature

Запросим список полей:

> show field keys
name: binary
fieldKey          fieldType
--------          ---------
No motion since   float
battery_level     float
device_class_str  string
friendly_name_str string
state             string
value             float

name: energy
fieldKey                fieldType
--------                ---------
friendly_name_str       string
icon_str                string
unit_of_measurement_str string
value                   float

name: humidity
fieldKey                fieldType
--------                ---------
battery_level           float
device_class_str        string
friendly_name_str       string
unit_of_measurement_str string
value                   float

name: temperature
fieldKey                fieldType
--------                ---------
battery_level           float
device_class_str        string
friendly_name_str       string
unit_of_measurement_str string
value                   float

Запросим данные одного из measurements:

> select value, battery_level, entity_id from autogen.temperature WHERE time > now() - 3h
name: temperature
time                           value battery_level entity_id
----                           ----- ------------- ---------

2019-05-28T13:37:05.37566592Z  17                  yandex_weather_apparent_temperature
2019-05-28T14:37:06.288192Z    18                  yandex_weather_temperature
2019-05-28T15:37:05.333918976Z 17                  yandex_weather_temperature
2019-05-28T15:37:05.36456192Z  14                  yandex_weather_apparent_temperature

В примере выше мы получаем значения полей value и battery_level, а также значение тэга entity_id и ограничиваем выборку последними тремя часами. В данном случае клиент не приводит временные метки к локальному времени, поэтому время записей возвращается в UTC. Так как я живу в часовом поясе UTC+3, моё локальное время последней записи будет 18:37:05.

Используем другую retention policy:

> select value, entity_id from year.temperature
name: temperature
time                 value              entity_id
----                 -----              ---------
2019-05-21T21:00:00Z 25.3               bedroom_temperature
2019-05-21T21:00:00Z 12                 yandex_weather_apparent_temperature
2019-05-21T21:00:00Z 13                 yandex_weather_temperature
2019-05-21T22:00:00Z 25.4               bedroom_temperature
2019-05-21T22:00:00Z 12.5               yandex_weather_apparent_temperature
2019-05-21T22:00:00Z 13.5               yandex_weather_temperature
2019-05-22T00:00:00Z 25.5               bedroom_temperature
2019-05-22T00:00:00Z 11                 yandex_weather_apparent_temperature
2019-05-22T00:00:00Z 12                 yandex_weather_temperature

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

Заключение

Во второй части мы научились создавать Continuous Queries, которых может быть произвольное количество, как и measurements, которые хранят агрегированные данные. Также нам удалось приспособить telegraf для сбора огромного числа метрик компьютеров нашей сети. Если у этой заметки когда-нибудь появится третья часть, в ней я поделюсь опытом централизованного сбора логов с большинства устройств домашней сети, а также автоматического информирования об ошибках, возникающих в этих логах.