Кроссплатформенная утилита мониторинга SNMP-трафика без зависимостей и наличия GUI

Здравствуй, Хабр!


В процессе своей работы (администрирование активного оборудования) столкнулся с необходимостью оперативного получения онлайн-данных (желательно с графиком) входящего/исходящего трафика на сетевом интерфейсе по SNMP.


При этом всегда попадается такое оборудование, которое либо не заведено в систему мониторинга, либо требует просмотра статистики чаще, чем раз в минуту (как rrdtool).
А в арсенале, зачастую, лишь консоль сервера на Windows или Debian.


Вот тогда и появилась идея сделать небольшую утилитку обладающую следующими возможностями:


— кроссплатформенность;
— без зависимостей (статическая линковка библиотек);
— построение графиков в онлайн-режиме;
— построение графиков в консоли (псевдографика — спасибо, curses);
— шаблоны для специальных OID (пока один для ifInOctets и ifOutOctets);
— возможность прорисовки нескольких кастомных графиков.


Пока альфа-версия бинарников. Разместил здесь на Sourceforge.
Проверено на Windows 7/8/10 32-bit и 64-bit. Debian и Ubuntu.
Кушает данные SNMP — COUNTER, INTEGER, GAUGE.


Примеры запуска утилиты.


Список интерфейсов с OID.


wtraf 10.1.16.2 -l

image


Теперь знаем OID интерфейса (наш №3) и запускаем.


wtraf 10.1.16.2 -i 3

Результат на Windows 8 в небольшом консольном окне:


image


Результат на Ubuntu 18.04 LTS на весь экран:


image


Запускаем с интервалом сбора данных (раз в 5 сек.) и ограничиваем пропускную способность до 50 МБит/сек.


wtraf 10.1.16.2 -i 3 -n 5 -m 50

Результат в PuTTY (прим. — для удобства восприятия график исходящего трафика течет слева, входящего — справа):


image


А теперь самое сочное. Пример кастомных графиков.


wtraf.exe 10.1.16.2 -xc -a .1.3.6.1.2.1.2.2.1.10.2:LAN:rl:x,8,*,1000,/,1000,/:Mbit/s -a .1.3.6.1.2.1.2.2.1.10.3:Internet:bl:x,8,*,1000,/,1000,/:Mbit/s:80 -a .1.3.6.1.2.1.2.2.1.10.4:LAN_to_GUS:gl:x,8,*,1000,/,1000,/:Mbit/s

image


В свою очередь хочу довести до ума утилитку, может порадует админов.


Буду благодарен выявленным ошибкам. На данный момент их очень много, не все залатал.

Поделиться публикацией
Комментарии 29
    +1
    Спасибо за приложение! Планируется ли переезд на GitHub? (На GitHub легче отправлять pull request)
      0
      Да, планируется. Разместил на скорую руку. В том числе планируется выложить исходники.
      0
      Без исходников?
        0
        Будут. Но попозже. Сейчас пытаюсь определить полезную составляющую приложения. Стоит ли развивать.
          0
          Выложите на гитхаб, по звездам и пулл реквестам можно будет судить о дальнейшем развитии. Да и трекать фичреквесты\баги будет удобнее.
        0
        del
          +1
          Утилита супер. Автору огромное спасибо! Для себя нашел применение делаешь tmux требуемую раскладку, сохраняешь. В мониторинге делаешь отдельный dashboard и через guacamole выводишь в iframe на этот дашбоард раскладку из tmux (можно сделать одной командой tmux attach) и получаешь графики в реальном времени в мониторинге, очень полезно для оперативной оценки ситуации.
            +1
            И еще не плохо бы видеть статус интерфейса up/down. И чтобы можно было отключать шапку таблицы. Т.е. вывод выглядел бы по маске: ||ifDescription|\n. Было бы совсем круто.
              0
              Будет сделано!
                0
                Ну да, это полезно для dashboard, всегда не хватает места. Подумайте пожалуйста, как можно компактно расположить требуемую информацию

                Мои предложения такие не плохо кроме Description еще и видеть название интерфейса, т.е. например [gigabitethernet 1/0/1]:
                >> Host: ..., Interface:… (up/down speed: 10 Gbit duplex full) Input Errors: и Output Errors: < — перенести в на две строки ниже и добавить скорость и дуплекс
                + interval: 5 sec, max limit: auto < — это вообще убрать
                Outbound traffic: перенести в ту же строку где Max и Min

                Сделать возможность выводить в текстовом виде следующие параметры одной командой
                In_Avg_5: 171
                Input Discards: 0/s
                Input Errors: 0/s
                Input bandwidth: 8.73 bit/s
                Input non-unicast packets: 0/s
                Input unicast packets: 0.02/s
                Length of output queue: 0
                Out_Avg_5: 875
                Output Discards: 0/s
                Output Errors: 0/s
                Output bandwidth: 5.59 Kbit/s
                Output non-unicast packets: 8.78/s
                Output unicast packets: 0/s

                так же не плохо было бы задавать маску для определения состояния, т.е. загружен на 90% например, Warning, и т.п.
                  0
                  Пардон, название выводится. Добавлю еще, добавить возможность работать программе в фоновом режиме по заранее определенному файлу конфигурации, если будет маска по требуемому параметру вести (загрузка, ошибки и т.п.) лог, отображающий статус ОK -> WARNING Input bandwidth: 950 Mbit/c или WARNING -> OK.
                    0
                    Концепцию утилиты строил исходя из того, что входные параметры только по аргументам. Выводить информацию в лог, в принципе, можно реализовать.
                    +1
                    Да и еще обнаружил что ifDescription режется. В варианте без шапки снять ограничение на длину дескприпшена и убрать не значившийся пробелы. Была бы клевая утиля для нагиособразных утилит.
                      +1
                      Как автору можно перевести donat за программу?
                      Да, можно было бы в мониторинг для некоторых интерфейсов отправлять информацию из нее в realtime, чего так не хватает. Например сохранять все опросы за 1 s в rrd файл и выводить их в спец графики.
                        0
                        Понял, рассмотрим. Если что напишу в личку.
                          0
                          Вот этим js можно было бы выводить график в realtime
                          github.com/terenn/rrdgraph-js
                            0
                            Главный цель программы, выводить графики в консоли без лишних телодвижений. В том числе установки дополнительного ПО и библиотек.
                              +1
                              Это понятно, я просто написал, как я планирую выводить графики в realtime, если вы сделаете сохранение опросов в rrd/rra и лог файл.
                          0
                          Добавлено в новой версии:
                          аргумент -l может принимать значения
                          <wtraf HOST -l> — минимальный листинг без шапки
                          <wtraf HOST -lt> — минимальный листинг с шапкой
                          <wtraf HOST -l++> — больше информации без шапки
                          <wtraf HOST -lt+> — больше информации с шапкой, в т.ч. up/down
                            0
                            Спасибо, это было очень нужно.
                        0
                        Про up/down хорошо подмечено!
                        +1
                        Спасибо за программу! проверил с mikrotik работает:)
                          0
                          Мне идея утилиты понравилась. Но показалась, что она работает несколько медленно по крайней мере с опцией -l. Я тут пораскинул умишком и на коленках изобразил одну из возможностей Вашей утили на perl. Реализована опция только -l. Представляю на суд общественности свою версию. В моем случае Ваша утилита работает 18 секунд, а моя 4 секунды
                          #!/usr/bin/perl -w
                          #
                          use strict;
                          #
                          #OIDS
                          use Net::SNMP qw(:snmp);
                          use Data::Dumper qw(Dumper);
                          use vars qw($OURVAR);
                          #
                          #Debug mode
                          my $debug=0;
                          my $log_file="/usr/share/cacti/log/test_query_juniper_rsvp_pl.log";
                          my $log_open = 0;
                          my $verbose=1;
                          my $xml_delimiter=':';
                          #
                          my ($snmp_auth,$snmp_community,$snmp_version,$snmp_port,$timeout,$snmp_user,$snmp_pw);
                          my ($result,$session,$error);
                          my $retries=1;
                          #
                          #SNMP OIDS;
                          my $ifIndex =".1.3.6.1.2.1.2.2.1.1";
                          my $ifStatus = ".1.3.6.1.2.1.2.2.1.8";
                          my $ifDescription = ".1.3.6.1.2.1.2.2.1.2";
                          my $ifName = ".1.3.6.1.2.1.31.1.1.1.1";
                          my $ifAlias = ".1.3.6.1.2.1.31.1.1.1.18";
                          my $ifType = ".1.3.6.1.2.1.2.2.1.3";
                          my $ifSpeed = ".1.3.6.1.2.1.2.2.1.5";
                          my $ifHWaddress = ".1.3.6.1.2.1.2.2.1.6";
                          my $ifInOctets = ".1.3.6.1.2.1.2.2.1.10";
                          my $ifOutOctets = ".1.3.6.1.2.1.2.2.1.16";
                          my $ifOutOctets64 = ".1.3.6.1.2.1.31.1.1.1.10";
                          my $FcIdName = ".1.3.6.1.4.1.2636.3.15.3.1.2";
                          my $FabricPriority = ".1.3.6.1.4.1.2636.3.15.3.1.3";
                          #
                          sub usage()
                          {
                          print «Usage: wtraf.pl -cCommunity -v(1|2) -l ip_device\n»;
                          }
                          #
                          #Main
                          #
                          #
                          my ($command,$i,$option,$ip,$list,$short)=0;
                          $snmp_version=1;
                          $snmp_community=«public»;
                          $timeout=1;

                          if ($#ARGV == -1 ){usage();exit;}
                          else{$i=0;
                          foreach $option (@ARGV)
                          {
                          chomp ($option);
                          if (($option =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) and ($i == $#ARGV)){$ip=$option;print «IPV4: $option\n»;}
                          elsif (($option =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) and ($i != $#ARGV)){
                          print «Wrong command line ip address must be last.\n»;
                          usage; exit;}
                          elsif ($option =~ m/\-l/){$command=$option;print «Option: $list\n»;$list=1;}
                          elsif ($option =~ m/^\-c\w+?$/) {
                          $snmp_community=$option;$snmp_community =~ s/^\-c//;
                          print«Community:$snmp_community\n»;}
                          elsif ($option =~ m/(^\-v\d$)/) {
                          $snmp_version=$option;
                          $snmp_version =~ s/^\-v//;
                          print«Version:$snmp_version\n»;}
                          $i++;
                          }
                          if ($command eq "-l"){print«List intrefaces:\n»}
                          print «Command line options:\nipv4:$ip\ncomunity:$snmp_community\nversion:$snmp_version\n»;
                          }
                          my %index_table;
                          if ($list==1){
                          ($session,$error) = Net::SNMP->session(
                          -hostname => $ip,
                          -community => $snmp_community,
                          -version => $snmp_version,
                          -timeout => $timeout,
                          -retries=> 1,
                          -nonblocking => 0,
                          );
                          if ( !defined $session) { printf «ERROR: %s.\n», $error; exit 1}
                          ####ifIndex
                          my $request_oid=$ifIndex;
                          $result = $session->get_table(
                          -baseoid => $request_oid,
                          -maxrepetitions => 10,
                          );
                          my %ifIndex=();
                          my %table=%{$result};
                          foreach my $key (keys %table)
                          {
                          my $data= $table{$key};
                          if ($key =~ /\.(\d+)$/)
                          {$key=$1;}
                          $ifIndex{$key}=$data;

                          }
                          ####ifName
                          $request_oid=$ifName;
                          $result = $session->get_table(
                          -baseoid => $request_oid,
                          -maxrepetitions => 10,
                          );
                          if (!defined $result) { printf «ERROR: %s\n», $session->error(); $session->close(); exit 1; }
                          my %ifName=();
                          %table=%{$result};
                          foreach my $key (keys %table)
                          {
                          my $data= $table{$key};
                          if ($key =~ /\.(\d+)$/)
                          {$key=$1;}
                          $ifName{$key}=$data;
                          }
                          ####ifAlias
                          $request_oid=$ifAlias;
                          $result = $session->get_table(
                          -baseoid => $request_oid,
                          -maxrepetitions => 10,
                          );
                          if (!defined $result) { printf «ERROR: %s\n», $session->error(); $session->close(); exit 1; }
                          my %ifAlias=();
                          %table=%{$result};
                          foreach my $key (keys %table)
                          {
                          my $data= $table{$key};
                          if ($key =~ /\.(\d+)$/)
                          {$key=$1;}
                          $ifAlias{$key}=$data;
                          }
                          ####ifStatus
                          $request_oid=$ifStatus;
                          $result = $session->get_table(
                          -baseoid => $request_oid,
                          -maxrepetitions => 10,
                          );
                          if (!defined $result) { printf «ERROR: %s\n», $session->error(); $session->close(); exit 1; }
                          my %ifStatus=();
                          %table=%{$result};
                          foreach my $key (keys %table)
                          {
                          my $data= $table{$key};
                          if ($key =~ /\.(\d+)$/)
                          {$key=$1;}
                          $ifStatus{$key}=$data;

                          }
                          ###############################
                          snmp_dispatcher();
                          $session->close();
                          my data="";
                          foreach my $key (sort keys %ifIndex)
                          {
                          print "$ifIndex{$key}|$ifName{$key}|$ifAlias{$key}|$ifStatus{$key}\n";
                          }
                          }
                          #End
                            0
                            Спасибо, как раз ранее был написан скрипт на perl.
                            Для автономности решено было собрать статические библиотеки net-snmp, pdcurses(ncurses) и boost. И написать утилитку на c++.
                            Ввиду сырости программы такая пока скорость, буду улучшать.
                            Для сбора информации по интерфейсам использую функцию библиотеки net-snmp:
                            «netsnmp_query_walk(hrprload_var, sess_handle);»
                              0
                              Кстати проверил у себя, сканирует в лет — 1 сек.
                                0
                                Вероятно у тебя низкий la на сервере.
                            0
                            Эх, ещё бы совместимость с arm…
                              0
                              Поподробней пожалуйста. Может реализуем.
                                0
                                Имеется ввиду возможность сборки (или готовый бинарник) для архитектуры arm, armv7l и т.п.(всевозможные одноплатники). Вероятно, после переезда исходников на github проблем собрать самому не возникнет, но всё же…

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое