Truuks vir die hantering van statistieke in Kapasitor

Heel waarskynlik, vandag het niemand 'n vraag hoekom dit nodig is om diensstatistieke te versamel nie. Die volgende logiese stap is om 'n waarskuwing vir die versamelde maatstawwe op te stel, wat jou in kennis stel van enige afwykings in die data na kanale wat vir jou gerieflik is (pos, Slack, Telegram). Aanlyn hotelbesprekingsdiens Ostrovok.ru alle maatstawwe van ons dienste word in InfluxDB gegooi en in Grafana vertoon, die basiese waarskuwing word ook daar gekonfigureer. Vir take soos "jy moet iets bereken en daarmee vergelyk", gebruik ons ​​Kapacitor.

Truuks vir die hantering van statistieke in Kapasitor
Kapasitor is deel van die TICK-stapel wat metrieke van InfluxDB kan hanteer. Hy kan verskeie metings aan mekaar koppel (join), iets nuttigs uit die ontvangde data bereken, die resultaat terugskryf na InfluxDB, 'n waarskuwing na Slack/Telegram/pos stuur.

Die hele stapel het 'n koel en gedetailleerde dokumentasie, maar daar sal altyd nuttige dinge wees wat nie uitdruklik in die handleidings aangedui word nie. In hierdie artikel het ek besluit om 'n aantal sulke nuttige nie-vanselfsprekende wenke te versamel (die hoofsintaksis van TICKscipt word beskryf hier) en wys hoe hulle toegepas kan word deur die voorbeeld van die oplossing van een van ons probleme te gebruik.

Kom ons gaan!

float & int, berekeningsfoute

Absoluut standaard probleem, opgelos deur rolverdeling:

var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))

Gebruik verstek()

As die merker/veld leeg is, sal foute in die berekeninge voorkom:

|default()
        .tag('status', 'empty')
        .field('value', 0)

vul in join (binne vs buitenste)

By verstek sal join punte weggooi waar daar geen data is nie (binne).
Met fill('null') sal 'n outer join uitgevoer word, waarna jy 'n default() moet doen en die leΓ« waardes moet invul:

var data = res1
    |join(res2)
        .as('res1', 'res2)
        .fill('null')
    |default()
        .field('res1.value', 0.0)
        .field('res2.value', 100.0)

Hier is nog 'n nuanse. As in die voorbeeld hierbo een van die reekse (res1 of res2) leeg is, sal die finale reeks (data) ook leeg wees. Daar is verskeie github-kaartjies oor hierdie onderwerp (1633, 1871, 6967) - ons wag vir regstellings en ly 'n bietjie.

Gebruik voorwaardes in berekeninge (indien in lambda)

|eval(lambda: if("value" > 0, true, false)

Die laaste vyf minute van die pyplyn vir die tydperk

Byvoorbeeld, jy moet die waardes van die laaste vyf minute met die vorige week vergelyk. Jy kan twee bondels data in twee afsonderlike bondels neem of 'n deel van die data uit 'n groter tydperk uittrek:

 |where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)

'n Alternatief vir die laaste vyf minute sou wees om die BarrierNode-nodus te gebruik, wat die data voor die gespesifiseerde tyd afsny:

|barrier()
        .period(5m)

Voorbeelde van die gebruik van Go se sjablone in boodskap

Sjablone pas by die formaat van die pakket teks.sjabloon, hieronder is 'n paar algemene probleme.

as-anders

Om dinge in orde te stel, nie weer mense met teks te aktiveer nie:

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
    )

Twee syfers na die desimale punt in boodskap

Verbetering van die leesbaarheid van die boodskap:

|alert()
    ...
    .message(
        'now value is {{ index .Fields "value" | printf "%0.2f" }}'
    )

Uitbreidende veranderlikes in boodskap

Ons vertoon meer inligting in die boodskap om die vraag "Hoekom skree dit" te beantwoord?

var warnAlert = 10
  |alert()
    ...
    .message(
       'Today value less then '+string(warnAlert)+'%'
    )

Waarskuwing unieke ID

'n Noodsaaklike ding wanneer daar meer as een groep in die data is, anders sal net een waarskuwing gegenereer word:

|alert()
      ...
      .id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')

Pasgemaakte hanteerder's

Daar is 'n uitvoerende hoof in die groot lys hanteerders, wat jou toelaat om jou skrif uit te voer met die parameters geslaag (stdin) - kreatiwiteit en meer!

Een van ons gewoontes is 'n klein luislangskrif vir die stuur van kennisgewings te slap.
Ons wou eers 'n foto van 'n magtiging-beskermde grafana in 'n boodskap stuur. Na - skryf OK in die draad na die vorige waarskuwing van dieselfde groep, en nie as 'n aparte boodskap nie. Voeg 'n bietjie later die mees algemene fout in die laaste X minute by die boodskap.

'n Aparte onderwerp is kommunikasie met ander dienste en enige aksies wat deur 'n waarskuwing geΓ―nisieer word (slegs as jou monitering goed genoeg werk).
'n Voorbeeld van 'n hanteerderbeskrywing, waar slack_handler.py ons selfgeskrewe skrif is:

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"]

Hoe om te ontfout?

Teken opsie

|log()
      .level("error")
      .prefix("something")

Kyk (cli): kapasitor -url gasheer-of-ip:9092 logs lvl=fout

Variant met httpOut

Wys die data in die huidige pyplyn:

|httpOut('something')

Kyk (kry): gasheer-of-ip:9092/kapacitor/v1/tasks/taaknaam/iets

Uitvoering skema

  • Elke taak gee 'n uitvoeringsboom met nuttige nommers in die formaat terug grafiek.
  • Ons vat 'n blok dot.
  • Plak in die kyker geniet.

Waar anders kan jy 'n hark kry

tydstempel in influxdb op terugskrywing

Ons stel byvoorbeeld 'n waarskuwing op vir die hoeveelheid versoeke per uur (groupBy(1h)) en wil die waarskuwing wat in influxdb gebeur het, aanteken (om die feit pragtig te wys dat daar 'n probleem op die grafiek in grafana is).

influxDBOut() sal die tydwaarde van die waarskuwing na die tydstempel onderskeidelik skryf, die punt op die grafiek sal vroeΓ«r/later geskryf word as wat die waarskuwing gekom het.

Wanneer akkuraatheid vereis word: ons omseil hierdie probleem deur 'n pasgemaakte hanteerder te skakel, wat die data na influxdb sal skryf met die huidige tydstempel.

docker, bou en ontplooi

By opstart kan kapasitor take, sjablone en hanteerders laai vanaf die gids gespesifiseer in die konfigurasie, in die [load] blok.

Om 'n taak korrek te skep, het jy die volgende dinge nodig:

  1. LΓͺernaam - brei uit na ID/naam van die skrif
  2. Tipe - stroom/batch
  3. dbrp - 'n sleutelwoord om te spesifiseer in watter databasis + beleid die skrif werk (dbrp "verskaffer". "autogen")

As daar geen lyn met dbrp in een of ander bondeltaak is nie, sal die hele diens weier om te begin en eerlik daaroor in die log skryf.

In chronograf, inteendeel, hierdie lyn behoort nie te bestaan ​​nie, dit word nie deur die koppelvlak aanvaar nie en gee 'n fout.

Hack wanneer 'n houer gebou word: Dockerfile gaan uit met -1 as daar lyne is met //.+dbrp, wat jou sal toelaat om dadelik die rede vir die mislukking te verstaan ​​wanneer die bou gebou word.

sluit een by baie aan

'n Voorbeeldtaak: jy moet die 95ste persentiel van die dienstyd vir 'n week neem, vergelyk elke minuut van die laaste 10 met hierdie waarde.

Jy kan nie 'n een-tot-veel aansluiting doen nie, laaste/gemiddelde/mediaan oor 'n groep punte verander die nodus in 'n stroom, die fout "kan nie kind wat nie ooreenstem met rande byvoeg nie: bondel -> stroom" sal teruggestuur word.

Die resultaat van 'n bondel, as 'n veranderlike in 'n lambda-uitdrukking, word ook nie vervang nie.

Daar is 'n opsie om die nodige nommers van die eerste bondel na 'n lΓͺer te stoor via udf en hierdie lΓͺer op te laai via sideload.

Wat het ons besluit?

Ons het ongeveer 100 hotelverskaffers, elkeen van hulle kan verskeie verbindings hΓͺ, kom ons noem dit 'n kanaal. Daar is ongeveer 300 van hierdie kanale, elkeen van die kanale kan afval. Van al die aangetekende maatstawwe sal ons die foutkoers (versoeke en foute) monitor.

Hoekom nie graphana nie?

Foutwaarskuwings wat in grafana opgestel is, het verskeie nadele. Sommige is krities, sommige kan jy jou oΓ« voor sluit, afhangende van die situasie.

Grafana kan nie tussen dimensies + waarskuwing bereken nie, maar ons benodig 'n koers (versoeke-foute)/versoeke.

Die foute lyk sleg:

Truuks vir die hantering van statistieke in Kapasitor

En minder boos as dit met suksesvolle versoeke beskou word:

Truuks vir die hantering van statistieke in Kapasitor

Goed, ons kan die koers in die diens voor grafana vooraf bereken, en in sommige gevalle sal dit werk. Maar nie in ons s'n nie, want. vir elke kanaal word die verhouding daarvan as "normaal" beskou, en waarskuwings werk volgens statiese waardes (ons soek oΓ«, verander as dit gereeld waarsku).

Hierdie is voorbeelde van "normaal" vir verskillende kanale:

Truuks vir die hantering van statistieke in Kapasitor

Truuks vir die hantering van statistieke in Kapasitor

Kom ons ignoreer die vorige punt en neem aan dat alle verskaffers 'n soortgelyke "normale" prentjie het. Nou is alles reg, en ons kan klaarkom met grafana-waarskuwings?
Ons kan, maar ons wil regtig nie, want ons moet een van die opsies kies:
a) maak baie grafieke vir elke kanaal afsonderlik (en vergesel dit pynlik)
b) laat een grafiek met alle kanale (en verdwaal in kleurvolle lyne en pasgemaakte waarskuwings)

Truuks vir die hantering van statistieke in Kapasitor

Hoe het jy dit gedoen?

Weereens, daar is 'n goeie beginvoorbeeld in die dokumentasie (Berekening van tariewe oor gevoegde reekse), kan jy loer of as basis neem in soortgelyke take.

Wat hulle op die ou end gedoen het:

  • sluit binne 'n paar uur by twee reekse aan, groepeer volgens kanale;
  • vul die reeks volgens groepe in as daar geen data was nie;
  • vergelyk die mediaan van die afgelope 10 minute met die vorige data;
  • skree as iets gevind word;
  • skryf berekende tariewe en waarskuwings wat voorgekom het in influxdb;
  • stuur 'n nuttige boodskap aan slack.

Myns insiens het ons alles wat ons graag by die uitset wil kry so mooi moontlik reggekry (en selfs 'n bietjie meer met pasgemaakte hanteerders).

Op github.com kan jy sien kode voorbeeld ΠΈ minimale skema (graphviz) skrif ontvang het.

'n Voorbeeld van die gevolglike kode:

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)

En wat is die gevolgtrekking?

Kapacitor is wonderlik om moniteringwaarskuwing met 'n klomp groeperings uit te voer, bykomende berekeninge op reeds aangetekende statistieke uit te voer, pasgemaakte aksies uit te voer en skrifte (udf) uit te voer.

Die toegangsdrempel is nie baie hoog nie - probeer dit as grafana of ander instrumente nie ten volle aan jou wenslys voldoen nie.

Bron: will.com

Voeg 'n opmerking