リアルタイムダッシュボード



ある日、夜遅くまで仕事で座っていた私は、ApacheまたはNgnixのログからエラーやその他の警告のグラフを描画するシンプルで小さなクイックダッシュボードを作成したかったのです。 リアルタイムという用語は少しお世辞です。実際、チャートには3秒ごとに更新が表示されます。 このようなダッシュボードプランは、特に新しいバージョンがバトルに展開されているときに、座って、それがサーバーに静かに広がる様子を見て、グラフ上の曲線の方向を変えるときに非常に便利です。



すぐに何が起こったのか見せたい







サーバー側



ログがある各サーバーで、それを把握します

tail -f /var/log/log | perl frenko_sender.pl
      
      







frenko_sender.pl

 #!/usr/local/bin/perl -w use strict; use IO::Socket; my($sock, $server_host, $msg, $PORTNO); $PORTNO = 15555; $server_host = '188.xxx'; $sock = IO::Socket::INET->new(Proto => 'udp', PeerPort => $PORTNO, PeerAddr => $server_host) or die "Creating socket: $!\n"; while (<>){ $sock->send($_) }
      
      





ご覧のとおり、frenko_sender.plは、udpログの各行を、データが集約されるサーバーに送信するだけです。 ちなみに、このスクリプトがなくても、必要な場所にすぐにudpログを送信するようにsyslogdに指示すると、 Dreadatour nc(Netcat)が指摘したように、誰もキャンセルしません。



 tail -f /var/log/log | nc -u 188.xxx 15555
      
      





計画にはログを処理するサーバー間で負荷を周期的に分散するためにラウンドロビンをねじ込むことが含まれているため、パールバージョンが選択されました。



さて、IO :: LambdaのDima Karasikのおかげで、統計を考慮しているサーバー(frenko_listen.pl)について、私が選んだのはその上だったからです。



frenko_listen.pl行(1-30)

 #!/usr/local/bin/perl -w use strict; use warnings; use Cache::Memcached::Fast; use IO::Socket; use IO::Lambda qw(:all); use IO::Lambda::Socket qw(:all); use Getopt::Std; use POSIX qw/setsid/; my $PORT=15555; my $LOCALHOST ='188.xxx'; my $CONFIG='./frenko_config'; my $time_to_die=0; my $memd = new Cache::Memcached::Fast({ servers => [ { address => 'localhost:11211', weight => 2.5 }], connect_timeout => 0.2, io_timeout => 0.5, close_on_error => 1, max_failures => 3, failure_timeout => 2, ketama_points => 150, nowait => 1, utf8 => ($^V >= 5.008001 ? 1 : 0), });
      
      





$ LOCALHOSTは、UDPを介してポート15555でリッスンするサーバーの外部IPアドレスです。 ご想像のとおり、memcached =)に数値を書き込みます。



frenko_listen.pl行(32-46)

 my %Regexps=(); load_config(\%Regexps); sub load_config{ my $re=shift; %{$re}=(); open (F,$CONFIG) || die 'cant open config '.$CONFIG; while (<F>){ next if /^[\s\n]+$/; my ($regexp,$key,undef,$rate)=split("\t",$_,4); $$re{$key}{re}=$regexp; $$re{$key}{rate}=$rate || 1; } close (F); }
      
      





ここで、正規表現を使用して構成をロードします。これにより、統計計算のルールを決定します。 構成は次のようになります。



frenko_config

 errdescr= 9 [E2;Javascript error at web interface - nginx acclog] 1 errorcode=\d+ 10 [E3;Fail upload with errorcode - nginx acclog] 1
      
      





最初の列には規則性、次に一意のキーがあり、それによってデータを読み取ってチャートに表示します。3番目の列にはエラーの説明が短くて完全ですが、最後の列にはデータを保存する可能性があります。 たとえば、ユーザーヒットをグラフにプロットすると、同じグラフの曲線間に大きなギャップがあります。たとえば、この間に3つのエラーと30,000のヒットがあり、この状況では対数目盛を使用しますが、美しくしたいです。 したがって、ヒットの場合、たとえば0.1に設定します-これは、ヒットカウンターを10%だけ増やすことを意味します。



frenko_listen.pl行(49-80)

 sub sendstat{ my ($key,$rate,$memd)= @ _; if ($rate==1 || $rate<rand()){ saver($key,$_,$memd) for (2,300,1200) } } sub saver{ my ($key,$accur,$memd)=@ _; my $memc=startpoint($accur); $key=$key.'_'.$accur.'_'.$memc; my $ttl=1000; if ($accur==300){$ttl=7400} elsif($accur==1200){$ttl=87000} $memd->set($key,1,$ttl) unless $memd->incr($key); } sub startpoint{ my $accur=shift; my $t=time(); my $x=$t % $accur; my $memc=$t-$x; return $memc; }
      
      





ここでは、データのカウンターを直接増やします。 また、3秒ごと、5分ごと、20分ごとに更新されるスケジュールのために、データを2倍、300秒、1200秒のセルに保存します。 たとえば、2秒のセル(frenko_configからのJavascriptエラー)のmemkeshのキーは次のようになります。300秒のセルの9_2_1338129044、9_2_1338129046:9_300_1338121800、9_300_1338122100など。



frenko_listen.pl行(82-128)

 sub udp_server(&@) { my ($callback, $port, $host) = @ _ ; my $server = IO::Socket::INET-> new( Proto => 'udp', LocalPort => $port, LocalHost => $host, Blocking => 0, ); return $! unless $server; return lambda { context $server, 65536, 0; recv { my ( $addr, $msg) = @ _ ; again; context $callback-> (), $addr, $msg; &tail(); }} } sub handle_incoming_connection_udp { lambda { my ( $addr, $msg ) = @ _ ; if (length($msg)>5){ if (! index($msg,'reconfig')){ load_config(\%Regexps); } else { foreach (keys %Regexps){ if ($msg=~/$Regexps{$_}{re}/){ sendstat($_,$Regexps{$_}{rate},$memd); last; } } } } } } sub runserv{ my $server = udp_server { handle_incoming_connection_udp } $PORT,$LOCALHOST; die $server unless ref $server; $server-> wait; }
      
      





ログ処理は、サブhandle_incoming_connection_udp内で行われます。 行が5文字より長い場合、解析を開始するようです。 設定からロードされたレギュラーのリストを確認します。



たとえば、300台のサーバーでログをリッスンし、構成に新しい規則性を追加する必要がある状況を想像してください。frenko_listen.plを停止すると、データが失われます。 このような状況を回避するには、上記のように、サーバーにその場で構成を更新するように教える必要があります。 「reconfig」で始まる行がある場合は、構成をオーバーロードします。 そしてもちろん、秋にfrenko_sender.plを上げるラッパーを忘れないでください。



ここで、ところで、燃費を最適化する方法について考える必要があります=)。



たとえば、頻度でソートされた特定の順序で実行します。 つまり、他のメトリックよりも多くのホストがある場合、通常のホストで実行を開始する必要があります。 また、いくつかのレギュラーの代わりに、index()関数を使用できます。



現時点では、1秒あたり1042ログ行を処理する場合(15のレギュラーの構成で)、プロセッサの負荷は30〜32%であることが判明しています。

  4-  Intel(R) Xeon(R) CPU E5405 @ 2.00GHz
      
      





そのような数字を見ると、悲しくなります。 4番目のパラメーターとしてindex()の式を追加することにより、構成をやり直します-速度を上げる必要があります。



frenko_config

 errdescr= 9 [E2;Javascript error at web interface - nginx acclog] 1 errdescr= errorcode=\d+ 10 [E3;Fail upload with errorcode - nginx acclog] 1 errorcode=
      
      





また、設定の読み込みと解析機能を少し変更します。

 sub load_config{ my $re=shift; %{$re}=(); open (F,$CONFIG) || die 'cant open config '.$CONFIG; while (<F>){ next if /^[\s\n]+$/; my ($regexp,$key,undef,$rate,$index)=split("\t",$_,5); $index=~s/[\n\t\r]//g; $$re{$key}{re}=$regexp; $$re{$key}{index}=$index; $$re{$key}{rate}=$rate || 1; } close (F); } sub handle_incoming_connection_udp { lambda { my ( $addr, $msg ) = @_; if (length($msg)>5){ if (substr($msg,0,8) eq 'reconfig'){ load_config(\%Regexps); } else { foreach my $key(keys %Regexps){ if (index($msg,$Regexps{$key}{index})>=0){ if ($Regexps{$key}{index} eq $Regexps{$key}{re}){ sendstat($key,$Regexps{$key}{rate},$memd); last; }elsif($msg=~/$Regexps{$key}{re}/){ sendstat($key,$Regexps{$key}{rate},$memd); last; } } } } } } }
      
      





これらの変更により、負荷の割合がわずかに減少し、25〜28%になりました。 理想的には、少なくとも10%を取得します。



memcachedデータは次のとおりです。

 STAT uptime 601576 STAT bytes_read 358221242 STAT bytes_written 92007777 STAT bytes 5018318 STAT curr_items 62269 STAT total_items 557908 STAT reclaimed 372969
      
      







進む



frenko_listen.pl行(130-157)

 sub signal_handler{ $time_to_die=1; } getopts('d:', my $opts = {}); if (exists $opts->{d}) { print "start daemon\n"; my $pid=fork; exit if $pid; die 'Couldn\'t fork: '.$! unless defined($pid); for my $handle (*STDIN, *STDOUT, *STDERR){ open ($handle, '+<', '/dev/null' ) || die 'Cant\t reopen '.$handle.' to /dev/null: '.$!; } POSIX::setsid() || die 'Can\'t start a new session: '.$!; $time_to_die=0; $SIG{TERM} = $SIG{INT} = $SIG{HUP}= \&signal_handler; until ($time_to_die){ runserv(); } } else { runserv() }
      
      





ここでは、サーバーが-dフラグによって悪魔化されることを教えました。



合計で、拡張性の高いサーバーアーキテクチャが得られました。 小規模の場合はそのままです。 クライアント側に移りましょう。



クライアント部

グラフの多くの図面を確認しましたが、フラッシュなしでグラフを描画したり、ajaxを使用して新しいポイントのデータを動的に読み込んだりするため、ハイチャートに落ち着きました。 私たちはハイチャートに行き、ドキュメントに記述されているようにすべてを行います。 すべてがシンプルです。



fastcgiスクリプトを使用してクライアント部分のデータを提供します。これはmemcacheに送られ、一定期間ポイントを提供します。



チャート自体は2つのクエリを実行します。これは、初期初期化とチャートの新しいポイントの一部です。 これらのクエリに対する回答は次のようになります。

/frenky.fpl?init=1&accur=2

 {"status":"1","init":[["U1",[[1338188480,19],[1338188482,21],[1338188484,11],[1338188486,19],[1338188488,13],[1338188490,9],[1338188492,13],[1338188494,18],[1338188496,14],[1338188498,21],[1338188500,17],[1338188502,13],[1338188504,21],[1338188506,19],[1338188508,22],[1338188510,23],[1338188512,14],[1338188514,17],[1338188516,24],[1338188518,15],[1338188520,23],[1338188522,19],[1338188524,20]],"User uploaded the file - apache errlog"]]}
      
      





/frenky.fpl?refresh=1&accur=2

 {"status":"1","refresh":[[1338188814,20],[1338188814,1],[1338188814,0],[1338188814,0],[1338188814,0],[1338188814,0],[1338188814,0],[1338188814,8],[1338188814,7],[1338188814,0],[1338188814,0],[1338188814,0]]}
      
      







私が遭遇した問題の1つは、実行中の2つのfastcgiプロセスの場合に、それらの構成を通常のもので更新したい場合です。 Webインターフェースを介して両方のプロセスに到達する方法-まだ美しい解決策が見つからないため、Apacheを再起動するだけです。



jsやfcgiを含むすべてのソースをここrdash.rarからダウンロードできます。



更新

コメントに記載されているすべての最適化の後、次の数値が受信されました。

毎秒5万行の負荷では、CPUの80%を消費します

ソートルールを追加すると、1秒あたり8万行、CPUの90%



https://github.com/frenkyoptic/rdash



All Articles