Највероватније, данас нико не пита зашто је потребно прикупљати метрику услуге. Следећи логичан корак је подешавање упозорења за прикупљене метрике, које ће обавештавати о евентуалним одступањима у подацима на каналима који вам одговарају (маил, Слацк, Телеграм). У онлајн сервису за резервацију хотела све метрике наших услуга се преливају у ИнфлукДБ и приказују у Графани, а ту се такође конфигурише основно упозорење. За задатке попут „треба нешто израчунати и упоредити са тим“, користимо Капацитор.

Капацитор је део ТИЦК стека који може да обрађује метрике из ИнфлукДБ-а. Може да повеже неколико мерења заједно (придружи), израчуна нешто корисно из примљених података, запише резултат назад у ИнфлукДБ, пошаље упозорење на Слацк/Телеграм/маил.
Цео низ је кул и детаљан , али увек ће бити корисних ствари које нису експлицитно назначене у приручницима. У овом чланку сам одлучио да прикупим неколико таквих корисних, неочигледних савета (описана је основна синтакса ТИЦКсципт-а ) и показати како се могу применити на примеру решавања једног од наших проблема.
Идемо!
флоат & инт, грешке у прорачуну
Апсолутно стандардни проблем, решен кроз касте:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Коришћење подразумеваног()
Ако ознака/поље није попуњено, доћи ће до грешака у прорачуну:
|default()
.tag('status', 'empty')
.field('value', 0)
попуните спој (унутрашњи наспрам спољашњег)
Подразумевано, придруживање ће одбацити тачке где нема података (унутрашњи).
Са филл('нулл') биће извршено спољно спајање, након чега треба да урадите дефаулт() и попуните празне вредности:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Овде још увек постоји нијанса. У горњем примеру, ако је једна од серија (рес1 или рес2) празна, резултујућа серија (подаци) ће такође бити празна. Постоји неколико карата на ову тему на Гитхуб-у (, , ) – чекамо поправке и мало патимо.
Коришћење услова у прорачунима (ако је у ламбда)
|eval(lambda: if("value" > 0, true, false)
Последњих пет минута од цевовода за период
На пример, потребно је да упоредите вредности последњих пет минута са претходном недељом. Можете узети две групе података у две одвојене групе или издвојити део података из већег периода:
|where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)
Алтернатива за последњих пет минута би била да користите БарриерНоде, који одсеца податке пре одређеног времена:
|barrier()
.period(5m)
Примери коришћења Го шаблона у поруци
Шаблони одговарају формату из пакета Испод су неке загонетке које се често сусрећу.
ако друго
Доводимо ствари у ред и више не изазивамо људе текстом:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Две цифре иза децималног зареза у поруци
Побољшање читљивости поруке:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Проширивање променљивих у поруци
Више информација приказујемо у поруци да бисмо одговорили на питање „Зашто виче“?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Јединствени идентификатор упозорења
Ово је неопходна ствар када постоји више група у подацима, иначе ће се генерисати само једно упозорење:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Прилагођени руковаоци
Велика листа руковалаца укључује екец, који вам омогућава да извршите своју скрипту са прослеђеним параметрима (стдин) - креативност и ништа више!
Један од наших обичаја је мала Питхон скрипта за слање обавештења у слацк.
Прво смо хтели да пошаљемо графана слику заштићену ауторизацијом у поруци. Након тога, напишите ОК у низу на претходно упозорење из исте групе, а не као посебну поруку. Мало касније - додајте у поруку најчешћу грешку у последњих Кс минута.
Посебна тема је комуникација са другим сервисима и све радње покренуте упозорењем (само ако ваш надзор функционише довољно добро).
Пример описа руковаоца, где је слацк_хандлер.пи наша скрипта која је сама написала:
topic: slack_graph
id: slack_graph.alert
match: level() != INFO AND changed() == TRUE
kind: exec
options:
prog: /sbin/slack_handler.py
args: ["-c", "CHANNELID", "--graph", "--search"]
Како отклонити грешке?
Опција са излазом дневника
|log()
.level("error")
.prefix("something")
Гледајте (цли): капацитор -урл :9092 евиденције лвл=грешка
Опција са хттпОут
Приказује податке у тренутном цевоводу:
|httpOut('something')
Гледајте (добите): :9092/капацитор/в1/таскс/име_задатка/нешто
Дијаграм извршења
- Сваки задатак враћа стабло извршења са корисним бројевима у формату .
- Узми блок .
- Залепите га у прегледач, .
Где још можете добити грабље?
временска ознака у инфлукдб при повратном упису
На пример, поставили смо упозорење за збир захтева по сату (гроупБи(1х)) и желимо да снимимо упозорење које се догодило у инфлукдб (да бисмо лепо приказали чињеницу проблема на графикону у графани).
инфлукДБОут() ће записати временску вредност од упозорења до временске ознаке, у складу са тим, тачка на графикону ће бити написана раније/касније него што је стигло упозорење.
Када је потребна тачност: заобилазимо овај проблем позивањем прилагођеног руковаоца, који ће писати податке у инфлукдб са тренутном временском ознаком.
доцкер, прављење и примену
Приликом покретања, капацитор може учитати задатке, шаблоне и руковаоце из директоријума наведеног у конфигурацији у блоку [лоад].
Да бисте исправно креирали задатак, потребне су вам следеће ствари:
- Име датотеке – проширено у ид/име скрипте
- Тип – стреам/батцх
- дбрп – кључна реч која означава у којој бази података и смерница се скрипта покреће (дбрп „добављач.“ „аутоген“)
Ако неки батцх задатак не садржи линију са дбрп-ом, цео сервис ће одбити да се покрене и искрено ће написати о томе у дневнику.
У хронографу, напротив, ова линија не би требало да постоји кроз интерфејс и генерише грешку.
Хак приликом прављења контејнера: Доцкерфиле излази са -1 ако постоје линије са //.+дбрп, што ће вам омогућити да одмах разумете разлог неуспеха приликом склапања буилд-а.
придружи један многима
Пример задатка: потребно је да узмете 95. перцентил радног времена услуге за недељу дана, упоредите сваки минут од последњих 10 са овом вредношћу.
Не можете да извршите спајање један-према-више, последња/средња/средња вредност преко групе тачака претвара чвор у ток, вратиће се грешка „не могу додати подређене неусклађене ивице: серија -> ток”.
Резултат серије, као променљива у ламбда изразу, такође није замењен.
Постоји опција да сачувате потребне бројеве из прве групе у датотеку преко удф-а и учитате ову датотеку путем сиделоад-а.
Шта смо решили овим?
Имамо око 100 хотелских добављача, сваки од њих може имати неколико веза, назовимо то канал. Постоји око 300 ових канала, сваки од канала може да отпадне. Од свих забележених метрика пратићемо стопу грешака (захтеви и грешке).
Зашто не графана?
Упозорења о грешци конфигурисана у Графани имају неколико недостатака. Неки су критични, пред некима можете затворити очи, у зависности од ситуације.
Графана не зна да рачуна између мерења + упозорење, али нам је потребна стопа (захтеви-грешке)/захтеви.
Грешке изгледају гадно:

И мање зло када се гледа са успешним захтевима:

У реду, можемо унапред да израчунамо стопу у сервису пре графане, ау неким случајевима ово ће успети. Али не у нашој, јер... за сваки канал свој однос се сматра „нормалним“, а упозорења раде према статичким вредностима (гледамо очима, мењамо ако има честих упозорења).
Ово су примери „нормалног“ за различите канале:


Игноришемо претходну тачку и претпостављамо да је „нормална“ слика слична за све добављаче. Сада је све у реду, и можемо да прођемо са упозорењима у графани?
Можемо, али заиста не желимо, јер морамо да изаберемо једну од опција:
а) направити много графикона за сваки канал посебно (и болно их пратити)
б) оставите један графикон са свим каналима (и изгубите се у шареним линијама и прилагођеним упозорењима)

Како си то урадио?
Опет, постоји добар почетни пример у документацији (), може се завирити или узети као основа у сличним проблемима.
Шта смо на крају урадили:
- придружите две серије за неколико сати, груписајући се по каналима;
- попунити низ по групама ако није било података;
- упореди медијану последњих 10 минута са претходним подацима;
- вичемо ако нешто нађемо;
- уписујемо израчунате стопе и упозорења која су се десила у инфлукдб;
- послати корисну поруку слацку.
По мом мишљењу, успели смо да постигнемо све што смо желели да добијемо на крају (па чак и мало више са прилагођеним руковаоцима) што је лепше могуће.
Можете погледати на гитхуб.цом и резултујућа скрипта.
Пример резултујућег кода:
dbrp "supplier"."autogen"
var name = 'requests.rate'
var grafana_dash = 'pczpmYZWU/mydashboard'
var grafana_panel = '26'
var period = 8h
var todayPeriod = 10m
var every = 1m
var warnAlert = 15
var warnReset = 5
var reqQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."requests"'
var errQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."errors"'
var prevErr = batch
|query(errQuery)
.period(period)
.every(every)
.groupBy(1m, 'channel', 'supplier')
var prevReq = batch
|query(reqQuery)
.period(period)
.every(every)
.groupBy(1m, 'channel', 'supplier')
var rates = prevReq
|join(prevErr)
.as('req', 'err')
.tolerance(1m)
.fill('null')
// заполняем значения нулями, если их не было
|default()
.field('err.value', 0.0)
.field('req.value', 0.0)
// if в lambda: считаем рейт, только если ошибки были
|eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0))
.as('rate')
// записываем посчитанные значения в инфлюкс
rates
|influxDBOut()
.quiet()
.create()
.database('kapacitor')
.retentionPolicy('autogen')
.measurement('rates')
// выбираем данные за последние 10 минут, считаем медиану
var todayRate = rates
|where(lambda: duration((unixNano(now()) - unixNano("time")) / 1000, 1u) < todayPeriod)
|median('rate')
.as('median')
var prevRate = rates
|median('rate')
.as('median')
var joined = todayRate
|join(prevRate)
.as('today', 'prev')
|httpOut('join')
var trigger = joined
|alert()
.warn(lambda: ("prev.median" - "today.median") > warnAlert)
.warnReset(lambda: ("prev.median" - "today.median") < warnReset)
.flapping(0.25, 0.5)
.stateChangesOnly()
// собираем в message ссылку на график дашборда графаны
.message(
'{{ .Level }}: {{ index .Tags "channel" }} err/req ratio ({{ index .Tags "supplier" }})
{{ if eq .Level "OK" }}It is ok now{{ else }}
'+string(todayPeriod)+' median is {{ index .Fields "today.median" | printf "%0.2f" }}%, by previous '+string(period)+' is {{ index .Fields "prev.median" | printf "%0.2f" }}%{{ end }}
http://grafana.ostrovok.in/d/'+string(grafana_dash)+
'?var-supplier={{ index .Tags "supplier" }}&var-channel={{ index .Tags "channel" }}&panelId='+string(grafana_panel)+'&fullscreen&tz=UTC%2B03%3A00'
)
.id('{{ index .Tags "name" }}/{{ index .Tags "channel" }}')
.levelTag('level')
.messageField('message')
.durationField('duration')
.topic('slack_graph')
// "today.median" дублируем как "value", также пишем в инфлюкс остальные филды алерта (keep)
trigger
|eval(lambda: "today.median")
.as('value')
.keep()
|influxDBOut()
.quiet()
.create()
.database('kapacitor')
.retentionPolicy('autogen')
.measurement('alerts')
.tag('alertName', name)
И какав је закључак?
Капацитор је одличан у обављању надзорних упозорења са гомилом груписања, обављању додатних прорачуна на основу већ снимљених метрика, извођењу прилагођених радњи и покретању скрипти (удф).
Баријера за улазак није велика - покушајте ако графана или други алати не задовољавају у потпуности ваше жеље.
Извор: ввв.хабр.цом
