Самописная система мониторинга IPTV
В этой статье я приведу пример самописной системы мониторинга IPTV каналов которой я пользуюсь в своей работе (и надо сказать вполне успешно). Моя система позволяет определять ошибки в потоках каналов и исправлять их самостоятельно без вручного вмешательства, ну и разумеется собирает статистику, которую потом можно анализировать и делать вполне определенные выводы. Например, я смог сделать выводы какие CAM-модули являются наиболее стабильными, какие ресиверы стабильнее работают с теми или иными спутниками или CAM-модулями
В этой статье я приведу пример самописной системы мониторинга IPTV каналов которой я пользуюсь в своей работе (и надо сказать вполне успешно). Моя система позволяет определять ошибки в потоках каналов и исправлять их самостоятельно без вручного вмешательства, ну и разумеется собирает статистику, которую потом можно анализировать и делать вполне определенные выводы. Например, я смог сделать выводы какие CAM-модули являются наиболее стабильными, какие ресиверы стабильнее работают с теми или иными спутниками или CAM-модулями
Кстати у меня еще есть система сбора статистики зрительского интереса к телепрограммам и я возможно опишу скоро и ее.
Кстати в статье про IPTV мониторинг с помощью ардуины и светодиодной матрицы - разумеется сам мониторинг осуществляла не ардуина, а вот данная система мониторинга которую я сейчас опишу
Идея этого мониторинга очень проста.
Раз в полчаса (для некоторых особо важных каналов, чаще, раз в 10 минут) по крону запускается скрипт, который делает следующее - запускает vlc на несколько секунд на определенный iptv канал, с записью куска эфира в файл с логгированием, затем останавливает запись, делает простейший анализ логов, накапливает информацию об ошибках (если они есть) и в конце принимает решение, если на ресивере более половины каналов идут с ошибками - нужно ли передергивать ресивер или нет. Откуда взято время - полчаса? У меня проверяется порядка 170 каналов, на каждый канал уходит примерно 8 секунд, т.е. на все каналы уходит 1360 секунд, или около 22-23 минут. Поэтому взято время полчаса чтобы проверки не накладывались друг на друга. Так же это время взято потому что если дергается ресивер - то дергаются все каналы с ресивера, а ведь часть каналов может быть и рабочими, идти без проблем.
Из этой системы я приведу только часть кода, которая может оказаться кому-то полезной и раскроет основные моменты. Весь код слишком специфичный, и заточен под мои конкретные нужды.
Для начала конфиг. Пусть он будет в виде файла mc_iptv.config
Download file mc_iptv.config239.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<>0 "; $result=mysql_query($sql); $arduino_msg = "IbZ "; if(mysql_num_rows($result)>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 => $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>0) { if(($rec_id>=230)&&($rec_id<=242)) { fwrite($fh,"need to reboot PBI 192.168.0.$rec_id !!!\n"); system("echo \"\\nreboot PBI 192.168.0.$rec_id:\\n\" >> /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>>/home/ibz/iptv.watchdog/reboot.log 1>>/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\" >> /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>>/home/ibz/iptv.watchdog/reboot.log 1>>/home/ibz/iptv.watchdog/reboot.log"); } } } else if($failed/$cnt_prog[$rec_id]>=0.5) { if(($rec_id>=230)&&($rec_id<=242)) { fwrite($fh,"need to reboot PBI 192.168.0.$rec_id !!!\n"); system("echo \"\\nreboot PBI 192.168.0.$rec_id:\\n\" >> /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>>/home/ibz/iptv.watchdog/reboot.log 1>>/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\" >> /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>>/home/ibz/iptv.watchdog/reboot.log 1>>/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 >>/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 >/home/ibz/iptv.watchdog/watchdogger2.log cat /home/ibz/iptv.watchdog/watchdogger.log >> /home/ibz/iptv.watchdog/watchdogger2.log date >>/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 # скрипт который подсчитывает разные параметры по кол-ву аварий и другую статистику. Его я приводить тоже не буду, скажу лишь что он складывает эту статистику по каналам в БД.
Далее рисуется вполне банальная веб-страничка, которая аккумулирует все собранные скриптами данные. У меня она выглядит так. Много пришлось заретушировать, извините, секрет :)