Самописная система мониторинга IPTV



В этой статье я приведу пример самописной системы мониторинга IPTV каналов которой я пользуюсь в своей работе (и надо сказать вполне успешно). Моя система позволяет определять ошибки в потоках каналов и исправлять их самостоятельно без вручного вмешательства, ну и разумеется собирает статистику, которую потом можно анализировать и делать вполне определенные выводы. Например, я смог сделать выводы какие CAM-модули являются наиболее стабильными, какие ресиверы стабильнее работают с теми или иными спутниками или CAM-модулями

В этой статье я приведу пример самописной системы мониторинга IPTV каналов которой я пользуюсь в своей работе (и надо сказать вполне успешно). Моя система позволяет определять ошибки в потоках каналов и исправлять их самостоятельно без вручного вмешательства, ну и разумеется собирает статистику, которую потом можно анализировать и делать вполне определенные выводы. Например, я смог сделать выводы какие CAM-модули являются наиболее стабильными, какие ресиверы стабильнее работают с теми или иными спутниками или CAM-модулями

Кстати у меня еще есть система сбора статистики зрительского интереса к телепрограммам и я возможно опишу скоро и ее.

Кстати в статье про IPTV мониторинг с помощью ардуины и светодиодной матрицы - разумеется сам мониторинг осуществляла не ардуина, а вот данная система мониторинга которую я сейчас опишу

Идея этого мониторинга очень проста.

Раз в полчаса (для некоторых особо важных каналов, чаще, раз в 10 минут) по крону запускается скрипт, который делает следующее - запускает vlc на несколько секунд на определенный iptv канал, с записью куска эфира в файл с логгированием, затем останавливает запись, делает простейший анализ логов, накапливает информацию об ошибках (если они есть) и в конце принимает решение, если на ресивере более половины каналов идут с ошибками - нужно ли передергивать ресивер или нет. Откуда взято время - полчаса? У меня проверяется порядка 170 каналов, на каждый канал уходит примерно 8 секунд, т.е. на все каналы уходит 1360 секунд, или около 22-23 минут. Поэтому взято время полчаса чтобы проверки не накладывались друг на друга. Так же это время взято потому что если дергается ресивер - то дергаются все каналы с ресивера, а ведь часть каналов может быть и рабочими, идти без проблем.

Из этой системы я приведу только часть кода, которая может оказаться кому-то полезной и раскроет основные моменты. Весь код слишком специфичный, и заточен под мои конкретные нужды.

Для начала конфиг. Пусть он будет в виде файла mc_iptv.config

Download file mc_iptv.config
239.250.1.1     243   0  0    РБК
239.250.1.2     247   1  0    Discovery Channel
239.250.1.3     250   0  0    2*2
239.250.1.4     250   1  0    EuroSport
...
239.250.3.12    205   1  1954 TV1000 Action
...
239.250.7.2     212   1  1654,1655    myZen HD
...
 

Первый столбец адрес мультикаст группы, второй - номер ресивера (я сделал еще и так чтобы он совпадал с последним октетом ип адреса менеджмента ресивера, т.е. если ресивер 250 - то его менеджмент ип = 192.168.0.250. Второй столбец - это кодированный канал (1), или FTA (0), четвертый столбец - это пиды ES которые надо игнорировать (понятнее станет для чего это нужно по ходу изложения) и затем идет название канала

Следующий компонент системы - скрипт test.sh - которому скармливаются параметры из конфига, он читает и проверяет мультикаст поток, и затем возвращает 0 (все хорошо) или 1 (все плохо) в зависимости от найденных/ненайденных ошибок. Попутно он скидывает сообщение в центральный сислог сервер нашей сети если канал закодирован или нет потока, логгирует состояние и вносит запись в базу данных.

Download file test.bash
#!/bin/sh
 
arg1=$1
arg2=$2
arg3=$3
arg4=$4
 
test_dir=/home/ibz/iptv.watchdog
test_filename=test_file
test_file=$test_dir/$test_filename
log_filename=vlc_watchdog.log
log_file=$test_dir/$log_filename
log_file2=$test_dir/watchdogger.log
syslog_ip=192.168.0.200  # ип сервера сислогов куда централизовано скидываются сообщения со всей сети
ip_for_syslog=192.168.0.1 # наш ип для сервера сислогов
 
rm $test_file
rm $log_file
vlc -d -vvv udp://@$arg1:1234 --file-logging --logfile $log_file --sout='#duplicate{dst=std{access=file,mux=raw,dst="'"$test_file"'"}}'
sleep 7
kill `ps axw|grep $test_filename|grep "access=file"|grep -v grep|gawk '{print $1}'`
 
if [ -f $test_file ]; then
  filesize=`du --bytes $test_file|gawk '{print $1}'`
  if [ $filesize -lt 10000 ]; then
    if cat $log_file | grep "ts warning: invalid header"
      then
        echo "`date`: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" >>$log_file2
        mv -f $log_file "$test_dir/vlc.logs/$arg4" #на всякий случай копируем лог-файл от vlc отдельно
        /usr/bin/mysql -ptv_pass -utv_user tv_db -e "update chan_stat set last_state=1, last_check_date=Now() where ip='$arg1';"
        echo "`date`: SCRAMBLED" >> /home/ibz/iptv.watchdog/logs/$arg1.log
        echo "<8>`date '+%b %e %k:%M:%S'` $ip_for_syslog IPTV: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" | nc -i 2 -w 2 -q 2 -u $syslog_ip 514
      else
        echo "`date`: Umer $arg4 -> $arg2 ( $arg1:1234 )!!!" >>$log_file2
        mv -f $log_file "$test_dir/vlc.logs/$arg4"
        /usr/bin/mysql -ptv_pass -utv_user tv_db -e "update chan_stat set last_state=2, last_check_date=Now() where ip='$arg1';"
        echo "`date`: NO STREAM" >> /home/ibz/iptv.watchdog/logs/$arg1.log
        echo "<8>`date '+%b %e %k:%M:%S'` $ip_for_syslog IPTV: NO STREAM $arg4 -> $arg2 ( $arg1:1234 )!!!" | nc -i 2 -w 2 -q 2 -u $syslog_ip 514
    fi
    exit 1
  else
    if [ $arg3 != "0" ]; then
      echo "test pids: $arg3"
      for pid in `echo "$arg3" | awk -F"," '{ print $1" "$2" "$3" "$4" "$5 }'`; do
        echo "ignore pid: $pid"
        cat $log_file | grep "ts warning: invalid header" | grep -v "(pid: $pid)" > $log_file."_".$pid
        rm $log_file
        mv $log_file."_".$pid $log_file
      done
      if cat $log_file | grep "ts warning: invalid header"
        then
          echo "`date`: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" >>$log_file2
          mv -f $log_file "$test_dir/vlc.logs/$arg4"
          /usr/bin/mysql -ptv_pass -utv_user tv_db -e "update chan_stat set last_state=1, last_check_date=Now() where ip='$arg1';"
          echo "`date`: SCRAMBLED" >> /home/ibz/iptv.watchdog/logs/$arg1.log
          echo "<8>`date '+%b %e %k:%M:%S'` $ip_for_syslog IPTV: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" | nc -i 2 -w 2 -q 2 -u $syslog_ip 514
          exit 1
      fi
    else
      if cat $log_file | grep "ts warning: invalid header"
        then
          echo "`date`: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" >>$log_file2
          mv -f $log_file "$test_dir/vlc.logs/$arg4"
          /usr/bin/mysql -ptv_pass -utv_user tv_db -e "update chan_stat set last_state=1, last_check_date=Now() where ip='$arg1';"
          echo "`date`: SCRAMBLED" >> /home/ibz/iptv.watchdog/logs/$arg1.log
          echo "<8>`date '+%b %e %k:%M:%S'` $ip_for_syslog IPTV: Scrambled $arg4 -> $arg2 ( $arg1:1234 )!!!" | nc -i 2 -w 2 -q 2 -u $syslog_ip 514
          exit 1
      fi
    fi
  fi
fi
 
/usr/bin/mysql -ptv_pass -utv_user tv_db -e "update chan_stat set last_state=0, last_check_date=Now(), prev_state=0, sms_sended=0, first_failed=NULL where ip='$arg1';"
echo "`date`: clear" >> /home/ibz/iptv.watchdog/logs/$arg1.log
 
sleep 1
exit 0

Когда поток идет скремблированный то vlc пишет в логах нечто подобное

ts warning: invalid header [0x1:d3:86:e3] (pid: 330)
ts warning: invalid header [0x13:29:1f:de] (pid: 330)
ts warning: invalid header [0xd1:86:f9:18] (pid: 330)
ts warning: invalid header [0x13:40:20:e] (pid: 330)
ts warning: invalid header [0x99:9f:d8:99] (pid: 410)

где в скобочках как раз pid ошибочного es. Но не всегда это признак того что целиком с каналом все плохо. Бывает просто что текущая подписка на карте доступа не открывает дорожку субтитров, или аудиодорожку определенного языка. Именно для этого и исключаем для некоторых каналов эти es. Следующий компонент - php-скрипт, консольный, watchdog_iptv.php

Download file watchdog_iptv.phps
#!/usr/bin/php
 
<?php 
  $arduino_is_live = true;
  $arduino_channels = array('239.250.0.3', // Культура
                            '239.250.0.4', // НТВ
                            '239.250.0.5', // Питер 5 Канал
                           ...   // чтобы сократить код, здесь я убрал перечисление каналов
                            '239.250.7.8', // Женский Мир HD
                            '239.250.7.9', // HD Life
                            '239.250.8.1'); // Наш Футбол
 
  mysql_connect("localhost","tv_user","tv_pass") or die(mysql_error());
  mysql_select_db("tv_db");
 
  $total_failed = 0;
  $fh = fopen("/home/ibz/iptv.watchdog/mc_iptv.config","r");
  $i=0;
  if($fh)
  {
     while (!feof($fh))
     {
       $buffer = fgets($fh, 4096);
       $buffer = trim($buffer);
       list($arg1, $arg2, $fta, $arg3, $arg4) = split("[ ]+",$buffer,5);
       if($fta==1)
       {
         if( isset($cnt_prog[$arg2]) )
         {
           $cnt_prog[$arg2]++;
         }
         else
         {
           $cnt_prog[$arg2]=1;
         }
       }
       if($arg2!="0")
       {
         echo "/home/ibz/iptv.watchdog/test.sh $arg1 $arg2 $arg3 $arg4\n";
// здесь начинается блок для моей arduino. Выбираются все сбойнувшие каналы, добавляется текущий проверяемый канал, потом все кодируется и отправляются на ethernet shield
         if($arduino_is_live)
         {
           $sql = "select ip from chan_stat where last_state&lt;&gt;0 ";
           $result=mysql_query($sql);
 
           $arduino_msg = "IbZ ";
           if(mysql_num_rows($result)&gt;0)
           {
             while($row=mysql_fetch_array($result))
             {
               $arduino_ip = $row["ip"];
               if($arduino_idx = array_search($arduino_ip,$arduino_channels))
               {
                 $arduino_msg .= pack("C*", $arduino_idx+1);
               }
             }
           }
           if($arduino_idx = array_search($arg1,$arduino_channels))
           {
             $arduino_msg .= pack("C*", $arduino_idx+1);
           }
           $arduino_msg .= pack("C*", 0);
 
           $fp=fsockopen("tcp://10.121.17.220", 3333, $errno, $errstr, 3);
           if($fp)
           {
             fwrite($fp, $arduino_msg);
             fclose($fp);
           }
           else
           {
             $arduino_is_live = false;
           }
         }
// оканчание блока для ардуины
         system("/home/ibz/iptv.watchdog/test.sh \"$arg1\" \"$arg2\" \"$arg3\" \"$arg4\"",$rc);
         print "retcode = $rc\n\n";
         if($rc==1)
         {
            $total_failed++;
            if( isset($cnt_fail[$arg2]) )
            {
              $cnt_fail[$arg2]++;
            }
            else
            {
              $cnt_fail[$arg2]=1;
            }
         }
       }
     }
  };
  fclose($fh);
 
 
  $fh = fopen("/home/ibz/iptv.watchdog/watchdogger.log","a");
  foreach($cnt_fail as $rec_id =&gt; $failed)
  {
    fwrite($fh,"$failed of ".$cnt_prog[$rec_id]." failed on 192.168.0.$rec_id !!!\n");
    if($cnt_prog[$rec_id]==0)
    {
      if($failed&gt;0)
      {
        if(($rec_id&gt;=230)&amp;&amp;($rec_id&lt;=242))
        {
          fwrite($fh,"need to reboot PBI 192.168.0.$rec_id !!!\n");
          system("echo \"\\nreboot PBI 192.168.0.$rec_id:\\n\" &gt;&gt; /home/ibz/iptv.watchdog/reboot.log");
          system("/usr/bin/snmpset -v1 -c private 192.168.0.$rec_id 1.3.6.1.4.1.1070.3.1.1.23.0 i 1 2&gt;&gt;/home/ibz/iptv.watchdog/reboot.log 1&gt;&gt;/home/ibz/iptv.watchdog/reboot.log");
        }
        else
        {
          fwrite($fh,"need to reboot 192.168.0.$rec_id !!!\n");
          system("echo \"\\nreboot 192.168.0.$rec_id:\\n\" &gt;&gt; /home/ibz/iptv.watchdog/reboot.log");
          system("echo -e \"enable adi\\n\\n\\nCamRst\\nCamPmtRst\\nRESET_UNIT\\n\" | nc -i 2 -q 2 -w 2 192.168.0.$rec_id 23 2&gt;&gt;/home/ibz/iptv.watchdog/reboot.log 1&gt;&gt;/home/ibz/iptv.watchdog/reboot.log");
        }
      }
    }
    else if($failed/$cnt_prog[$rec_id]&gt;=0.5)
    {
      if(($rec_id&gt;=230)&amp;&amp;($rec_id&lt;=242))
      {
        fwrite($fh,"need to reboot PBI 192.168.0.$rec_id !!!\n");
        system("echo \"\\nreboot PBI 192.168.0.$rec_id:\\n\" &gt;&gt; /home/ibz/iptv.watchdog/reboot.log");
        system("/usr/bin/snmpset -v1 -c private 192.168.0.$rec_id 1.3.6.1.4.1.1070.3.1.1.23.0 i 1 2&gt;&gt;/home/ibz/iptv.watchdog/reboot.log 1&gt;&gt;/home/ibz/iptv.watchdog/reboot.log");
      }
      else
      {
        fwrite($fh,"need to reboot 192.168.0.$rec_id !!!\n");
        system("echo \"\\nreboot 192.168.0.$rec_id:\\n\" &gt;&gt; /home/ibz/iptv.watchdog/reboot.log");
        system("echo -e \"enable adi\\n\\n\\nCamRst\\nCamPmtRst\\nRESET_UNIT\\n\" | nc -i 2 -q 2 -w 2 192.168.0.$rec_id 23 2&gt;&gt;/home/ibz/iptv.watchdog/reboot.log 1&gt;&gt;/home/ibz/iptv.watchdog/reboot.log");
      }
    }
  }
  fclose($fh);
?>

У меня с 230 по 242 - это ресиверы фирмы PBI, а остальные - ADI. В массиве $arduino_channels перечисляются каналы так как они расположены на плате со светодиодами. Соответственно в процессе работы скрипта все время горят светодиоды сбойнувших каналов и еще зажигается на время проверки тестируемый канал. Внимательный читатель скажет - а где блок который окончательно устанавливает состояние светодиодов на ардуине? Дело в том что у меня список каналов для проверки заведомо больший, и там есть каналы которые не перечислены в массиве - соответственно необязательно в конце еще раз посылать сообщение с неработающими каналами - оно и так пошлется.

И скрипт который запускается в кроне, который все и выполняет

Download file watchdog_iptv.bash
#!/bin/sh
 
dt_st=`date`
 
/home/ibz/iptv.watchdog/watchdog_iptv2.php &gt;&gt;/home/ibz/iptv.watchdog/watchdog_iptv.php.log #основной скрипт которы проверяет все каналы
/home/ibz/iptv.watchdog/send_sms.php #скрипт отправки смс при появлении новых сбойных каналов, его код приводить не буду
 
filesize=`du --bytes /home/ibz/iptv.watchdog/watchdogger.log | gawk '{print $1}'`
if [ $filesize -gt 1 ]; then
  echo $dt_st &gt;/home/ibz/iptv.watchdog/watchdogger2.log
  cat /home/ibz/iptv.watchdog/watchdogger.log &gt;&gt; /home/ibz/iptv.watchdog/watchdogger2.log
  date &gt;&gt;/home/ibz/iptv.watchdog/watchdogger2.log
 
  cat /home/ibz/iptv.watchdog/watchdogger2.log | sendmail my@mail.com
  rm /home/ibz/iptv.watchdog/watchdogger.log
  rm /home/ibz/iptv.watchdog/watchdogger2.log
fi
 
/home/ibz/iptv.watchdog/get_avg_stat.pl # скрипт который подсчитывает разные параметры по кол-ву аварий и другую статистику. Его я приводить тоже не буду, скажу лишь что он складывает эту статистику по каналам в БД.
 

Далее рисуется вполне банальная веб-страничка, которая аккумулирует все собранные скриптами данные. У меня она выглядит так. Много пришлось заретушировать, извините, секрет :)