Prometheus – одна из систем мониторинга, адаптированных под сбор time series данных.
Она достаточно проста в инсталляции и первоначальной настройке. Имеет встроенную графическую подсистему для отображения данных PromDash, однако сами же разработчики рекомендуют использовать бесплатный сторонний продукт Grafana. Prometheus умеет мониторить много чего («железо», контейнеры, различные СУБД), однако в данной статье хотелось бы остановиться на реализации мониторинга инстанса Cache (точнее, инстанс будет Ensemble, но метрики будем брать кашовые). Кому интересно — милости просим под кат.


В нашем простейшем случае Prometheus и Cache будут жить на одной машине (Fedora Workstation 24 x86_64). Версия Cache:

%SYS>write $zv
Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2016.1 (Build 656U) Fri Mar 11 2016 17:58:47 EST

Инсталляция и настройка


Качаем с офсайта подходящий дистрибутив Prometheus и сохраняем в каталог /opt/prometheus.

image

Распаковываем архив, правим имеющийся шаблонный конфиг согласно нашим нуждам и стартуем Prometheus. По умолчанию, Prometheus будет писать логи своей работы прямо в консоль, поэтому мы перенаправим записи о его активности в лог-файл.

Запускаем Prometheus
# pwd
/opt/prometheus
# ls
prometheus-1.4.1.linux-amd64.tar.gz
# tar -xzf prometheus-1.4.1.linux-amd64.tar.gz
# ls
prometheus-1.4.1.linux-amd64 prometheus-1.4.1.linux-amd64.tar.gz
# cd prometheus-1.4.1.linux-amd64/
# ls
console_libraries consoles LICENSE NOTICE prometheus prometheus.yml promtool
# cat prometheus.yml
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.

scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']

# ./prometheus > /var/log/prometheus.log 2>&1 &
[1] 7117
# head /var/log/prometheus.log
time=«2017-01-01T09:01:11+02:00» level=info msg=«Starting prometheus (version=1.4.1, branch=master, revision=2a89e8733f240d3cd57a6520b52c36ac4744ce12)» source=«main.go:77»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Build context (go=go1.7.3, user=root@e685d23d8809, date=20161128-09:59:22)» source=«main.go:78»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading configuration file prometheus.yml» source=«main.go:250»
time=«2017-01-01T09:01:11+02:00» level=info msg=«Loading series map and head chunks...» source=«storage.go:354»
time=«2017-01-01T09:01:11+02:00» level=info msg=«23 series loaded.» source=«storage.go:359»
time=«2017-01-01T09:01:11+02:00» level=info msg="Listening on :9090" source=«web.go:248»

Конфиг prometheus.yml описывается на языке YAML, не любящем табуляций, использовать нужно только пробелы. Выше мы указали, что метрики будем тянуть с адреса http://localhost:57772 и опрашивать будем приложение /metrics/cache (имя приложения выбрано произвольно), т.е. конечный адрес для сбора метрик будет http://localhost:57772/metrics/cache. К каждой метрике будет дописываться метка «job=isc_cache». Метка – очень грубо говоря, это аналог WHERE для SQL. В нашем случае использоваться она не будет, но для числа серверов, больше одного, очень даже сгодится. Например, имена серверов (и/или инстансов) можно сохранять в метках и в дальнейшем метками параметризировать запросы для отрисовки графиков. Проверим, что Prometheus заработал (выше в выводе мы видим порт, который он прослушивает — 9090):

image

Открылся веб-интерфейс, стало быть, Prometheus работает. Однако метрик Cache он пока что, естественно, не видит (проверим, нажав Status > Targets):

image

Подготовка метрик


Наша задача – сделать так, чтобы по адресу http://localhost:57772/metrics/cache можно было стянуть метрики в понятном для Prometheus виде. Будем использовать REST-возможности Cache в силу их простоты. Сразу отметим, что Prometheus «понимает» только метрики числовые, так что строковые метрики экспортировать не будем. Для получения тестовых метрик будем использовать API класса SYS.Stats.Dashboard. Эти метрики используются самой Cache для отображения Системной панели инструментов:

image

Пример того же самого в Терминале
%SYS>set dashboard = ##class(SYS.Stats.Dashboard).Sample()
%SYS>zwrite dashboard
dashboard=<OBJECT REFERENCE>[2@SYS.Stats.Dashboard]
+----------------- general information — | oref value: 2
| class name: SYS.Stats.Dashboard
| reference count: 2
+----------------- attribute values — | ApplicationErrors = 0
| CSPSessions = 2
| CacheEfficiency = 280.03
| DatabaseSpace = «Normal»
| DiskReads = 2317
| DiskWrites = 329
| ECPAppServer = «OK»
| ECPAppSrvRate = 0
| ECPDataServer = «OK»
| ECPDataSrvRate = 0
| GloRefs = 740953
| GloRefsPerSec = «166.00»
| GloSets = 60947
| JournalEntries = 403


Песочницей будет служить область USER. Для начала создадим REST веб-приложение /metrics. Для хоть какой-то безопасности укажем вход в приложение по паролю, также укажем, что соответствовать этому веб-приложению будет некий ресурс, назовем его PromResource. В ресурсе закроем публичный доступ. То есть сделаем следующее:
%SYS>write ##class(Security.Resources).Create("PromResource","Resource for Metrics web page","")
1

Настройки нашего веб-приложения:

image

Нам нужен будет пользователь, имеющий возможность использовать этот ресурс. Также пользователь должен иметь права на чтение рабочей базы (в нашем случае, USER) и запись в нее. Помимо этого, ему понадобится право на чтение системной базы CACHESYS, поскольку ниже в коде реализации мы будем переходить в область %SYS. Будем следовать стандартной схеме, т.е. создадим роль PromRole c этими возможностями, после чего создадим пользователя PromUser с этой ролью. Пароль выберем, например, "Secret":
%SYS>write ##class(Security.Roles).Create("PromRole","Role for PromResource","PromResource:U,%DB_USER:RW,%DB_CACHESYS:R")
1
%SYS>write ##class(Security.Users).Create("PromUser","PromRole","Secret")
1

Именно этого пользователя PromUser мы укажем для аутентификации в конфиге Prometheus. После чего перечитаем конфиг путем посылки процессу сервера сигнала SIGHUP.

Более безопасный конфиг
# cat /opt/prometheus/prometheus-1.4.1.linux-amd64/prometheus.yml
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.

scrape_configs:
  - job_name: 'isc_cache'
    metrics_path: '/metrics/cache'
    static_configs:
    - targets: ['localhost:57772']
    basic_auth:
      username: 'PromUser'
      password: 'Secret'

#
# kill -SIGHUP $(pgrep prometheus) # or kill -1 $(pgrep prometheus)

Теперь Prometheus может успешно пройти аутентификацию для использования веб-приложения с метриками.

Метрики нам будет отдавать класс-обработчик REST-запросов my.Metrics.

Вот его реализация
Class my.Metrics Extends %CSP.REST
{

Parameter ISCPREFIX = "isc_cache";

Parameter DASHPREFIX = {..#ISCPREFIX_"_dashboard"};

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/cache" Method="GET" Call="getMetrics"/>
</Routes>
}

/// Output should obey the Prometheus exposition formats. Docs:
/// https://prometheus.io/docs/instrumenting/exposition_formats/
/// 
/// The protocol is line-oriented. A line-feed character (\n) separates lines. 
/// The last line must end with a line-feed character. Empty lines are ignored.
ClassMethod getMetrics() As %Status
{
	set nl = $c(10)
	do ..getDashboardSample(.dashboard)
	do ..getClassProperties(dashboard.%ClassName(1), .propList, .descrList)
	
	for i=1:1:$ll(propList) {
		set descr = $lg(descrList,i)
		set propertyName = $lg(propList,i)
		set propertyValue = $property(dashboard, propertyName)
		
		// Prometheus supports time series database 
		// so if we get empty (for example, backup metrics) or non-digital metrics 
		// we just omit them.
		if ((propertyValue '= "") && ('$match(propertyValue, ".*[-A-Za-z ]+.*"))) {
			set metricsName = ..#DASHPREFIX_..camelCase2Underscore(propertyName)
			set metricsValue = propertyValue
			
			// Write description (help) for each metrics.
			// Format is that the Prometheus requires.
			// Multiline descriptions we have to join in one string.
			write "# HELP "_metricsName_" "_$replace(descr,nl," ")_nl
			write metricsName_" "_metricsValue_nl
		}
	}
	
	write nl
	quit $$$OK
}

ClassMethod getDashboardSample(Output dashboard)
{
	new $namespace
	set $namespace = "%SYS"
	set dashboard = ##class(SYS.Stats.Dashboard).Sample()
}

ClassMethod getClassProperties(className As %String, Output propList As %List, Output descrList As %List)
{
	new $namespace
	set $namespace = "%SYS"
	
	set propList = "", descrList = ""
	set properties = ##class(%Dictionary.ClassDefinition).%OpenId(className).Properties
	
	for i=1:1:properties.Count() {
		set property = properties.GetAt(i)
		set propList = propList_$lb(property.Name)
		set descrList = descrList_$lb(property.Description)
	}
}

/// Converts metrics name in camel case to underscore name with lower case
/// Sample: input = WriteDaemon, output = _write_daemon
ClassMethod camelCase2Underscore(metrics As %String) As %String
{
	set result = metrics
	set regexp = "([A-Z])"
	set matcher = ##class(%Regex.Matcher).%New(regexp, metrics)
	while (matcher.Locate()) {
		set result = matcher.ReplaceAll("_"_"$1")
	}
	
	// To lower case
	set result = $zcvt(result, "l")
	
	// _e_c_p (_c_s_p) to _ecp (_csp)
	set result = $replace(result, "_e_c_p", "_ecp")
	set result = $replace(result, "_c_s_p", "_csp")
	
	quit result
}
}


В консоли проверим, что наши труды не прошли зря (добавлен ключ --silent, чтобы curl не мешал своим прогресс-баром):

Проверяем выгрузку метрик
# curl --user PromUser:Secret --silent -XGET 'http://localhost:57772/metrics/cache' | head -20
# HELP isc_cache_dashboard_application_errors Number of application errors that have been logged.
isc_cache_dashboard_application_errors 0
# HELP isc_cache_dashboard_csp_sessions Most recent number of CSP sessions.
isc_cache_dashboard_csp_sessions 2
# HELP isc_cache_dashboard_cache_efficiency Most recently measured cache efficiency (Global references / (physical reads + writes))
isc_cache_dashboard_cache_efficiency 439.56
# HELP isc_cache_dashboard_disk_reads Number of physical block read operations since system startup.
isc_cache_dashboard_disk_reads 2605
# HELP isc_cache_dashboard_disk_writes Number of physical block write operations since system startup
isc_cache_dashboard_disk_writes 1021
# HELP isc_cache_dashboard_ecp_app_srv_rate Most recently measured ECP application server traffic in bytes/second.
isc_cache_dashboard_ecp_app_srv_rate 0
# HELP isc_cache_dashboard_ecp_data_srv_rate Most recently measured ECP data server traffic in bytes/second.
isc_cache_dashboard_ecp_data_srv_rate 0
# HELP isc_cache_dashboard_glo_refs Number of Global references since system startup.
isc_cache_dashboard_glo_refs 1593830
# HELP isc_cache_dashboard_glo_refs_per_sec Most recently measured number of Global references per second.
isc_cache_dashboard_glo_refs_per_sec 131.00
# HELP isc_cache_dashboard_glo_sets Number of Global Sets and Kills since system startup.
isc_cache_dashboard_glo_sets 132003

Теперь можно проверить и в интерфейсе Prometheus:

image

Вот и список наших метрик:

image

Не будем останавливаться на их просмотре в самом Prometheus. Желающие могут выбрать нужную метрику и нажать кнопку “Execute”. Выбрав вкладку “Graph”, можно увидеть и график (показана эффективность кэша):

image

Визуализация метрик


Для графического отображения поставим себе Grafana. Для данной статьи была выбрана инсталляция из тарболла. Но вариантов инсталляции много, от пакетов до контейнера. Проделаем указанные шаги (предварительно только создадим каталог /opt/grafana и перейдем в него):

image

Конфиг пока оставим как есть. И в последнем шаге мы стартуем Grafana в фоновом режиме. Лог работы Grafana перенаправим в файл, как и в случае с Prometheus:
# ./bin/grafana-server > /var/log/grafana.log 2>&1 &

Веб-интерфейс Grafana по умолчанию доступен на порту 3000. Логин/пароль: admin/admin.

image

Как подружить Prometheus с Grafana описано здесь. Если своими словами, то – добавляем новый Data Source с типом Prometheus. В качестве доступа direct/proxy выбираете свой вариант:

image

Затем добавляем Дашбоард с нужными нам панелями. Тестовая заготовка Дашбоарда имеется в общем доступе. Там же лежит и код класса для сбора метрик. Дашбоард можно просто импортировать в свою Grafana (Dashboards > Import):

image

После импорта получим вот такую картину:

image

Сохраним Дашбоард:

image

Выбор временного диапазона и период обновления выбираются вверху справа:

image

Примеры работы мониторинга


Протестируем мониторинг обращения к глобалам:
USER>for i=1:1:1000000 {set ^prometheus(i) = i}
USER>kill ^prometheus

Видим, что число ссылок на глобалы в секунду возросло, тогда как эффективность кэша упала (глобала ^prometheus в кэше еще не было):

image

Проверим использование лицензий. Создадим примитивную CSP-страницу PromTest.csp в области USER:
<html>
<head><title>Prometheus Test Page</title></head>
<body>Monitoring works fine!</body>
</html>

И посетим ее энное число раз (приложение /csp/user предполагаем незапароленным):
# ab -n77 http://localhost:57772/csp/user/PromTest.csp

Увидим такую картину с лицензиями:

image

Краткие выводы


Как видим, внедрение подобного мониторинга не составляет большого труда. Уже на начальном этапе мы можем получить важные сведения о работе системы, как то: использование лицензий, эффективность кэша глобалов, наличие ошибок приложений. Для примера был взят класс SYS.Stats.Dashboard, однако не меньшего внимания достойны и другие классы пакетов SYS, %SYSTEM, %SYS. Также никто не ограничивает в фантазии и возможности написания своего класса, выдающего по запросу метрики именно вашего приложения, например, число документов того или иного типа. Некоторые полезные метрики планируется вынести в отдельный шаблон для Grafana.

To be continued


Если данная тема интересна, продолжение будет следовать. Что в планах:

  1. Подготовка шаблона для Grafana с метриками работы демона записи. Неплохо бы сделать некий графический аналог утилиты ^mgstat, во всяком случае, для части ее метрик.
  2. Пароль на веб-приложение – это хорошо, но хотелось бы проверить возможность использования сертификатов.
  3. Использование Prometheus, Grafana и некоторых экспортеров для Prometheus уже в виде Docker-контейнеров.
  4. Использование сервисов обнаружения для автоматического добавления новых инстансов Cache в мониторинг Prometheus. Тут также хотелось бы показать на практике такую удобную вещь Grafana, как шаблоны. Это что-то вроде динамических панелей, когда в зависимости от выбранного сервера показываются только его метрики, но все это на одном и том же Дашбоарде.
  5. Alert Manager Prometheus.
  6. Конфигурационные настройки Prometheus, касающиеся времени хранения данных, а также возможности по оптимизации для систем с большим числом метрик и малым интервалом их сбора.
  7. Разные нюансы, которые всплывут по ходу дела.

Ссылки


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


Спасибо дочитавшим до этой строки!
Поделиться с друзьями
-->

Комментарии (7)


  1. morisson
    10.01.2017 18:26
    +1

    А онлайн-демо где-то можно посмотреть как это все работает вживую?


    1. myardyas
      10.01.2017 18:37
      +2

      Попробуем в скором времени организовать. Как будет, скину сюда адрес.


    1. myardyas
      10.01.2017 21:01
      +2

      http://37.139.17.101:3000/dashboard/db/isc-cache
      Логин — operator
      Пароль — PromOperator

      Не пишу в основной текст, поскольку не совсем уверен в долгожительстве данной ссылки.


      1. morisson
        11.01.2017 06:03

        Спасибо! Выглядит симпатично и работает быстро.
        А такой дашборд если я захочу у себя, надо руками собирать или можно вот прям такой же загрузить и сразу у себя получить?


        1. myardyas
          11.01.2017 09:39
          +1

          Можно прямо так и загрузить (вкупе с классом my.Metrics, ну или как вы его там переименуете). Если не заработает — пишите. В недалеком будущем появятся еще дашбоарды. Есть целое их хранилище. Там пока нет кашовых. Но, скорее всего, туда и положим. Во 2-й части статьи эту тему постараюсь осветить.


  1. DAiMor
    10.01.2017 18:47
    +3

    Хотелось бы еще узнать несколько тонкостей, как я понял prometeus периодически делает запрос на в Cache за новыми значениями метрик? Насколько часто это происходит, и можно ли это изменить. Ответ от Cache я так понимаю должен быть, просто значение метрик на момент запроса.

    mgstat было бы тоже интересно. Но тут дополнительный вопрос, а prometeus умеет работать только с метриками которые можно отобразить на графике? Например фазы WD на графике наверно несовсем корректно отображать, хотя и можно.

    а так, спасибо за статью


    1. myardyas
      10.01.2017 18:57
      +2

      Спасибо! Рад, что понравилось. Надеюсь, будет продолжение.
      Насчет интервала опроса — да, конфигурится в prometheus.yml, параметр scrape_interval. Сейчас там стоит 15 секунд. Там еще много чего можно настроить. Что меня лично интересует — это возможность длительного хранения данных. Т.е., конечно, такая возможность есть, но хотелось бы понаблюдать за размером базы хранения. Собираюсь внедрить это на проде для одного из клиентов. Тогда появятся практические цифры.
      Насчет фаз WD — ну график не так уж плох для просмотра историчности -). Но можно, конечно, отображать текущее значение и текстом в Grafana. Нужно только задать маппинг приезжающих от Prometheus цифр и текста. Мол, приехало 5 — пишем в текстовой панели «Write to WIJ». Текстовую панель такую включу в шаблон отображения метрик WD. Если не забуду, конечно -)