Я хочу поделиться своим опытом использования Zabbix для анализа проблем с производительностью PostgreSQL, используя расширение pg_stat_statements.
pg_stat_statements
Что такое Zabbix, объяснять, я думаю, не нужно, раз вы уже зашли в эту статью. Пару слов про pg_stat_statements. Это расширение (extension) PostgreSQL, которое поставляется в пакете contrib (например, postgresql14-contrib) вместе с другими официальными пакетами из репозитория PostgreSQL. Для того, чтобы оно работало, необходимо прописать загрузочный модуль в shared_preload_libraries, после чего этот модуль начинает собирать статистику (метрики), сгруппированную по SQL-запросам (query). Статистика разнообразна: длительность выполнения запроса; использование запросом различных ресурсов, например, различных буферов в памяти. Такие запросы как SELECT, UPDATE, INSERT и DELETE он пытается преобразовывать: удаляет содержащиеся в них данные, потом ищет похожие и объединяет их статистику.
Всё это позволяет выявлять различные проблемы, связанные с производительностью. Например, можно использовать это расширение для поиска наиболее долгоиграющих запросов (поиск по max_exec_time
); запросов, создающих наибольшую нагрузку на сервер (поиск по total_exec_time
); а потом можно будет использовать другие инструменты (EXPLAIN ANALYZE или auto_explain) для анализа этих запросов. Что касается буферов памяти, то тут можно смотреть на метрики shared_blks_*
, они соответствуют конфигурационной опции shared_buffers
и позволяют выяснить, когда она недостаточная. Метрики local_blks_*
соответствуют конфигурационной опции temp_buffers
, а метрики temp_blks_*
— опции work_mem
. Есть ещё конфигурационная опция, тоже отвечающая за размер буферов памяти, maintenance_work_mem
, но я пока не смог выяснить, можно ли её нехватку заметить по метрикам pg_stat_statements
, и если да, то по каким. Если кто знает, пишите в комментариях.
Метрики по нехватке shared_buffers
(кэш ОЗУ) можно посмотреть и другим способом, в pg_stat_database
. В pg_statio_user_tables
, pg_statio_user_indexes
и pg_statio_user_sequences
отдельно метрики по использованию кэша для таблиц, индексов и последовательностей. Эту статистику уже собирает официальный PostgreSQL плагин для второго агента Zabbix. Pg_stat_statements
может быть интереснее тем, что помимо информации по использованию shared_buffers
даёт информацию по использованию temp_buffers
и work_mem
. А ещё его можно настроить таким образом, чтобы в эту статистику попадала только работа приложения, а не служебных запросов, например от Zabbix.
Для того, чтобы получить доступ к этим метрикам, нужно в любой базе данных создать расширение pg_stat_statements
(не нужно создавать в каждой), тогда там появится view pg_stat_statements
со всеми запросами. Обычный пользователь может видеть только свои запросы, а чтобы видеть все, нужен либо суперпользователь, либо, начиная с 10-го PostgreSQL, нужно быть в роли pg_read_all_stats
или pg_monitor
.
И тут появляются две проблемы, ради решения которых и пришлось прикручивать Zabbix. Первая: оптимизацией запросов к базам данных в современном мире сейчас занимаются веб-программисты (раньше для этого была специальная должность DBA). А у веб-программистов нет прямого доступа к SQL-серверу прода. Использование Zabbix в качестве промежуточного звена позволит дать им ограниченный доступ к метрикам запросов в эксплуатационной системе, без прямого SQL-доступа.
Вторая проблема: если установить pg_stat_statements
«на всякий случай» и потом обратиться к нему, когда в эксплуатации случился какой‑то «ахтунг», то от этого будет мало пользы. Там окажется статистика по всем запросам, допустим, за полгода. И найти среди них то, что создаёт проблему прямо сейчас или на этой неделе, будет весьма затруднительно. Для того, чтобы метрики были актуальны и соответствовали действительности, необходимо регулярно сбрасывать статистику, например раз в сутки в полночь. Сохраняя при этом информацию за предыдущие сутки, скажем, о наиболее проблемных запросах. Тогда можно будет посмотреть, например, как менялось распределение проблемных запросов в течение недели. А также, если понадобится срочно решать внезапно появившуюся проблему, можно будет быть уверенным, что вся информация, которая в текущий момент находится в pg_stat_statements
, содержится только за текущий день. И тут тоже на помощь приходит Zabbix: он будет в полночь сохранять метрики и после этого сбрасывать (reset) статистику.
Проблемы совместимости pg_stat_statements
Предлагаемое мной решение сможет работать в PostgreSQL версий 10-16 (впрочем, на 16 ещё не тестировал), а также в версиях 9.5 и 9.6 c оговорками. Конечно, в такой современной компании, как наша, такие древние версии давно уже не используются, но они могут использоваться в каких-то других, «плохих» компаниях.
Первая проблема совместимости, на которую стоит обратить внимание, возникла в 13-й версии PostgreSQL. Если раньше (до 13-й) статистика собиралась по полной длительности выполнения запроса, учитывая время, затраченное и планирование, и на выполнение, и записывалось в поле, например, max_time
, то начиная с 13-й версии считается уже отдельно метрика длительности планирования max_plan_time
и отдельно — длительности исполнения max_exec_time
. Причём по умолчанию длительность планирования не собирается (всегда 0). Чтобы её собирать, нужно указать специальную конфигурационную опцию pg_stat_statements.track_planning
. Для моей задачи это означает, что придётся писать разные SQL-запросы для 13-й (и старше) и для 12-й (и младше) версий.
Вторую проблему не назвать проблемой совместимости, но важно её учесть. Статистика сбрасывается вызовом функции pg_stat_statements_reset
. Начиная с 12-й версии PostgreSQL можно указывать для этой функции базу данных, для которой следует сбросить статистику, в то время как до 12-й версии эта функция сбрасывает статистику для всех баз данных сразу. Это не проблема совместимости, потому что начиная с 13-й можно продолжать по-старинке вызывать pg_stat_statements_reset()
без параметров и сбрасывать всю статистику. Но для Zabbix эта функциональность имеет важное значение. Разумеется, правильным было бы помещать статистику pg_state_statements
в элементы данных (item) Zabbix, которые привязаны к конкретной базе данных. И потом, например, давать к ним доступ конкретным веб-программистам, которые отвечают за этот сервис. Начиная с 12-го PostgreSQL сделать это в Zabbix легко: единым скриптом собираешь метрики для нужной базы и тут же сбрасываешь статистику. А до 12-го всё сложнее: приходится собирать все метрики по всем базам на сервере сразу и сбрасывать статистику для всего сервера. А потом эти метрики надо разложить так, чтобы они оказались в элементах данных, которые относятся к конкретным базам данных.
Что с 9.5 и 9.6? Там нет такой роли как pg_monitor
или pg_read_all_stats
. Поэтому придётся запускать Zabbix под суперпользователем: либо выдавая суперпользователя для zbx_monitor
, либо для этих запросов подключая Zabbix под другим пользователем, у которого эти права есть, например postgres.
В современном Zabbix используется второй агент. И под него существует PostgreSQL плагин, который позволяет оптимизировать доступ к базе данных. Он использует для всех запросов одно и то же подключение (нет необходимости подключаться для каждого запроса), а запросы оформляет в prepared statement, что уменьшает накладные расходы. Это особенно важно, если запросы планируется выполнять часто. Но, к сожалению, этот плагин написали веб-программисты на Go, и поскольку люди они самостийные и творческие, они использовали не стандартную библиотеку libpq, а что-то своё. И поэтому возникло две проблемы.
Первая: этот плагин попросту не работает с PostgreSQL <10. Возможно, мог бы частично работать для каких-то запросов, но они проверяют версию сервера в самом начале и разрывают соединение, если версия меньше 10. В текущей реализации я использую как соединения через psql (библиотека libpq), так и через плагин, там, где это оправданно. При текущей реализации сбор статистики за день сможет работать, а сбор статистики на текущий момент времени, точнее метрики скоростей изменения статистики, в версиях 9.5-9.6 работать не смогут, потому что они реализованы через плагин. Если потребуется, чтобы всё работало с базами 9.5-9.6, то надо будет переписать реализацию через psql. Для этого можно использовать всё тот же второй агент, переходить на первый не нужно.
Вторая проблема: если подключаться через файловые сокеты, то URI для таких соединений для плагина на Go принципиально отличается от аналогичного для библиотеки libpq (например, приложения psql). В libpq для соединения через файловые сокеты достаточно не указать в URI имя хоста, а для Go надо указывать путь в файловой системе к этому файловому сокету. Вопрос «Что лучше?», быть может, спорный и выходит за рамки этой статьи. Но такую разницу точно надо учитывать. И хранить различные URI: один для доступа через psql, другой для доступа через плагин от Zabbix.
В общем, в текущей реализации версии 9.5-9.6 не поддерживаются, но если сильно надо, то под них несложно переписать.
Подготовка PostgreSQL
Чтобы всё работало, серверы PostgreSQL должны быть определённым образом подготовлены. Для начала, обычные рутины для Zabbix-мониторинга: должен быть заведён пользователь для подключения Zabbix, обычно это zbx_monitor
, и ему должны быть выданы нужные права, обычно это роль pg_monitor
.
GRANT pg_monitor TO zbx_monitor;
Чтобы работал pg_stat_statements
, необходимо добавить загрузочный модуль в конфигурационную опцию shared_preload_libraries
(потребуется перезапуск сервера). И нужно создать расширение в базе postgres
.
CREATE EXTENSION pg_stat_statements;
База postgres
задумана как раз для таких вот системных утилит. В ней должны быть выданы права на запуск функции pg_stat_statements_reset
для пользователя, под которым будет заходить агент Zabbix для ежедневного сбора статистики.
GRANT EXECUTE ON FUNCTION pg_stat_statements_reset TO zbx_monitor;
Как аутентифицировать Zabbix в PostgreSQL? Самый естественный вариант, если агент Zabbix запущен на том же самом сервере, где и база данных, так обычно и делают. В этом случае можно было бы указать, что Linux-пользователь zabbix
может заходить в PostgreSQL под SQL-пользователем zbx_monitor
без пароля. Аутентификация при этом выполняется самой операционной системой. Для этого надо в pg_hba.conf добавить что-то вроде:
local all zbx_monitor peer map=zbx_monitor
И в файл pg_ident.conf:
zbx_monitor zabbix zbx_monitor
После таких правок не обязательно делать restart, достаточно reload. В некоторых «плохих» компаниях так не заморачиваются и попросту пишут другой способ аутентификации «trust» для локальных пользователей. Когда я впервые стал работать с PostgreSQL в 1999 году, уже тогда это считалось недопустимым и плохим тоном. В нашей компании, разумеется, так никто не делает.
Низкоуровневое обнаружение (Low Level Discovery)
Речь тут пойдёт о таком методе Zabbix как низкоуровневое обнаружение (LLD, Low Level Discovery). Суть его в следующем. В Zabbix создаётся специальное правило LLD, которое тестирует, например, Linux и ищет в нём определенного типа ресурсы. Для найденных ресурсов с помощью прототипов элементов данных (prototype item) создаются элементы данных (item), которые хранят ту или иную метрику ресурса. Так обнаруживаются, например, смонитрованные файловые системы или сетевые интерфейсы. Есть такое правило LLD (discovery rule) и у PostgreSQL плагина Zabbix.
Но это правило LLD у плагина не умеет корректно работать, если на Linux-сервере много серверов PostgreSQL. Оно может работать только с одним сервером (instance). Ему в качестве параметра надо передать URI для подключения к этому серверу, потом он туда заходит, находит все базы данных и для каждой из них создаёт набор элементов данных для хранения метрик. Всё это особенно странно, учитывая, что PostgreSQL всегда умел запускать несколько серверов на одном Linux-сервере. А поддержка этого в пакетах дистрибутивов, я помню, впервые появилась в Debian, очень давно. Не скажу точно, когда, я тогда ещё молодым был.
В нашей фирме это обычная практика, иметь по несколько PostgreSQL серверов на одном Linux сервере. В девелоперовском контуре их может быть несколько десятков на одном сервере, по одному на каждую команду. И почему профессионалы Zabbix это не учли, для меня большая загадка. Впрочем, они и system load делят на количество ядер у процессора, об этом как‑нибудь ниже. Один из способов, как тут можно выкрутится, это создавать в Zabbix «фейковые хосты» на каждый сервер PostgreSQL, и там указывать плагину PostgreSQL в качестве параметра URI к этому серверу. Выглядит нелепо, так делают в каких-то других, «плохих» компаниях, в нашей так никогда никто не делает.
Учитывая всё вышесказанное, появилось желание создать собственное правило LLD для серверов PostgreSQL, лишённое таких недостатков. И оказалось его очень просто сделать. Такие правила LLD прописываются в шаблоне (template), который можно подключить к нужному хосту в Zabbix прямо или опосредованно (через наследование шаблонов). Он будет создавать элементы данных для всех серверов и всех баз данных PostgreSQL, которые он там найдёт.
В конце статьи я приложу шаблон и все файлы. Правило LLD сделано через такую функциональность агента Zabbix как UserParameter
. К сожалению, в Zabbix нельзя использовать переносы строк в этой синтаксической конструкции, выглядит это совершенно нечитаемо, особенно на Хабре, где отсутствует перенос длинных строк во вставленном коде. Поэтому читателей я отсылаю к файлу в конце статьи, который нужно будет положить в /etc/zabbix/zabbix_agent2.d/odsamoylov.conf
.
Правило LLD Pg_srv
Правило LLD Pg_srv
основано на UserParameter
с ключом pg.servers
(в файле odsamoylov.conf)
. Это, по сути, shell-скрипт, который ищет файловые сокеты PostgreSQL в директории /var/run/postgresql/, и для тех серверов PostgreSQL, что он находит, заполняет макросы LLD, которые потом будут использоваться в прототипах для создания элементов данных для этого сервера. В этом правиле будут создаваться те элементы данных, которые соответствуют серверу, а не каждой базе данных. Например, это может быть версия сервера или размер WAL директории. Так уж исторически сложилось, что дистрибутивы на основе RedHat и Debian раскладывают файлы PostgreSQL по разным директориям. А здесь редкое исключение: и в RedHat, и в Debian файловые сокеты лежат в одной директории, так что работать будет и там, и там.
Список определяемых макросов LLD и что они значат:
{#POSTGRESQL_URI}
. Обычный URI для PostgreSQL, который принимают приложения на основе libpq, например psql. Вид:postgresql://hostname:5432
. Может использоваться, например, в прототипе триггера, чтобы таким образом указать базу данных, где произошла проблема. И потом к ней будет легко подключиться, просто скопировав этот URI к себе в командную строку для запуска psql. Может использоваться и для проверки сетевых подключений к этой базе данных типа пинга, если, конечно, база будет заранее открыта для таких проверок.{#URI}
. Сначала URI (который выше) я использовал и для именования элементов данных внутри Zabbix. Но потом столкнулся с тем, что у Zabbix под именование элемента данных выделено довольно мало места, а написать там хочется много. Поэтому это «укороченный» URI для таких случаев. Будет выглядеть как:5432/
. Ни на что не влияет, можно заменить в соответствии со своим чувством прекрасного.{#PG.URI}
. Этот URI для Zabbix плагина postgresql. По сути это путь к файловому сокету.{#PG.SERVER_VERSION_NUM}
. Для того, чтобы уже на уровне правила LLD можно было бы решать, какие прототипы использовать в зависимости от версии PostgreSQL, сюда я заношу версию сервера в видеshow server_version_num;
.{#HOSTNAME}
. Просто содержимое bash-переменной$HOSTNAME
, имя сервера, как определено в Linux. Есть аналогичная переменная, определённая в Zabbix, только там она будет такой, как она определена в конфиге Zabbix. Для моих целей Linux hostname был более информативным. Использовалась в прототипе триггера, который я, правда, удалил... Скажу по-другому: вы сможет использоваться в прототипе триггера, если его напишете.{#PG.PORT}
. Порт, на котором находится PostgreSQL-сервер. Используется в том числе для подключения к серверу Zabbix через файловый сокет с помощьюUserParameter
и psql.
Правило LLD Pg_db
Теперь про то, что актуально для каждой базы данных, например про её размер. В шаблоне правило LLD называется Pg_db
, основано на UserParameter
с ключом pg.databases
. К сожалению, в Zabbix нельзя сделать так. чтобы одно правило LLD определяло другое правило LLD. То есть чтобы правило для обнаружения серверов настраивало бы правило для обнаружения баз данных внутри серверов. Все правила LLD друг от друга независимы. Поэтому этот скрипт повторяет то же самое, что и предыдущий. Сначала анализирует файловые сокеты, определяет, какие серверы в наличии, а потом подключается к каждому из них и запрашивает, какие базы данных в этом сервере существуют. В списке по одной строчке на каждую базу данных. {#POSTGRESQL_URI}
, {#URI}
, {#PG.URI}
, {#PG.PORT}
, {#PG.SERVER_VERSION_NUM}
такие же, как и в предыдущем скрипте, только к URI добавлено имя базы данных. И отдельно задаётся {#PG.DATABASE}
с именем базы данных.
Прототипы элементов данных
Макросы LLD поступают в прототипы элементов данных, из чего получаются элементы данных, которые и будут хранить метрики. Как я уже говорил, есть сложность с тем, что pg_stat_statements
у разных версий PostgreSQL заметно отличаются. Поддержку 9.5 и 9.6 я не сделал, да их и разработчики PostgreSQL уже не поддерживают. Хоть добавить их и не сложно, но будет гораздо больше писанины и копипастов. А версии с 10 по 16 можно разделить на две группы по тому, как их можно обрабатывать:
Последние версии (с 13 и старше) характерны тем что там статистика по времени разделена на время планирования и время выполнения. Столбцы называются по-другому, и, как следствие, текст SQL-запроса будет другим. Для этих версий можно сбрасывать статистику отдельно для каждой базы данных, что значительно упрощает алгоритм. Если бы все базы были современными, то этим можно было бы и ограничиться.
А в группе с 10 по 12 версию общая длительность и планирования, и выполнения, столбы называются по-другому. И статистику сбрасывать приходится сразу для всех баз данных. То есть сначала нужно получить всю статистику на сервер, всю сбросить, а потом разложить её по полочкам, отдельно на каждую базу данных.
Имя каждого прототипа элементов данных будет, например, {#URI}->pgstmts.day.rows.v13
. Сначала идёт {#URI}
— это тот самый макрос LLD, который был определён правилом LLD, по сути идентификатор базы данных. После ->
идёт аббревиатура pgstmts
, это всего лишь сокращение от pg_stat_statements
, я сильно экономлю место. Следующее — либо day
, либо sec
. Day
означает, что эта статистика ежедневная, а sec
— ежесекундная, по сути секундная скорость, усреднённая на минуту. Такое разделение я поясню позже. Дальше идёт идентификатор метрики, например rows
. А затем идёт пояснение, к какой версии этот прототип относится: v13
— для версий PostgreSQL 13 и старше, v12
— для версий 10-12. Такой прототип превратится в название элемента данных, например :5432/database->pgstmts.day.rows.v13
(где database
— это название базы данных).
Для того, чтобы определить, какую версию использовать, применяется такой механизм Zabbix как замещения (overrides) внутри правила LLD. По умолчанию настроено так, что у прототипов с v13
выставлен флаг Обнаружение (Discover), то есть эти прототипы будут использоваться для низкоуровневого обнаружения. А у прототипов с v12
флаг Обнаружения (Discover) по умолчанию убран, они работать не будут. Замещение с названием pgstmts.*.v13
отключает флаг Обнаружение у прототипов v13
, если версия PostgreSQL меньше 13. Сделано не через численное сравнение, а через regexp, но смысл тот же. А Замещение с названием pgstmts.*.v12
включает Обнаружение для прототипов с v12
, если версия PostgreSQL будет 10-12. Сделано так, что для каждого сервера сработают прототипы либо v12
, либо v13
, и создадут нужные элементы данных.
Ежедневная статистика (day)
Для версий 13 и выше. Раз в сутки (время выставлено в 00:00) срабатывает элемент данных, полученный из прототипа из правила LLD Pg_db (для каждой базы данных) с именем {#URI}->pgstmts.day.json.v13
(тут в URI входит название базы данных). Конечно, нельзя ожидать, что время выполнения у всех будет ровно 00:00, потому что количество одновременно выполняемых процессов ограничено, но это не важно, нам же не нужна высокая точность в этом вопросе. Этот элемент получает данные из скрипта UserParameter
с ключом pgstmts.day.json.v13[port,database]
. А скрипт получает информацию из pg_stat_statements
по тем метрикам, которые интересны, и после этого, второй командой эту статистику сбрасывает. Сделать это через плагин для postgresql от Zabbix через функциональность CustomQueries
невозможно, потому что CustomQueries
подразумевает один запрос, который можно упаковать в PREPARED STATEMENT. Два запроса туда не упакуешь. Все нужные данные получаются единым запросом, упаковываются в JSON и записываются в Zabbix как текстовое поле. Но содержимое этого поля не хранится в истории, вместо этого JSON разбирается на части теми элементами данных, которые оформлены как зависимые.
Для версий 10-12 раз в сутки, также в 00:00, срабатывает элемент данных из правила LLD Pg_srv (один на каждый сервер) с именем {#URI}->pgstmts.day.json.v12
, вот только в URI имени базы данных нет. Пример такого имени :5432/->pgstmts.day.json.v12
. Туда заносится информация, полученная из скрипта UserParameter pgstmts.day.json.v12[port]
. Это упакованная в JSON вся нужная информация по всем базам данных сразу, после чего статистика сбрасывается. У Zabbix есть ограничение на размер возвращаемых таким образом данных. И если данные из pg_stat_statements
содержат очень длинные запросы (такие обычно генерируют ORM), да ещё по всем базам данных, то случается переполнение буфера (так было) и никакая информация не возвращается. Поэтому предпочтительнее метод, когда статистика собирается отдельно для каждой БД (для версий 13 и выше). Этот JSON с информацией по всем базам данных хранится в виде текстового поля в истории Zabbix 4 часа.
В два часа ночи запускается элементы данных уже из Pg_db, то есть для каждой базы данных. Имя прототипа, {#URI}->pgstmts.day.json.v12
, вроде бы такое же, но здесь в URI название базы данных уже входит, например :5432/database->pgstmts.day.json.v12
. Эти элементы данных вычисляемого типа (calculated). Они забирают JSON, полученный в предыдущем абзаце, с информацией по всем базам данных, и с помощью механизма Предобработки (Preprocessing) берут оттуда раздел, относящийся к своей базе данных. В результате получается ровно такой же элемент данных JSON с информацией по одной базе данных, который раньше был описан для версии 13, он также не хранится в истории Zabbix и нужен только для того, чтобы его разобрали на части зависящие от него элементы данных.
Ремарка про кибербезопасность. Как я уже говорил, pg_stat_statements
убирает данные из запросов типа SELECT, UPDATE, INSERT, DELETE. Но не убирает данные из запросов типа ALTER USER username WITH PASSWORD 'password'
. В самом начале тестирования я с удивлением обнаружил, что в Zabbix засветился пароль от пользователя, который никому видеть бы не следовало. Единственное, что я придумал, это отфильтровывать в pg_stat_statements
запросы от пользователя postgres. Другой нежелательный пользователь — zbx_monitor
: запросы от Zabbix одни из самых медленных, а это не то, что нужно веб-программистам для работы. Да и остальные запросы от служебных пользователей веб-разработчикам видеть ни к чему. Поэтому в UserParameter
в скриптах, в которых я забираю данные из pg_stat_statements
, я отфильтровываю запросы от нежелательных пользователей (postgres, zbx_monitor, pgbouncer).
Я думал о том, чтобы список таких пользователей оформить в виде параметра. Технически это возможно. Но есть опасность, потому что тогда пользователь, обладающий правами редактировать конфигурацию Zabbix, сможет настроить так, что начнёт видеть запросы от суперпользователя. Поэтому список нежелательных пользователей прописан в коде в конфигурационном файле в UserParameter
, и изменить его может только сисадмин с правами редактирования этого файла (обычно root) на этом сервере.
Итак, на этом этапе есть текстовый элемент данных (item) типа JSON, в котором находится вся нужная информация для конкретной базы данных, имя и ключ которого заканчивается либо на v13
, либо на v12
, но содержимое полностью одинаково. Если бы Zabbix был объектно-ориентированным языком, то можно было бы легко весь последующий код, объекты (items, графики, триггеры и т. д.) сделать универсальным. Но, к сожалению, Zabbix это не объектно-ориентированный язык программирования. Нельзя сделать зависимые объекты так, чтобы они настраивались на JSON-элемент данных v12
или v13
, в зависимости от того, который есть. Поэтому пришлось полностью дублировать код, создавать все объекты для v13
и потом их клонировать и переделывать на v12
. Если кто придумает, как этого можно избежать и всё сделать более элегантно, напишите в комментариях. При условии, что сами всё сделаете, протестируете и убедитесь, что работает. Я перепробовал разные варианты, и решение для Zabbix v6.0 не нашёл.
Сейчас я распишу, какие есть элементы данных, для v12 и v13 они совпадают.
top_max_time
— текстовое поле топ-5 запросов по критерию максимальная длительность выполнения.top_total_time
— текстовое поле топ-5 запросов по критерию суммарная длительность выполнения. Эти запросы создают максимальную нагрузку на сервер.max_time
— максимальная длительность выполнения запроса, выбранная из всех запросов за период с момента сброса статистики. У меня это сутки.mean_time
— средняя длительность всех запросов. Толку от этого мало, но может помочь оценивать производительность сервера в каких‑то ситуациях.min_time
— минимальная длительность выполнения, выбранная из всех запросов.calls
— так это поле называется вpg_stat_statements
, тут будет количество запросов в сутки. Резкое увеличение этого параметра может сигнализировать, что что‑то пошло не так.rows
— суммарное количество строк, которые запросы возвращают или обрабатывают в сутки. Необычное изменение значения тоже может навести на какие‑то умные мысли, что с базой не так.shared_blks_miss
— количество промахов мимо кэшаshared_buffers
, в процентах.local_blks_miss
— количество промахов мимо кэшаtemp_buffers
, в процентах.temp_blks_read
— тут количество промахов в процентах не посчитаешь, поэтому абсолютное значение. Говорит о том, что параметрwork_mem
недостаточен и поэтому PostgreSQL приходится сбрасывать данные на винчестер. Конкретно это значение — количество блоков, прочитанных с винчестера. Если памяти хватает, то всё содержится в памяти, с винчестера чтений не будет.
Если так получится, что к базе не было вообще никаких запросов, которые бы попали в JSON, то метрики будут отсутствовать и не будут занимать место.
Посекундная статистика (sec)
В Zabbix есть термины история и динамика (history и trends). В истории хранится история как она есть, которая собирается, например каждую минуту. В динамике хранится усреднённая статистика за большие периоды времени, например по суткам, и хранится дольше. История хороша для того, чтобы быстро решать внезапно появившиеся проблемы. Динамика хороша для оценки тенденций, анализа и прогнозов потребления ресурсов при нормальной работе базы данных.
Та статистика, которую я назвал «ежедневная» и которая собирается раз в сутки, это как бы «динамика» в терминах Zabbix. Но для быстрого решения внезапных проблем полезно иметь графики, которые показывали бы текущую ситуацию и позволяли бы быстро её оценивать. Не все метрики из вышеперечисленных можно так показывать, для многих необходимо сбрасывать статистику. Чтобы показывать раз в минуту top_total_time
, max_total_time
(топ списки запросов), mean_time
(среднее время) и т. д., надо будет и статистику сбрасывать раз в минуту. Большие издержки, толку мало. Но в то же время есть такие метрики, для которых характерно увеличение значения каждую минуту. Например, total_time
— суммарное машинное время, потраченное на запросы. Zabbix позволяет у такого параметра получить разницу между двумя соседними измерениями и разделить на количество секунд между этими измерениями. Получится скорость или первая производная для этой метрики, усреднённая за минуту (если измерения каждую минуту). Для таких параметров можно строить графики скорости хоть каждую минуту без сброса pg_stat_statements
. И они могут быть полезны для быстрой диагностики проблем.
Почему нужны оба способа сбора метрик? Ежедневная статистика полезна тем, что в ней есть метрики, которые иначе не получишь. Она была написана в первую очередь для того, чтобы ежедневно получать топ пять самых долгоиграющих запросов. Секундная статистика полезна тогда, когда что-то идёт не так и надо быстро продиагностировать проблему. Ещё она полезна, когда надо оценить графики в зависимости от времени суток. Например, если проблемы возникают в каком-то узком периоде времени и нужно понять, что там происходит. В общем, одна другую не заменит.
Поскольку секундная статистика снимается часто (раз в минуту) и не требует сброса, то её можно и нужно делать через такую функциональность PostgreSQL плагина от Zabbix как CustomQueries
. Это своеобразный аналог UserParameter
, только тут вместо shell-скрипта прописывается непосредственно SQL-запрос, который выполняется оптимальным образом, без постоянного запуска psql, переподключений к БД и т. д. Поддерживается постоянное подключение, и в нём все запросы выполняются в виде PREPARED STATEMENT. Нужные запросы лежат в файлах pgstmts.sec.json.v12.sql и pgstmts.sec.json.v13.sql. Вызываются они в прототипах элемента данных {#URI}->pgstmts.sec.json.v12
и {#URI}->pgstmts.sec.json.v13,
в правиле LLD Pg_db, раз в минуту. Потом этот JSON точно так же разбирается на части, причём у этих метрик вычисляется скорость изменения в секунду описанным выше способом. Получаются следующие элементы данных:
calls
— количество запросов в секунду;load
— аналог system load, об этом ниже;row
— количество обработанных строк в секунду;local_blks_hit
— количество попаданий в кэшtemp_buffers
;local_blks_read
— количество чтений мимо кэшаtemp_buffers
;shared_blks_hit
— количество попаданий в кэшshared_buffers
;shared_blks_read
— количество чтений мимо кэшаshared_buffers
;temp_blks_read
— количество чтений мимоwork_mem
.
Как уже отмечал, тут не получилось сделать метрику доли промахов мимо кэша. Вместо этого собираются метрики по количеству чтений блоков в секунду из кэша и мимо него, по другому не сделаешь. Но работать с процентами, конечно, удобнее и нагляднее. Поэтому есть ещё два вычисляемых элемента данных, которые вычисляют из перечисленных выше метрик промахи в процентах.
local_blks_miss
— доля промахов дляtemp_buffers
;shared_blks_miss
— доля промахов дляshared_buffers
.
Осталось объяснить, почему одна из метрик называется load
.
Load
Маленькая ремарка об истории этой метрики. Load, она же загрузка, это, наверное, самая первая метрика оценки загруженности Linux сервера с незапамятных времен. У неё очень простой смысл: это количество активных процессов на текущий момент времени. В load
попадают процессы, которые активно работают, потребляя процессор, или находятся в ожидании поступления информации с винчестера. Возможно, потому, что раньше микросхем «прямого доступа к памяти» не было и заносить данные с винчестера в ОЗУ приходилось тому же самому многострадальному процессору. А вот процессы, ожидающие поступления информации по сети, в load
не попадают. Так исторически сложилось. Возможно, я где-то тут смог и напутать, но только слегка, если кто более сведущ, пишите в комментариях.
Сама по себе load
в чистом виде никогда не использовалась, применялись только её усреднённые значения за одну, пять и пятнадцать минут. Что позволяло, сравнивая три числа, понять, является ли высокая загрузка пиковой или постоянной. Её можно было посмотреть в выводе команды uptime
, top
или в файле /proc/loadavg. Там она фигурирует как load average
, но так её никто не называл, потому что average
, очевидно, не является частью имени, а всего лишь характеристика того, что load
усреднена. Говорили просто «какой лоад?» или «какой был лоад?» Поскольку никаких других метрик load
на тот момент не было, то пояснений и не требовалось.
Потом появилась вторая знаковая метрика, у которой было нормальное собственное имя — CPU Load
. Вычислялась как загрузка каждого ядра процессора в процентах. И сразу стало понятно, что между старой доброй load
и CPU Load
есть какая-то связь. Если load
равна, например 3, то CPU Load
покажет какую-то нагрузку на примерно трёх ядрах процессора. В идеале, если бы быстродействие процессов зависело только от CPU, то при load
3 можно было бы ожидать, что CPU Load
покажет 100 % нагрузку на ровно трёх ядрах процессора. Но такое редко бывает. Например, когда купили новый сервер (на предыдущей работе), его нужно было протестировать, и я по знакомству запустил на нём задачку из теорфизики, что-то посчитать про металлический водород. Тогда да, все ядра процессора были на 100 % и load
был ровно столько, сколько ядер у процессора.
Но в обычной жизни, например при работе с базой данных, быстродействие лимитирует не процессор, а память или винчестер. CPU Load
вполне может показывать низкую нагрузку на каждое ядро, при том что load
будет немаленьким. Это не означает, что проблем нет и "процессор справляется", ровно наоборот: проблемы есть, и либо винчестер, либо ОЗУ не справляется, например, сервер свопит свою память, или данные не помещаются в кэш и читаются с винчестера. И поэтому делить load
на количество ядер процессора — несусветная глупость, которую можно увидеть и в Zabbix. Если у load
есть совершенно чёткий физический смысл (написал выше), то у поделенного на число ядер процессора никакого смысла нет. Даже если лимитирует только процессор, то есть load
1 и одно из ядер загруженно на 100 %, какой смысл будет у поделенного load
, равного 0,041666666666667?
После того, как появился CPU Load
, мне стало интересно, а нет ли у load
(также называемого load average или LA) какого-то реального названия? Повторюсь, average частью названия быть не может, CPU Load
может тоже как-то усреднённо вычисляться, не называть же его CPU Load average
? И где-то на просторах интернета я нашёл подходящее название: System Load
, или загрузка системы. Возможно, оно так и называлось, но поскольку конкурентов долгое время не было, то слово system из названия выпало, для краткости.
System Load
— полезная метрика, которая очень хорошо, гораздо лучше, чем CPU Load
, показывает порог, когда сервер теряет производительность, начинает подвисать, лагать, а порой и вовсе отказываться работать. Хотя такой порог у каждого сервера и нагрузки свой, современные машины с отключённым свопом и на твердотельных винчестерах гораздо лучше переносят высокие значения System Load
, подвисать начинают гораздо позже. И я захотел найти аналог этой метрики, но для баз данных. В идеале, чтобы можно было видеть, какой вклад каждая из баз данных привносит в System Load
.
Так появилась метрика {#URI}->pgstmts.sec.load.v13
, которая, по сути, является разницей суммарного времени, потраченного на запросы в секундах, поделенного на количество секунд между двумя измерениями. Этакий аналог System Load average 1 min
для базы данных, основанная на pg_stat_statements
. Не без недостатков: вся информация о потраченном времени приходит только в момент завершения запроса. Поэтому на коротких запросах, у которых длительность выполнения значительно меньше минуты, работает хорошо. Если же запросы превышают минуту, то длительность их загрузки не распределяется по длительности реального выполнения, а создаёт высокие пики в момент завершения запроса. Это, конечно, недостаток, хотя и польза тоже есть: очень хорошо визуально заметны запросы, выполняющиеся дольше одной минуты. Поэтому я и не стал обрезать.
Примеры реального использования
Эта система была поднята в девелоперовском контуре. Вот так выглядит график load
для всех баз данных, а там их больше сотни.
Видно, что не смотря на большое количество БД, реальную нагрузку на сервер создают только три базы. С одной стороны, хочется похвалить разработчиков этих сервисов, они большие молодцы, раз тестируют под нагрузкой. Но если поскрести (использовать другие метрики, например, показывающих основные проблемные запросы), то оказалось что не только в тестовой нагрузке было дело. Эти базы содержали реальные проблемы. Например, у той, что на графике изображена тёмно-синей, была очень коварная «несовместимость» перехода на 12-й PostgreSQL, настолько коварная, что она даже официально не объявлена как несовместимость. Про неё подробно рассказывал Олег Бартунов в выступлении про CTE в 12-м PostgreSQL.
Эту проблему удалось своевременно выявить и исправить до того, как она попала в эксплуатацию. Теперь про базу, которая отмечена на графике бледно-голубым. Вот её график до и после оптимизации:
Сразу видно, что оптимизация запросов пошла на пользу. Так бывает не всегда. Случай, когда, казалось бы, такая безобидная оптимизация как добавление индекса приводит к катастрофичному падению производительности, я описал в другой статье. Поэтому полезно проверять, что принятые меры дали желательный результат.
Ближайшие аналоги и конкуренты
Какую-то похожую информацию может выдать плагин PostgreSQL by Zabbix agent 2. Ключ:
pgsql.queries["{$PG.CONNSTRING}","{$PG.USER}","{$PG.PASSWORD}","{$PG.DATABASE}","{$PG.QUERY_ETIME.MAX.WARN}"]
С документацией было не густо, полез смотреть сорсы. Этот ключ раз в минуту анализирует pg_stat_activity
на предмет открытых подключений к базе. И собирает там информацию. Надо ли говорить, что поскольку такой опрос происходит раз в минуту и может видеть только активные на текущий момент или последние законченные запросы (если коннект простаивает), то большая часть информации о всех запросах попросту пропадает и не доходит до Zabbix. А та что доходит, если она касается выполняемых на текущий момент запросов, будет неточной.
Реальный случай. Сотрудницы одного из наших подразделений жаловались на задержки в работе из-за базы данных. Когда сисадмины смотрели на график максимальной длительности запросов, полученных как раз из этого плагина Zabbix, то было видно, что за ближайшие полгода максимум времени, в течение которого выполнялся бы запрос, был несколько десятков миллисекунд. И сисадмины отвечали одним из двух популярных среди сисадминов ответов всех времён и народов: «Не вижу проблем». Второй популярный ответ всех времён и народов у сисадминов: «У меня всё работает». Но когда я поставил на эту базу pg_stat_statements
и пока ещё вручную стал проверять работу, то выяснилось, что вполне бывают запросы, которые выполняются и по 40 минут. А то, что показывает плагин Zabbix, это, конечно, красивые графики, которые и выглядят правдоподобно, и начальству нравятся, и создают иллюзию работы, и иллюзию контроля над проблемами. Вот только не имеют никакой связи с действительностью.
Именно после этого случая я и решил придумать систему мониторинга запросов в Zabbix на основе pg_stat_statements
.
PS v.2
После обсуждения в комментариях я решил сделать еще одну попытку найти способ, чтобы те базы, которые бездействуют (например, обычно postgres) не выдавали бы unsupported и ошибки. И это мне удалось, оказалось у zabbix есть необходимый функционал для этого, сложность была только в том, где найти. Так что сейчас, если база данных бездействует, то и никакие пустые значения в zabbix не вставляются (бездействующие базы место не занимают), но и ошибки никакие не появляются, как раньше. Если эти правки будете вставлять вручную, то имейте в виду, что изменения в Preprocessing у item prototype (а изменения там) Zabbix 6.0 не воспринимаются как изменения в item и он их не пересоздает при discovery. Поэтому все item содержащие в названии pgstmts (из моего шаблона) из конфигурации host надо будет удалить вручную в Configuration/Hosts и потом их пересоздать вызвав (Execute now) discovery Pg_srv и Pg_db. Либо удалить старый мой шаблон с очисткой и потом импортировать заново. Шаблон в конце статьи обновил.
Файлы
pgstmts.sec.json.v12.sql
WITH su AS (
SELECT usesysid FROM pg_user
WHERE usename IN ('postgres', 'zbx_monitor', 'pgbouncer'))
SELECT jsonb_build_object(
'temp_blks_read', sum(temp_blks_read),
'shared_blks_read', sum(shared_blks_read),
'shared_blks_hit', sum(shared_blks_hit),
'local_blks_read', sum(local_blks_read),
'local_blks_hit', sum(local_blks_hit),
'total_time', sum(total_time),
'calls', sum(calls),
'rows', sum(rows)
) AS "json"
FROM pg_stat_statements(false)
WHERE (SELECT pgd.oid FROM pg_database AS pgd WHERE datname=$1) = dbid
AND userid NOT IN (TABLE su);
pgstmts.sec.json.v13.sql
WITH su AS (
SELECT usesysid FROM pg_user
WHERE usename IN ('postgres', 'zbx_monitor', 'pgbouncer'))
SELECT jsonb_build_object(
'temp_blks_read', sum(temp_blks_read),
'shared_blks_read', sum(shared_blks_read),
'shared_blks_hit', sum(shared_blks_hit),
'local_blks_read', sum(local_blks_read),
'local_blks_hit', sum(local_blks_hit),
'total_time', sum(total_exec_time + total_plan_time),
'calls', sum(calls),
'rows', sum(rows)
) AS "json"
FROM pg_stat_statements(false)
WHERE (SELECT pgd.oid FROM pg_database AS pgd WHERE datname=$1) = dbid
AND userid NOT IN (TABLE su);
odsamoylov.conf
# discover PostgreSQL servers accessible by file sockets
UserParameter=pg.servers, echo '['; delimiter=''; for f in /var/run/postgresql/.s.PGSQL.*; do if [ -S "$f" ]; then p="${f#/var/run/postgresql/.s.PGSQL.}"; svn="$(psql --no-psqlrc --set=ON_ERROR_STOP=on --no-password --no-readline --tuples-only --no-align --user='zbx_monitor' --port="$p" --dbname='postgres' --command="show server_version_num;")"; echo "${delimiter}{ \"{#URI}\":\":$p/\", \"{#POSTGRESQL_URI}\":\"postgresql://$HOSTNAME:$p\", \"{#PG.URI}\":\"$f\", \"{#PG.SERVER_VERSION_NUM}\":$svn, \"{#HOSTNAME}\":\"$HOSTNAME\", \"{#PG.PORT}\":$p }"; delimiter=', '; fi; done; echo ']'
# discover PostgreSQL databases accessible by file sockets
UserParameter=pg.databases,echo '['; delimiter='';for f in /var/run/postgresql/.s.PGSQL.*; do if [ -S "$f" ]; then p="${f#/var/run/postgresql/.s.PGSQL.}"; svn="$(psql --no-psqlrc --set=ON_ERROR_STOP=on --no-password --no-readline --tuples-only --no-align --user='zbx_monitor' --port="$p" --dbname='postgres' --command="show server_version_num;")"; for d in $(psql --no-psqlrc --set=ON_ERROR_STOP=on --no-password --no-readline --tuples-only --no-align --user='zbx_monitor' --port="$p" --dbname='postgres' --command="SELECT datname FROM pg_database WHERE NOT datistemplate AND datallowconn;"); do echo "${delimiter}{ \"{#URI}\":\":$p/$d\", \"{#POSTGRESQL_URI}\":\"postgresql://$HOSTNAME:$p/$d\", \"{#PG.URI}\":\"$f\", \"{#PG.PORT}\":$p, \"{#PG.DATABASE}\":\"$d\", \"{#PG.SERVER_VERSION_NUM}\":$svn }"; delimiter=', '; done; fi; done; echo ']'
# get pg_stat_statements per databases
# в данном запросе, если zbx_moniotor дать grant на pg_stat_statements_reset() и роль pg_read_all_stats или pg_monitor,
# то можно будет запускать под zbx_monitor
# расшифровка ключа
# pgstmts данные из pg_stat_statements
# day суточные данные (бывают еще посекундные)
# все гребу в единый json, потом разгребаю
# v13 -> postgresql >=13
# pgstmts.day.json.v13[port,database]
UserParameter=pgstmts.day.json.v13[*], psql --no-psqlrc --set=ON_ERROR_STOP=on --no-password --no-readline --tuples-only --quiet --no-align --user='zbx_monitor' --port='$1' --dbname='postgres' --command="WITH su AS (SELECT usesysid FROM pg_user WHERE usename IN ('postgres', 'zbx_monitor', 'pgbouncer')) SELECT jsonb_build_object( 'top_total_time', (SELECT jsonb_agg(stat.*) FROM (SELECT * FROM pg_stat_statements(true) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su) ORDER BY total_exec_time + total_plan_time DESC LIMIT 5) AS stat), 'top_max_time', (SELECT jsonb_agg(stat.*) FROM (SELECT * FROM pg_stat_statements(true) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su) ORDER BY max_exec_time + max_plan_time DESC LIMIT 5) AS stat)) || (SELECT jsonb_build_object( 'temp_blks_read', sum(temp_blks_read), 'shared_blks_miss', CASE WHEN sum(shared_blks_read)=0 THEN 0 ELSE 100.*sum(shared_blks_read)/sum(shared_blks_hit+shared_blks_read) END, 'local_blks_miss', CASE WHEN sum(local_blks_read)=0 THEN 0 ELSE 100.*sum(local_blks_read)/sum(local_blks_hit+local_blks_read) END, 'max_time', max(max_exec_time + max_plan_time), 'min_time', min(min_exec_time + min_plan_time), 'mean_time', avg(mean_exec_time + mean_plan_time), 'total_time', sum(total_exec_time + total_plan_time), 'calls', sum(calls), 'rows', sum(rows) ) FROM pg_stat_statements(false) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su) ) FROM pg_database AS pgd WHERE datname='$2';" --command "DO \$DO\$ BEGIN PERFORM pg_stat_statements_reset(0,pgd.oid,0) FROM pg_database AS pgd WHERE datname='$2'; END \$DO\$;"
# get pg_stat_statements per instance
# расшифровка ключа
# pgstmts данные из pg_stat_statements
# day суточные данные (бывают еще посекундные)
# все гребу в единый json, потом разгребаю
# v12 -> 10<= postgresql <=12
# pgstmts.day.json.v12[port]
UserParameter=pgstmts.day.json.v12[*], psql --no-psqlrc --set=ON_ERROR_STOP=on --no-password --no-readline --tuples-only --quiet --no-align --user='zbx_monitor' --port='$1' --dbname='postgres' --command="WITH su AS (SELECT usesysid FROM pg_user WHERE usename IN ('postgres', 'zbx_monitor', 'pgbouncer')) SELECT jsonb_object_agg( pgd.datname, jsonb_build_object( 'top_total_time', (SELECT jsonb_agg(stat.*) FROM (SELECT * FROM pg_stat_statements(true) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su) ORDER BY total_time DESC LIMIT 5) AS stat), 'top_max_time', (SELECT jsonb_agg(stat.*) FROM (SELECT * FROM pg_stat_statements(true) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su) ORDER BY max_time DESC LIMIT 5) AS stat)) || (SELECT jsonb_build_object( 'temp_blks_read', sum(temp_blks_read), 'shared_blks_miss', CASE WHEN sum(shared_blks_read)=0 THEN 0 ELSE 100.*sum(shared_blks_read)/sum(shared_blks_hit+shared_blks_read) END, 'local_blks_miss', CASE WHEN sum(local_blks_read)=0 THEN 0 ELSE 100.*sum(local_blks_read)/sum(local_blks_hit+local_blks_read) END, 'max_time', max(max_time), 'min_time', min(min_time), 'mean_time', avg(mean_time), 'total_time', sum(total_time), 'calls', sum(calls), 'rows', sum(rows) ) FROM pg_stat_statements(false) WHERE dbid=pgd.oid AND userid NOT IN (TABLE su)) ) FROM pg_database AS pgd WHERE NOT datistemplate AND datallowconn;" --command="DO \$DO\$ BEGIN PERFORM pg_stat_statements_reset(); END \$DO\$;"
Файл odsamoylov.conf должен лежать там, куда указывается опция Include=/etc/zabbix/zabbix_agent2.d/*.conf
в файле /etc/zabbix/zabbix_agent2.conf
. В данном случае в директории /etc/zabbix/zabbix_agent2.d/.
А файлы pgstmts.sec.json.v*.sql должны лежать в директории, куда указывает опция Plugins.PostgreSQL.CustomQueriesPath
в файле /etc/zabbix/zabbix_agent2.d/plugins.d/postgresql.conf
. Обычно она закомментирована (значит, опция отключена), надо расскоментировать и прописать нужный путь, например /etc/zabbix/postgresql
.
zbx_export_templates.yaml
А этот файл добавить в Zabbix через Импорт, раздел Шаблоны, кнопка в правом верхнем углу. Это вторая версия, улучшенная и исправленная.
zabbix_export:
version: '6.0'
date: '2023-12-05T16:03:58Z'
groups:
- uuid: 748ad4d098d447d492bb935c907f652f
name: Templates/Databases
templates:
- uuid: 11887f960b8945fd8405569f6d5cc4ab
template: odsamoylov
name: 'ODSamoylov PostgresQL discovery'
description: 'Discover working PostgreSQL servers from existing socket files. v.2'
groups:
- name: Templates/Databases
items:
- uuid: 9843942f673540f8b61b7983761cdbd7
name: 'System Load (avg 1 min)'
key: 'system.cpu.load[all, avg1]'
value_type: FLOAT
description: 'System load, 1min average.'
discovery_rules:
- uuid: 2d573742e4e64329a9d02d4165902df7
name: Pg_db
key: pg.databases
delay: 1d
lifetime: 1d
item_prototypes:
- uuid: deccbd857fba47b7be99646694c4032b
name: '{#URI}->pgstmts.sec.json.v12'
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
history: '0'
trends: '0'
discover: NO_DISCOVER
value_type: TEXT
description: |
Текущие значения из pg_stat_statements, без reset.
Для postgresql <13
preprocessing:
- type: JSONPATH
parameters:
- '$[0]["json"]'
- uuid: 60ab8bb11ea84732b2e28470e9e729ef
name: '{#URI}->pgstmts.sec.json.v13'
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
history: '0'
trends: '0'
value_type: TEXT
description: |
Текущие значения из pg_stat_statements, без reset.
Для postgresql >=13
Если для zbx_monitor выдать правильные пермишенсы, можно будет запускать от его имени.
preprocessing:
- type: JSONPATH
parameters:
- '$[0]["json"]'
- uuid: d51ce3c027194c03ac9b9fad60954f62
name: '{#URI}->pgstmts.day.calls.v12'
type: DEPENDENT
key: 'pgstmts.day.calls.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["calls"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 9a5c104842d543f3bbcbe03108a1dbfa
name: '{#URI}->pgstmts.day.calls.v13'
type: DEPENDENT
key: 'pgstmts.day.calls.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
preprocessing:
- type: JSONPATH
parameters:
- '$["calls"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 1817cd7081194d8cb91a584d3d497b13
name: '{#URI}->pgstmts.day.json.v12'
type: CALCULATED
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
delay: 0;h2
history: '0'
trends: '0'
discover: NO_DISCOVER
value_type: TEXT
params: 'first(//pgstmts.day.json.v12["{#PG.PORT}"], 4h)'
description: 'Parse databases stats. 9.5 <= postgresql <=12.'
preprocessing:
- type: JSONPATH
parameters:
- '$["{#PG.DATABASE}"]'
- uuid: 7c5e2872b3124158b315baf92547e292
name: '{#URI}->pgstmts.day.json.v13'
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
delay: 0;h0
history: '0'
trends: '0'
value_type: TEXT
description: 'Get pg_stat_statements and reset statistics, need postgresql >=13'
- uuid: 6e9f27eb89704487b57e4fed2000d7db
name: '{#URI}->pgstmts.day.local_blks_miss.v12'
type: DEPENDENT
key: 'pgstmts.day.local_blks_miss.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: '%'
description: 'Need to increase temp_buffers PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_miss"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: ce749edb579b4d498fabb5879b812bc9
name: '{#URI}->pgstmts.day.local_blks_miss.v13'
type: DEPENDENT
key: 'pgstmts.day.local_blks_miss.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: '%'
description: 'Need to increase temp_buffers PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_miss"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 19569e8199f94a5f98531a996b062028
name: '{#URI}->pgstmts.day.max_time.v12'
type: DEPENDENT
key: 'pgstmts.day.max_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["max_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: c989cf9537d14064b88302f92bc6cbc3
name: '{#URI}->pgstmts.day.max_time.v13'
type: DEPENDENT
key: 'pgstmts.day.max_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["max_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 76b7a5b9f6604920babcf5cac5f54b97
name: '{#URI}->pgstmts.day.mean_time.v12'
type: DEPENDENT
key: 'pgstmts.day.mean_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["mean_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 56c7a1339c8f45fb86a37fbf7ffdfe3c
name: '{#URI}->pgstmts.day.mean_time.v13'
type: DEPENDENT
key: 'pgstmts.day.mean_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["mean_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 690c0ddf1d864d29a9482d59b46e7db3
name: '{#URI}->pgstmts.day.min_time.v12'
type: DEPENDENT
key: 'pgstmts.day.min_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["min_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 55d279ff6943492d96cc6f23ace8aa86
name: '{#URI}->pgstmts.day.min_time.v13'
type: DEPENDENT
key: 'pgstmts.day.min_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["min_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: df66165ed81142129de2cbdf5e8b59d7
name: '{#URI}->pgstmts.day.rows.v12'
type: DEPENDENT
key: 'pgstmts.day.rows.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["rows"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 4d2dbd01aea2455ab58dccfdf13dd7bf
name: '{#URI}->pgstmts.day.rows.v13'
type: DEPENDENT
key: 'pgstmts.day.rows.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
preprocessing:
- type: JSONPATH
parameters:
- '$["rows"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: a2ffb06cc87a4d19bda568497ec92743
name: '{#URI}->pgstmts.day.shared_blks_miss.v12'
type: DEPENDENT
key: 'pgstmts.day.shared_blks_miss.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: '%'
description: 'Need to increase shared_buffers PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_miss"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 6e41b880cdd243d89f5556c94d31c569
name: '{#URI}->pgstmts.day.shared_blks_miss.v13'
type: DEPENDENT
key: 'pgstmts.day.shared_blks_miss.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: '%'
description: 'Need to increase shared_buffers PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_miss"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: e29562671e1a4e88b29637b5a018f4a3
name: '{#URI}->pgstmts.day.temp_blks_read.v12'
type: DEPENDENT
key: 'pgstmts.day.temp_blks_read.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
description: 'Need to increase work_mem PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["temp_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: f26de5990a8d434a8d2ef3c401d66c44
name: '{#URI}->pgstmts.day.temp_blks_read.v13'
type: DEPENDENT
key: 'pgstmts.day.temp_blks_read.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
description: 'Need to increase work_mem PostgresQL config option.'
preprocessing:
- type: JSONPATH
parameters:
- '$["temp_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 243a966177c944adbbeddb07af730b06
name: '{#URI}->pgstmts.day.top_max_time.v12'
type: DEPENDENT
key: 'pgstmts.day.top_max_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
trends: '0'
discover: NO_DISCOVER
value_type: TEXT
description: 'Top queries for max_time.'
preprocessing:
- type: JSONPATH
parameters:
- '$["top_max_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: JAVASCRIPT
parameters:
- 'return JSON.stringify(JSON.parse(value),null," ")'
- type: STR_REPLACE
parameters:
- \\n
- \n
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 88daedcf4efb4645aa418e555b83e83b
name: '{#URI}->pgstmts.day.top_max_time.v13'
type: DEPENDENT
key: 'pgstmts.day.top_max_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
trends: '0'
value_type: TEXT
description: 'Top queries for max_exec_time'
preprocessing:
- type: JSONPATH
parameters:
- '$["top_max_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: JAVASCRIPT
parameters:
- 'return JSON.stringify(JSON.parse(value),null," ")'
- type: STR_REPLACE
parameters:
- \\n
- \n
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: ae1e10422cd0469faec4550bd9da3da0
name: '{#URI}->pgstmts.day.top_total_time.v12'
type: DEPENDENT
key: 'pgstmts.day.top_total_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
trends: '0'
discover: NO_DISCOVER
value_type: TEXT
description: 'Top queries for total_time.'
preprocessing:
- type: JSONPATH
parameters:
- '$["top_total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: JAVASCRIPT
parameters:
- 'return JSON.stringify(JSON.parse(value),null," ")'
- type: STR_REPLACE
parameters:
- \\n
- \n
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 8a195b984ac24a6c8ff7a9da0ca673ce
name: '{#URI}->pgstmts.day.top_total_time.v13'
type: DEPENDENT
key: 'pgstmts.day.top_total_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
trends: '0'
value_type: TEXT
description: 'Top queries for total_exec_time.'
preprocessing:
- type: JSONPATH
parameters:
- '$["top_total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: JAVASCRIPT
parameters:
- 'return JSON.stringify(JSON.parse(value),null," ")'
- type: STR_REPLACE
parameters:
- \\n
- \n
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 2e9b82cb015a41e6ac34dc7abc50da13
name: '{#URI}->pgstmts.day.total_time.v12'
type: DEPENDENT
key: 'pgstmts.day.total_time.v12["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
discover: NO_DISCOVER
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v12["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: 501c9852f74147228960eabc2137b9c3
name: '{#URI}->pgstmts.day.total_time.v13'
type: DEPENDENT
key: 'pgstmts.day.total_time.v13["{#PG.URI}","{#PG.DATABASE}"]'
delay: '0'
history: 1w
value_type: FLOAT
units: ms
preprocessing:
- type: JSONPATH
parameters:
- '$["total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
master_item:
key: 'pgstmts.day.json.v13["{#PG.PORT}","{#PG.DATABASE}"]'
- uuid: a79a9d0c0e944d5792f1a8e061dc7ea4
name: '{#URI}->pgstmts.sec.calls.v12'
type: DEPENDENT
key: 'pgstmts.sec.calls.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
description: 'Queries calls per second.'
preprocessing:
- type: JSONPATH
parameters:
- '$["calls"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: 6a0e03a545644a82b0240f51ffd803d3
name: '{#URI}->pgstmts.sec.calls.v13'
type: DEPENDENT
key: 'pgstmts.sec.calls.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
description: 'Queries calls per second.'
preprocessing:
- type: JSONPATH
parameters:
- '$["calls"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: eb5775d274ae4b45a400b8eb5b676c93
name: '{#URI}->pgstmts.sec.load.v12'
type: DEPENDENT
key: 'pgstmts.sec.load.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
value_type: FLOAT
description: 'Аналог system load, вычисляется на базу данных из pg_stat_statements. Поскольку данные поступают с завершением запросов, запросы больше минуты будут вносить неточность.'
preprocessing:
- type: JSONPATH
parameters:
- '$["total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: MULTIPLIER
parameters:
- '0.001'
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: 3ab68c00d288440b8fe576905b1787a4
name: '{#URI}->pgstmts.sec.load.v13'
type: DEPENDENT
key: 'pgstmts.sec.load.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
value_type: FLOAT
description: 'Аналог system load, вычисляется на базу данных из pg_stat_statements. Поскольку данные поступают с завершением запросов, запросы больше минуты будут вносить неточность.'
preprocessing:
- type: JSONPATH
parameters:
- '$["total_time"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: MULTIPLIER
parameters:
- '0.001'
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: 9062ad5dad914883a02db6c42e0841f3
name: '{#URI}->pgstmts.sec.local_blks_hit.v12'
type: DEPENDENT
key: 'pgstmts.sec.local_blks_hit.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_hit"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: a2e6e98edcbb4ff5a87be6539b10ebd9
name: '{#URI}->pgstmts.sec.local_blks_hit.v13'
type: DEPENDENT
key: 'pgstmts.sec.local_blks_hit.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_hit"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: f1c46395b722427eb3abe1952bd64b70
name: '{#URI}->pgstmts.sec.local_blks_miss.v12'
type: CALCULATED
key: 'pgstmts.sec.local_blks_miss.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
trends: '0'
discover: NO_DISCOVER
value_type: FLOAT
units: '%'
params: '100*last(//pgstmts.sec.local_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"])/(last(//pgstmts.sec.local_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"])+last(//pgstmts.sec.local_blks_hit.v12["{#PG.URI}",,,"{#PG.DATABASE}"])+0.00001)'
preprocessing:
- type: CHECK_NOT_SUPPORTED
parameters:
- ''
- uuid: 5ac87f478a1349f4b4b1fd6200d94763
name: '{#URI}->pgstmts.sec.local_blks_miss.v13'
type: CALCULATED
key: 'pgstmts.sec.local_blks_miss.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
trends: '0'
value_type: FLOAT
units: '%'
params: '100*last(//pgstmts.sec.local_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"])/(last(//pgstmts.sec.local_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"])+last(//pgstmts.sec.local_blks_hit.v13["{#PG.URI}",,,"{#PG.DATABASE}"])+0.00001)'
preprocessing:
- type: CHECK_NOT_SUPPORTED
parameters:
- ''
- uuid: 51baf10158164b0cb5470bb4743ddc53
name: '{#URI}->pgstmts.sec.local_blks_read.v12'
type: DEPENDENT
key: 'pgstmts.sec.local_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: c1eb2c53db3d48f698650822662d2c67
name: '{#URI}->pgstmts.sec.local_blks_read.v13'
type: DEPENDENT
key: 'pgstmts.sec.local_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["local_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: d8687f228a724f3a96036d520adc0d7c
name: '{#URI}->pgstmts.sec.rows.v12'
type: DEPENDENT
key: 'pgstmts.sec.rows.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["rows"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: 46c7fff000d944cc81792141877e30b3
name: '{#URI}->pgstmts.sec.rows.v13'
type: DEPENDENT
key: 'pgstmts.sec.rows.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["rows"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: 645e9467a9a04e86b4f7b29edca134b9
name: '{#URI}->pgstmts.sec.shared_blks_hit.v12'
type: DEPENDENT
key: 'pgstmts.sec.shared_blks_hit.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_hit"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: 3697b2fc1c3e40a18ede6998ec6915cf
name: '{#URI}->pgstmts.sec.shared_blks_hit.v13'
type: DEPENDENT
key: 'pgstmts.sec.shared_blks_hit.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_hit"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: b8a03880ab72460f84108b56215fafd9
name: '{#URI}->pgstmts.sec.shared_blks_miss.v12'
type: CALCULATED
key: 'pgstmts.sec.shared_blks_miss.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
trends: '0'
discover: NO_DISCOVER
value_type: FLOAT
units: '%'
params: '100*last(//pgstmts.sec.shared_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"])/(last(//pgstmts.sec.shared_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"])+last(//pgstmts.sec.shared_blks_hit.v12["{#PG.URI}",,,"{#PG.DATABASE}"])+0.00001)'
preprocessing:
- type: CHECK_NOT_SUPPORTED
parameters:
- ''
- uuid: 969c9423e22344d0beb86aed847e2c9f
name: '{#URI}->pgstmts.sec.shared_blks_miss.v13'
type: CALCULATED
key: 'pgstmts.sec.shared_blks_miss.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
trends: '0'
value_type: FLOAT
units: '%'
params: '100*last(//pgstmts.sec.shared_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"])/(last(//pgstmts.sec.shared_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"])+last(//pgstmts.sec.shared_blks_hit.v13["{#PG.URI}",,,"{#PG.DATABASE}"])+0.00001)'
preprocessing:
- type: CHECK_NOT_SUPPORTED
parameters:
- ''
- uuid: 325fc8eacfeb4719b3634409a5151025
name: '{#URI}->pgstmts.sec.shared_blks_read.v12'
type: DEPENDENT
key: 'pgstmts.sec.shared_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: aa15364ef029496fa5b9ab4cd24dad0e
name: '{#URI}->pgstmts.sec.shared_blks_read.v13'
type: DEPENDENT
key: 'pgstmts.sec.shared_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["shared_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
- uuid: 10020da9cc0e4363bb68d4f8b4d1eb41
name: '{#URI}->pgstmts.sec.temp_blks_read.v12'
type: DEPENDENT
key: 'pgstmts.sec.temp_blks_read.v12["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
discover: NO_DISCOVER
preprocessing:
- type: JSONPATH
parameters:
- '$["temp_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v12", "{#PG.DATABASE}"]'
- uuid: d7585acf54144ba9b9e2427c4aaaf3fc
name: '{#URI}->pgstmts.sec.temp_blks_read.v13'
type: DEPENDENT
key: 'pgstmts.sec.temp_blks_read.v13["{#PG.URI}",,,"{#PG.DATABASE}"]'
delay: '0'
trends: '0'
preprocessing:
- type: JSONPATH
parameters:
- '$["temp_blks_read"]'
- type: NOT_MATCHES_REGEX
parameters:
- ^null$
error_handler: DISCARD_VALUE
- type: CHANGE_PER_SECOND
parameters:
- ''
master_item:
key: 'pgsql.custom.query["{#PG.URI}",,,"postgres","pgstmts.sec.json.v13", "{#PG.DATABASE}"]'
overrides:
- name: 'pgstmts.*.v13'
step: '1'
filter:
conditions:
- macro: '{#PG.SERVER_VERSION_NUM}'
value: '^(1[0-2]....|.....)$'
formulaid: A
operations:
- operationobject: ITEM_PROTOTYPE
operator: REGEXP
value: '->pgstmts\..*\.v13$'
discover: NO_DISCOVER
- name: 'pgstmts.*.v12'
step: '2'
filter:
conditions:
- macro: '{#PG.SERVER_VERSION_NUM}'
value: '^(1[0-2]....)$'
formulaid: A
operations:
- operationobject: ITEM_PROTOTYPE
operator: REGEXP
value: '->pgstmts\..*\.v12$'
discover: DISCOVER
- uuid: f56274e9f87e42bb8122559c13c7ea5d
name: Pg_srv
key: pg.servers
delay: 1d
lifetime: 1d
description: 'Discover PostgreSQL servers by looking for unix sockets.'
item_prototypes:
- uuid: cb751a1fe02f463da409a158375570b1
name: '{#URI}->pgstmts.day.json.v12'
key: 'pgstmts.day.json.v12["{#PG.PORT}"]'
delay: 0;h0
history: 4h
trends: '0'
discover: NO_DISCOVER
value_type: TEXT
description: |
Get pg_stat_statements and reset statistics, need 9.5 <= postgresql <=12.
Works per instances,
overrides:
- name: 'pgstmts.*.v12'
step: '1'
filter:
conditions:
- macro: '{#PG.SERVER_VERSION_NUM}'
value: '^(1[0-2]....)$'
formulaid: A
operations:
- operationobject: ITEM_PROTOTYPE
operator: REGEXP
value: '/->pgstmts\..*\.v12$'
discover: DISCOVER