Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

زیادہ تر امکان ہے، آج کوئی نہیں پوچھتا کہ سروس میٹرکس کو جمع کرنا کیوں ضروری ہے۔ اگلا منطقی مرحلہ جمع شدہ میٹرکس کے لیے ایک الرٹ ترتیب دینا ہے، جو آپ کے لیے آسان چینلز (میل، سلیک، ٹیلیگرام) میں ڈیٹا میں کسی بھی انحراف کے بارے میں مطلع کرے گا۔ آن لائن ہوٹل بکنگ سروس میں Ostrovok.ru ہماری سروسز کے تمام میٹرکس کو InfluxDB میں ڈالا جاتا ہے اور گرافانا میں ڈسپلے کیا جاتا ہے، اور وہاں بنیادی الرٹ بھی ترتیب دیا جاتا ہے۔ "آپ کو کسی چیز کا حساب لگانے اور اس کے ساتھ موازنہ کرنے کی ضرورت ہے" جیسے کاموں کے لیے، ہم Kapacitor استعمال کرتے ہیں۔

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔
Kapacitor TICK اسٹیک کا حصہ ہے جو InfluxDB سے میٹرکس پر کارروائی کر سکتا ہے۔ یہ کئی پیمائشوں کو ایک ساتھ جوڑ سکتا ہے (جوائن کر سکتا ہے)، موصولہ ڈیٹا سے کسی مفید چیز کا حساب لگا سکتا ہے، نتیجہ واپس InfluxDB پر لکھ سکتا ہے، Slack/Telegram/mail پر الرٹ بھیج سکتا ہے۔

پورا اسٹیک ٹھنڈا اور تفصیلی ہے۔ دستاویزات، لیکن ہمیشہ مفید چیزیں ہوں گی جن کی ہدایت نامہ میں واضح طور پر اشارہ نہیں کیا گیا ہے۔ اس مضمون میں، میں نے اس طرح کے متعدد مفید، غیر واضح نکات جمع کرنے کا فیصلہ کیا (TICKscipt کا بنیادی نحو بیان کیا گیا ہے۔ یہاں) اور دکھائیں کہ ہمارے مسائل میں سے کسی ایک کو حل کرنے کی مثال کے ذریعے ان کا اطلاق کیسے کیا جا سکتا ہے۔

ہم چلتے ہیں!

فلوٹ اور انٹ، حساب کی غلطیاں

ایک بالکل معیاری مسئلہ، ذاتوں کے ذریعے حل کیا گیا:

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)

شمولیت کو بھریں (اندرونی بمقابلہ بیرونی)

پہلے سے طے شدہ طور پر، جوائن ان پوائنٹس کو رد کر دے گا جہاں کوئی ڈیٹا (اندرونی) نہیں ہے۔
fill('null') کے ساتھ، ایک بیرونی جوائن کیا جائے گا، جس کے بعد آپ کو ڈیفالٹ() کرنے اور خالی اقدار کو پُر کرنے کی ضرورت ہے:

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

یہاں اب بھی ایک nuance ہے. اوپر دی گئی مثال میں، اگر سیریز میں سے ایک (res1 یا res2) خالی ہے، تو نتیجے میں آنے والی سیریز (ڈیٹا) بھی خالی ہو گی۔ Github پر اس موضوع پر کئی ٹکٹیں ہیں (1633, 1871, 6967) - ہم اصلاحات کا انتظار کر رہے ہیں اور تھوڑا سا تکلیف اٹھا رہے ہیں۔

حساب میں شرائط کا استعمال (اگر لیمبڈا میں)

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

مدت کے لیے پائپ لائن سے آخری پانچ منٹ

مثال کے طور پر، آپ کو پچھلے ہفتے کے ساتھ آخری پانچ منٹ کی قدروں کا موازنہ کرنے کی ضرورت ہے۔ آپ دو الگ الگ بیچوں میں ڈیٹا کے دو بیچ لے سکتے ہیں یا کسی بڑے عرصے سے ڈیٹا کا کچھ حصہ نکال سکتے ہیں:

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

آخری پانچ منٹ کے لیے ایک متبادل BarrierNode استعمال کرنا ہوگا، جو مخصوص وقت سے پہلے ڈیٹا کو کاٹ دیتا ہے:

|barrier()
        .period(5m)

پیغام میں گو ٹیمپلیٹس استعمال کرنے کی مثالیں۔

ٹیمپلیٹس پیکیج کے فارمیٹ سے مطابقت رکھتے ہیں۔ text.templateذیل میں کچھ ایسی پہیلیاں ہیں جن کا اکثر سامنا ہوتا ہے۔

اور اگر

ہم چیزوں کو ترتیب دیتے ہیں اور لوگوں کو ایک بار پھر متن کے ساتھ متحرک نہیں کرتے ہیں:

|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" }}')

کسٹم ہینڈلر کا

ہینڈلرز کی بڑی فہرست میں exec شامل ہے، جو آپ کو اپنے اسکرپٹ کو پاس شدہ پیرامیٹرز (stdin) کے ساتھ چلانے کی اجازت دیتا ہے - تخلیقی صلاحیت اور اس سے زیادہ کچھ نہیں!

ہمارے رواجوں میں سے ایک سلیک پر نوٹیفیکیشن بھیجنے کے لیے ایک چھوٹی سی Python اسکرپٹ ہے۔
سب سے پہلے، ہم ایک پیغام میں اجازت سے محفوظ گرافانا تصویر بھیجنا چاہتے تھے۔ اس کے بعد، اسی گروپ سے پچھلے الرٹ پر تھریڈ میں OK لکھیں، نہ کہ علیحدہ پیغام کے طور پر۔ تھوڑی دیر بعد - پیغام میں آخری X منٹ میں سب سے عام غلطی شامل کریں۔

ایک الگ موضوع دیگر خدمات کے ساتھ مواصلت اور کسی بھی کارروائی کو جو الرٹ کے ذریعہ شروع کیا گیا ہے (صرف اس صورت میں جب آپ کی نگرانی کافی حد تک کام کرتی ہے)۔
ہینڈلر کی تفصیل کی ایک مثال، جہاں slack_handler.py ہماری خود تحریر کردہ اسکرپٹ ہے:

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

واچ (cli): kapacitor -url میزبان یا آئی پی:9092 logs lvl=error

HTTPOut کے ساتھ آپشن

موجودہ پائپ لائن میں ڈیٹا دکھاتا ہے:

|httpOut('something')

دیکھیں (حاصل کریں): میزبان یا آئی پی:9092/kapacitor/v1/tasks/task_name/something

عملدرآمد کا خاکہ

  • ہر ٹاسک فارمیٹ میں کارآمد نمبروں کے ساتھ ایک عملدرآمد درخت لوٹاتا ہے۔ گراف ویز.
  • ایک بلاک لیں۔ ڈاٹ.
  • اسے ناظرین میں چسپاں کریں، لطف اندوز.

آپ کو ریک کہاں سے مل سکتا ہے؟

رائٹ بیک پر influxdb میں ٹائم اسٹیمپ

مثال کے طور پر، ہم فی گھنٹہ درخواستوں کے مجموعے کے لیے ایک الرٹ ترتیب دیتے ہیں (groupBy(1h)) اور influxdb میں پیش آنے والے انتباہ کو ریکارڈ کرنا چاہتے ہیں (گرافانا میں گراف پر مسئلے کی حقیقت کو خوبصورتی سے ظاہر کرنے کے لیے)۔

influxDBOut() الرٹ سے ٹائم اسٹیمپ تک ٹائم ویلیو لکھے گا؛ اس کے مطابق، چارٹ پر پوائنٹ الرٹ آنے سے پہلے/بعد میں لکھا جائے گا۔

جب درستگی کی ضرورت ہوتی ہے: ہم ایک کسٹم ہینڈلر کو کال کرکے اس مسئلے پر کام کرتے ہیں، جو موجودہ ٹائم اسٹیمپ کے ساتھ influxdb پر ڈیٹا لکھے گا۔

ڈاکر، تعمیر اور تعیناتی

سٹارٹ اپ پر، kapacitor ٹاسک، ٹیمپلیٹس اور ہینڈلرز کو [لوڈ] بلاک میں ترتیب میں بیان کردہ ڈائریکٹری سے لوڈ کر سکتا ہے۔

صحیح طریقے سے کام بنانے کے لیے، آپ کو درج ذیل چیزوں کی ضرورت ہے:

  1. فائل کا نام - اسکرپٹ آئی ڈی/نام میں پھیلا ہوا ہے۔
  2. قسم - سلسلہ/بیچ
  3. dbrp - مطلوبہ لفظ اس بات کی نشاندہی کرنے کے لیے کہ کس ڈیٹا بیس + پالیسی میں اسکرپٹ چلتا ہے (dbrp "سپلائر۔" "آٹوجن")

اگر بیچ کے کچھ ٹاسک میں dbrp کے ساتھ لائن شامل نہیں ہے، تو پوری سروس شروع کرنے سے انکار کر دے گی اور لاگ میں ایمانداری سے اس کے بارے میں لکھے گی۔

chronograf میں، اس کے برعکس، یہ لائن موجود نہیں ہونی چاہیے؛ یہ انٹرفیس کے ذریعے قبول نہیں کی جاتی ہے اور ایک غلطی پیدا کرتی ہے۔

کنٹینر بناتے وقت ہیک کریں: اگر //.+dbrp کے ساتھ لائنیں ہیں تو Dockerfile -1 کے ساتھ باہر نکلتی ہے، جو آپ کو بلڈ کو جمع کرتے وقت ناکامی کی وجہ کو فوری طور پر سمجھنے کی اجازت دے گی۔

ایک سے کئی میں شامل ہوں۔

مثالی کام: آپ کو ایک ہفتے کے لیے سروس کے آپریٹنگ وقت کا 95واں پرسنٹائل لینا ہوگا، آخری 10 کے ہر منٹ کا اس قدر سے موازنہ کریں۔

آپ ایک سے کئی جوائن نہیں کر سکتے، پوائنٹس کے ایک گروپ پر آخری/میان/میڈین نوڈ کو سٹریم میں بدل دیتا ہے، خرابی "چائلڈ مماثل کناروں کو شامل نہیں کر سکتی: بیچ -> سٹریم" واپس آ جائے گی۔

ایک بیچ کا نتیجہ، لیمبڈا اظہار میں متغیر کے طور پر، بھی متبادل نہیں ہے۔

پہلے بیچ سے ضروری نمبرز کو udf کے ذریعے فائل میں محفوظ کرنے اور سائڈ لوڈ کے ذریعے اس فائل کو لوڈ کرنے کا آپشن موجود ہے۔

ہم نے اس کے ساتھ کیا حل کیا؟

ہمارے پاس تقریباً 100 ہوٹل سپلائرز ہیں، ان میں سے ہر ایک کے کئی کنکشن ہو سکتے ہیں، آئیے اسے ایک چینل کہتے ہیں۔ ان میں سے تقریباً 300 چینلز ہیں، ہر ایک چینل گر سکتا ہے۔ تمام ریکارڈ شدہ میٹرکس میں سے، ہم غلطی کی شرح (درخواستوں اور غلطیوں) کی نگرانی کریں گے۔

گرافانا کیوں نہیں؟

گرافانا میں کنفیگر کردہ ایرر الرٹس کے کئی نقصانات ہیں۔ کچھ نازک ہیں، کچھ پر آپ اپنی آنکھیں بند کر سکتے ہیں، صورتحال پر منحصر ہے۔

گرافانا نہیں جانتا کہ پیمائش + الرٹنگ کے درمیان کیسے حساب لگانا ہے، لیکن ہمیں ایک شرح (درخواست-غلطیاں)/درخواستوں کی ضرورت ہے۔

غلطیاں گندی نظر آتی ہیں:

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

اور جب کامیاب درخواستوں کے ساتھ دیکھا جائے تو کم برائی:

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

ٹھیک ہے، ہم گرافانا سے پہلے سروس میں شرح کا پہلے سے حساب لگا سکتے ہیں، اور کچھ صورتوں میں یہ کام کرے گا۔ لیکن ہم میں نہیں، کیونکہ... ہر چینل کے لیے اس کا اپنا تناسب "نارمل" سمجھا جاتا ہے، اور انتباہات جامد اقدار کے مطابق کام کرتے ہیں (ہم انہیں اپنی آنکھوں سے دیکھتے ہیں، اگر بار بار انتباہات ہوں تو انہیں تبدیل کریں)۔

یہ مختلف چینلز کے لیے "نارمل" کی مثالیں ہیں:

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

ہم پچھلے نکتے کو نظر انداز کرتے ہیں اور فرض کرتے ہیں کہ "عام" تصویر تمام سپلائرز کے لیے ایک جیسی ہے۔ اب سب کچھ ٹھیک ہے، اور ہم گرافانا میں انتباہات کے ساتھ حاصل کر سکتے ہیں؟
ہم کر سکتے ہیں، لیکن ہم واقعی نہیں چاہتے، کیونکہ ہمیں آپشنز میں سے ایک کا انتخاب کرنا ہے:
a) ہر چینل کے لیے الگ الگ بہت سے گراف بنائیں (اور دردناک طور پر ان کے ساتھ)
ب) تمام چینلز کے ساتھ ایک چارٹ چھوڑ دیں (اور رنگین لائنوں اور حسب ضرورت انتباہات میں گم ہو جائیں)

Kapacitor میں میٹرکس کی پروسیسنگ کے لیے ترکیبیں۔

تم نے یہ کیسے کیا؟

ایک بار پھر، دستاویزات میں ایک اچھی ابتدائی مثال موجود ہے (شامل کردہ سیریز میں شرحوں کا حساب لگانا) میں جھانکنا یا اسی طرح کے مسائل کی بنیاد کے طور پر لیا جا سکتا ہے۔

ہم نے آخر میں کیا کیا:

  • چند گھنٹوں میں دو سیریز میں شامل ہوں، چینلز کے لحاظ سے گروپ بندی؛
  • اگر کوئی ڈیٹا نہیں تھا تو گروپ کے لحاظ سے سیریز کو پُر کریں۔
  • پچھلے ڈیٹا کے ساتھ آخری 10 منٹ کے میڈین کا موازنہ کریں۔
  • اگر ہمیں کچھ مل جائے تو ہم چیختے ہیں۔
  • ہم حسابی شرحیں اور انتباہات لکھتے ہیں جو influxdb میں واقع ہوئے ہیں۔
  • سست کو ایک مفید پیغام بھیجیں۔

میری رائے میں، ہم ہر وہ چیز حاصل کرنے میں کامیاب ہو گئے جو ہم آخر میں حاصل کرنا چاہتے تھے (اور حسب ضرورت ہینڈلرز کے ساتھ اس سے بھی کچھ زیادہ) ہر ممکن حد تک خوبصورتی سے۔

آپ github.com پر دیکھ سکتے ہیں۔ کوڈ کی مثال и کم سے کم سرکٹ (گراف ویز) نتیجے میں سکرپٹ.

نتیجے میں کوڈ کی ایک مثال:

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)

نتیجہ کیا ہے؟

Kapacitor گروپ بندیوں کے ایک گروپ کے ساتھ نگرانی کے انتباہات کو انجام دینے، پہلے سے ریکارڈ شدہ میٹرکس کی بنیاد پر اضافی حسابات کرنے، حسب ضرورت کارروائیاں کرنے اور اسکرپٹس (udf) چلانے میں بہت اچھا ہے۔

داخلے میں رکاوٹ بہت زیادہ نہیں ہے - اگر گرافانا یا دیگر اوزار آپ کی خواہشات کو پوری طرح سے پورا نہیں کرتے ہیں تو اسے آزمائیں.

ماخذ: www.habr.com

نیا تبصرہ شامل کریں