Pull to refresh

Мониторинг прогнозированием, оповещения о потенциальном сбое

Reading time6 min
Views5.4K

Этот пост является продолжением предыдущего. Мониторинг прогнозированием – не стандартный метод мониторинга. Поэтому и оповещения необходимо использовать не совсем стандартные. Рассмотрим, как это делается и почему именно так.

Какое должно быть оповещение


Я думаю всем интересно, кто какую использует систему мониторинга в ежедневной работе. Мы, к примеру, используем Чувака. Мы научили его осуществлять мониторинг всей инфраструктуры, кроме самого важного для нас – транзитного VoIP. Чувак по умолчанию умеет давать оповещения по почте и popup–ом, когда запущено клиентское приложение. Еще, мы научили его отсылать SMS-ки внешним по отношению к нему приложением. Но речь не об этом.
Когда приходит оповещение от системы мониторинга, в самом оповещении должно быть указано на каком из узлов был сбой, и результат последнего измерения величины, находящейся под мониторингом. К примеру, вам может прийти оповещение, о том, что на маршрутизаторе Router1 процессор загружен на 95 процентов. Само по себе такое оповещение достаточно информативно, чтобы понять, как быстро надо на него реагировать. Другое дело – когда вы осуществляете мониторинг методом прогнозирования и при этом проверяете состояние тысяч параметров.
Да, можно в оповещении сообщать результат последнего измерения, можно даже вывести средний результат нескольких последних измерений, или значение измерения которое было ровно сутки назад. Но этого часто может оказаться недостаточно. Самым эффективным методом оповещения в данном случае является оповещение, в котором отображается график измеряемой величины за определенный промежуток времени. Простого взгляда на график, скорее всего, окажется достаточно, чтобы понять, критичен ли потенциальный сбой, и был ли на самом деле сбой, или это ложное срабатывание.

Детали реализации в rrdtool


Как я писал в предыдущих постах, для того, чтобы rrdtool начал прогнозировать и выявлять потенциальные сбои – аберрации, необходимо правильно сформировать базу данных rrd и просто заполнять ее текущими значениями. При такой конфигурации, кроме самих значений измеряемой величины, база автоматически заполняется еще одним значением – FAILURES. FAILURES – простой hash, индексом которого является время (unixtime), в которое проводилось измерение. Значения по индексу показывают наличие аберраций измеряемой величины. Все просто, была аберрация значение FAILURES равно 1, не было – 0.
Однако простой проверки значения FAILURES после последнего измерения не достаточно. Дело в том, что аберрация может длиться достаточно долго, взгляните на график.

Скорее всего, система мониторинга не должна все время, пока существует аберрация, давать оповещение после каждого измерения величины. Хотя, в определенных случаях, так тоже нужно делать. Кроме того, если аберрация закончилась, это вовсе не означает, что проблема устранена, может случиться так, что система просто подстроит прогноз под новые значения. Реальный пример на рисунке ниже, проблема с потоком не решена, а аберрация уже закончилась.

Самым правильным при мониторинге прогнозированием IMHO, является отсылка оповещения с графиком, в моменты, когда аберрация началась и когда она закончилась.

Как это делается


Собственно, скрипт:
#!/usr/bin/env python
import os
import time
import rrdtool
import tempfile
import smtplib
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

COMMASPACE = ', '
# Define params
rrdpath = '/usr/rrdmonit/rrd/'
pngpath = '/usr/local/share/cacti/rrdmonit/
width = '500'
height = '200'
mailsender = "alerter@my-domain.com"
mailreceip = ["admins@my-domain.com", "support@my-domain.com"]
mailserver = 'mx.my-domain.com'

# Generate charts for last 48 hours
enddate = int(time.mktime(time.localtime())) 
begdate = enddate - 172800

def send_alert_attached(subject, flist):
    """ Will send e-mail, attaching png
    files in the flist.
    """
    msg = MIMEMultipart()
    msg['Subject'] = subject
    msg['From'] = mailsender
    msg['To'] = COMMASPACE.join(mailreceip)
    for file in flist:
        png_file = pngpath+file.split('.')[0]+'.png'
        print png_file
        fp = open(png_file, 'rb')
        img = MIMEImage(fp.read())
        fp.close()
        msg.attach(img)
    mserver = smtplib.SMTP(mailserver)
    mserver.sendmail(mailsender, mailreceip, msg.as_string())         
    mserver.quit()
    
def check_aberration(rrdpath,fname):
    """ This will check for begin and end of aberration
        in file. Will return:
        0 if aberration not found. 
        1 if aberration begins
        2 if aberration ends
    """
    ab_status = 0
    rrdfilename = rrdpath+fname
    info = rrdtool.info(rrdfilename)
    rrdstep = int(info['step'])
    lastupdate = info['last_update']
    previosupdate = str(lastupdate - rrdstep - 1)
    graphtmpfile = tempfile.NamedTemporaryFile()
    # Ready to get FAILURES  from rrdfile
    # will process failures array values for time of 2 last updates
    values = rrdtool.graph(graphtmpfile.name,
    'DEF:f0='+rrdfilename+':val:FAILURES:start='+previosupdate+':end='+str(lastupdate),
    'PRINT:f0:MIN:%1.0lf',
    'PRINT:f0:MAX:%1.0lf',
    'PRINT:f0:LAST:%1.0lf')
    fmin = int(values[2][0])
    fmax = int(values[2][1])
    flast = int(values[2][2])
    # check if failure value had changed.
    if (fmin != fmax):
        if (flast == 1):
            ab_status = 1
        else:
            ab_status = 2
    return ab_status 
    
def gen_image(rrdpath, pngpath, fname, width, height, begdate, enddate):
    """
    Generates png file from rrd database:
    rrdpath - the path where rrd is located
    pngpath - the path png file should be created in
    fname - rrd file name, png file will have the same name .png extention
    width - chart area width
    height - chart area height
    begdate - unixtime
    enddate - unixtime  
    """
    # 24 hours before current time, will show on chart using SHIFT option  
    ldaybeg = str(begdate - 86400)
    ldayend = str(enddate - 86400)
    # Will show some additional info on chart 
    endd_str = time.strftime("%d/%m/%Y %H:%M:%S",(time.localtime(int(enddate)))).replace(':','\:')
    begd_str = time.strftime("%d/%m/%Y %H:%M:%S",(time.localtime(int(begdate)))).replace(':','\:')
    title = 'Chart for: '+fname.split('.')[0]
    # Files names 
    pngfname = pngpath+fname.split('.')[0]+'.png'
    rrdfname = rrdpath+fname
    # Get iformation from rrd file
    info = rrdtool.info(rrdfname)
    rrdtype = info['ds[val].type']
    # Will use multip variable for calculation of totals,
    # should be usefull for internet traffic accounting,
    # or call/minutes count from CDR's. 
    # Do not need logic for DERIVE and ABSOLUTE
    if rrdtype == 'COUNTER':
        multip = str(int(enddate) - int(begdate))
    else:
        # if value type is GAUGE should divide time to step value
        rrdstep = info['step']
        multip = str(round((int(enddate) - int(begdate))/int(rrdstep)))
    # Make png image
    rrdtool.graph(pngfname,
    '--width',width,'--height',height,
    '--start',str(begdate),'--end',str(enddate),'--title='+title,
    '--lower-limit','0',
    '--slope-mode',
    'COMMENT:From\:'+begd_str+'  To\:'+endd_str+'\\c',
    'DEF:value='+rrdfname+':val:AVERAGE',
    'DEF:pred='+rrdfname+':val:HWPREDICT',
    'DEF:dev='+rrdfname+':val:DEVPREDICT',
    'DEF:fail='+rrdfname+':val:FAILURES',
    'DEF:yvalue='+rrdfname+':val:AVERAGE:start='+ldaybeg+':end='+ldayend,
    'SHIFT:yvalue:86400',
    'CDEF:upper=pred,dev,2,*,+',
    'CDEF:lower=pred,dev,2,*,-',
    'CDEF:ndev=dev,-1,*',
    'CDEF:tot=value,'+multip+',*',
    'CDEF:ytot=yvalue,'+multip+',*',
    'TICK:fail#FDD017:1.0:"Failures"\\n',
    'AREA:yvalue#C0C0C0:"Yesterday\:"',
    'GPRINT:ytot:AVERAGE:"Total\:%8.0lf"',
    'GPRINT:yvalue:MAX:"Max\:%8.0lf"',
    'GPRINT:yvalue:AVERAGE:"Average\:%8.0lf" \\n',
    'LINE3:value#0000ff:"Value    \:"',
    'GPRINT:tot:AVERAGE:"Total\:%8.0lf"',
    'GPRINT:value:MAX:"Max\:%8.0lf"',
    'GPRINT:value:AVERAGE:"Average\:%8.0lf" \\n',
    'LINE1:upper#ff0000:"Upper Bound "',
    'LINE1:pred#ff00FF:"Forecast "',
    'LINE1:ndev#000000:"Deviation "',
    'LINE1:lower#00FF00:"Lower Bound "')

# List of new aberrations
begin_ab = []
# List of gone aberrations
end_ab = []
# List files and generate charts
for fname in os.listdir(rrdpath):
    gen_image(rrdpath, pngpath, fname, width, height, begdate, enddate)
# Now check files for beiaberrations
for fname in os.listdir(rrdpath):
    ab_status = check_aberration(rrdpath,fname)
    if ab_status == 1:
       begin_ab.append(fname)
    if ab_status == 2:
       end_ab.append(fname)
if len(begin_ab) > 0:
   send_alert_attached('New aberrations detected',begin_ab)
if len(end_ab) > 0:
   send_alert_attached('Abberations gone',end_ab)

Это модификации скрипта, приведенного в прошлом посте, просто добавлены процедуры проверки наличия аберрации и отсылки почты.

P.S. Не устаю поражаться емкости и красоте решений на python-е.
Tags:
Hubs:
+26
Comments17

Articles