# HG changeset patch # User devi # Date 1131032996 -7200 # Node ID ba4d6515b8fdd5cc1de80193d7eb9944f38f3f07 # Parent 4b86595adb4b0bf713857ea44c3d62c537b5ee0a Выполнен шаг (3) в плане (N05) по построению распределённой системы lilalo. Агент l3-агент в реальном времени анализирует скрипты в указанном ему каталоге и по мере обнаружения новых завершённых команд записывает их в кэш-файл. Данные о том, докуда разобран каждый скрипт-файл сохраняются во временном файле, для того чтобы при перезапуске агента он мог продолжить разбор с того места, где он был остановлен в прошлый раз, а не копировал данные в кэш-файл повторно. Агент запускается для каждого пользователя системы. Если агент обнаружил свою копию работающую от имени того же пользователя, он автоматически завершается. Поиск копии агента выполняется так: просматривается pid-файл агента - если его нет, считается, что и агент не запущен (Внимание! Не удаляйте pid-файл!! Работа нескольких агентов от имени одного пользователя может быть некорректной!) Если он есть, выполняется проверка, действительно ли процесс с таким идентификатором это l3-агент текущего пользователя. Если нет, pid-файл удаляется, и агент запускается. Нормальное завершение агента, работающего в режиме демона, выполняется с помощью сигнала TERM. При завершении агент автоматически стирает свой pid-файл. Добавлены атрибуты команды, хранящие информацию о участке бинарного файла скрипта, соответствующей команды: raw_start - начало блока команды raw_output_start - начало вывода команды raw_end - окончание вывода raw_file - имя бинарного файла Файлы: (могут меняться с помощью конфигурационных параметров) ~/.labmaker/.cache.dat ~/.labmaker/cache.xml ~/.labmaker/l3-agent.pid Конфигурационные параметры: cache_stat Имя файла с информацией о текущей позиции разбора в каждом файле mode Режим, в котором работает агент. Допустимые значения: daemon - в режиме непрерывного опроса каталога Программа не завершается после окончания анализа, а ждёт появления новых данных normal - однократный анализ каталога. Программа завершается после окончания анализа данных daemon_sleep_interval Интервал через который агент просматривает каталог скриптов в поисках новых данных detach Нужно ли выполнять отключение от терминала при работе в режиме демона? (строго говоря, если процесс не отключился от терминала, то и в режиме демона он работать не может. Здесь имеется в виду режим непрерывного опроса каталога) agent_pidfile Путь к файлу, который будет хранить идентификатор процесса агента. l3-agent Имя, под которым будет известен процесс l3-agent Незначительные исправления: * убрана отладочная информация о new_commands и new_files из frontend'а diff -r 4b86595adb4b -r ba4d6515b8fd l3-agent --- a/l3-agent Wed Nov 02 19:25:39 2005 +0200 +++ b/l3-agent Thu Nov 03 17:49:56 2005 +0200 @@ -5,6 +5,7 @@ # use strict; +use POSIX; use Term::VT102; use Text::Iconv; use Data::Dumper; @@ -21,6 +22,13 @@ our %Commands_Stat; # Statistics about commands usage our %Files_Stat; # Statistics about commands usage +our %Script_Files; # Информация о позициях в скрипт-файлах, + # до которых уже выполнен разбор + # и информация о времени модификации файла + # $Script_Files{$file}->{size} + # $Script_Files{$file}->{tell} + +our $Killed =0; # В режиме демона -- процесс получил сигнал о завершении sub init_variables; sub main; @@ -34,6 +42,10 @@ sub print_command_lines; sub printq; +sub save_cache_stat; +sub load_cache_stat; + + sub load_diff_files { my @pathes = @_; @@ -183,7 +195,8 @@ my $lab_scripts_mask = $_[1]; my $cline_re_base = qq' - (?:\\^?([0-9]*C?)) # exitcode + ( + (?:\\^?([0-9]*C?)) # exitcode (?:_([0-9]+)_)? # uid (?:_([0-9]+)_) # pid (...?) # day @@ -193,6 +206,7 @@ .\\[50D.\\[K # killing symbols (.*?([\$\#]\\s?)) # prompt (.*) # command line + ) '; #my $cline_re = qr/$cline_re_base(?:$cline_re_base|$)/x; #my $cline_re = qr/(?:$cline_re_base)*$cline_re_base$/x; @@ -221,9 +235,20 @@ for $file (@lab_scripts){ #printf "\t%i %3.2f\n", $ii, (100*$ii++/$files_number) if $Config{"verbose"} =~ /y/; + + # Пропускаем файл, если он не изменялся со времени нашего предудущего прохода + my $size = (stat($file))[7]; + next if ($Script_Files{$file} && $Script_Files{$file}->{size} && $Script_Files{$file}->{size} >= $size); open (FILE, "$file"); binmode FILE; + + # Переходим к тому месту, где мы окончили разбор + seek (FILE, $Script_Files{$file}->{tell}, 0) if $Script_Files{$file}->{tell}; + $Script_Files{$file}->{size} = $size; + $Script_Files{$file}->{tell} = 0 unless $Script_Files{$file}->{tell}; + + $file =~ m@.*/(.*?)-.*@; my $tty = $1; @@ -231,6 +256,7 @@ my %cl; my $last_output_length=0; while () { + $commandlines_processed++; # time @@ -247,7 +273,7 @@ # Previous command my %last_cl = %cl; - my $err = $1 || ""; + my $err = $2 || ""; =cut @@ -380,11 +406,14 @@ raw_start (*) Начало блока командной строки в файле бинарного представления + raw_output_start (*) + Начало блока вывода + raw_end (*) Конец блока командной строки в файле бинарного представления raw_cline (*) - Необработанная командная строка в бинарном виде + Необработанная командная строка (без приглашения) в бинарном виде raw_data (*) Бинарное представление команды и результатов её выполнения @@ -402,17 +431,24 @@ =cut # Parse new command - $cl{"uid"} = $2; + $cl{"uid"} = $3; $cl{"euid"} = $cl{"uid"}; # Если в команде обнаружится sudo, euid поменяем на 0 - $cl{"pid"} = $3; - $cl{"day"} = $4; - $cl{"lab"} = $5; - $cl{"hour"} = $6; - $cl{"min"} = $7; - $cl{"sec"} = $8; - $cl{"fullprompt"} = $9; - $cl{"prompt"} = $10; - $cl{"raw_cline"} = $11; + $cl{"pid"} = $4; + $cl{"day"} = $5; + $cl{"lab"} = $6; + $cl{"hour"} = $7; + $cl{"min"} = $8; + $cl{"sec"} = $9; + $cl{"fullprompt"} = $10; + $cl{"prompt"} = $11; + $cl{"raw_cline"} = $12; + + { + use bytes; + $cl{"raw_start"} = tell (FILE) - length($1); + $cl{"raw_output_start"} = tell FILE; + } + $cl{"raw_file"} = $file; $cl{"err"} = 0; $cl{"output"} = ""; @@ -454,6 +490,7 @@ } # Error code + $last_cl{"raw_end"} = $cl{"raw_start"}; $last_cl{"err"}=$err; $last_cl{"err"}=130 if $err eq "^C"; @@ -484,11 +521,16 @@ if (!$Config{"lab"} || $cl{"lab"} eq $Config{"lab"}) { # Changing encoding for (keys %last_cl) { + next if /raw/; $last_cl{$_} = $converter->convert($last_cl{$_}) if ($Config{"encoding"} && $Config{"encoding"} !~ /^utf-8$/i); } push @Command_Lines, \%last_cl; + + # Сохранение позиции в файле, до которой выполнен + # успешный разбор + $Script_Files{$file}->{tell} = $last_cl{raw_end}; } next; } @@ -620,12 +662,14 @@ sub print_command_lines { my $output_filename=$_[0]; - open(OUT, ">", $output_filename) + my $mode = ">"; + $mode =">>" if $Config{mode} eq "daemon"; + open(OUT, $mode, $output_filename) or die "Can't open $output_filename for writing\n"; - print OUT "\n"; + #print OUT "\n"; my $cl; my $in_range=0; @@ -708,6 +752,10 @@ # Начинаем вывод команды print OUT "\n"; print OUT "\n"; + print OUT "",$cl->{raw_start},"\n"; + print OUT "",$cl->{raw_output_start},"\n"; + print OUT "",$cl->{raw_end},"\n"; + print OUT "",$cl->{raw_file},"\n"; print OUT "",$cl->{tty},"\n"; print OUT "",$out_class,"\n"; print OUT ""; @@ -739,10 +787,31 @@ } - print OUT "\n"; + #print OUT "\n"; close(OUT); + save_cache_stat(); } +sub save_cache_stat +{ + open (CACHE, ">$Config{cache_stat}"); + for my $f (keys %Script_Files) { + print CACHE "$f\t",$Script_Files{$f}->{size},"\t",$Script_Files{$f}->{tell},"\n"; + } + close(CACHE); +} + +sub load_cache_stat +{ + if (open (CACHE, "$Config{cache_stat}")) { + while() { + my ($f, $size, $tell) = split /\t/; + $Script_Files{$f}->{size} = $size; + $Script_Files{$f}->{tell} = $tell; + } + close(CACHE); + }; +} =cut sub print_command_lines2 @@ -806,6 +875,11 @@ main(); +sub process_was_killed +{ + $Killed = 1; +} + sub main { $| = 1; @@ -817,10 +891,64 @@ load_diff_files($lab_log); } -load_command_lines($Config{"input"}, $Config{"input_mask"}); -sort_command_lines; -process_command_lines; -print_command_lines($Config{"cache"}); +if ($Config{"mode"} ne "daemon") { + load_command_lines($Config{"input"}, $Config{"input_mask"}); + sort_command_lines; + process_command_lines; + print_command_lines($Config{"cache"}); +} +else { + if (open(PIDFILE, $Config{agent_pidfile})) { + my $pid = ; + close(PIDFILE); + if ( ! -e "/proc/$pid" || !`grep $Config{"l3-agent"} /proc/$pid/cmdline && grep "uid:.*\b$<\b" /proc/$pid/status`) { + print "Removing stale pidfile\n"; + unlink $Config{agent_pidfile}; + or die "Can't remove stale pidfile ". $Config{agent_pidfile}. " : $!"; + } + else { + print "l3-agent is already running\n"; + exit(0); + } + } + if ($Config{detach} =~ /^y/i) { + #$Config{verbose} = "no"; + my $pid = fork; + exit if $pid; + die "Couldn't fork: $!" unless defined ($pid); + + open(PIDFILE, ">", $Config{agent_pidfile}) + or die "Can't open pidfile ". $Config{agent_pidfile}. " for wrting: $!"; + print PIDFILE $$; + close(PIDFILE); + + for my $handle (*STDIN, *STDOUT, *STDERR) { + open ($handle, "+<", "/dev/null") + or die "can't reopen $handle to /dev/null: $!" + } + + POSIX::setsid() + or die "Can't start a new session: $!"; + + $0 = $Config{"l3-agent"}; + + $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&process_was_killed; + } + while (not $Killed) { + @Command_Lines = (); + @Command_Lines_Index = (); + load_cache_stat(); + load_command_lines($Config{"input"}, $Config{"input_mask"}); + if (@Command_Lines) { + sort_command_lines; + process_command_lines; + print_command_lines($Config{"cache"}); + } + sleep($Config{"daemon_sleep_interval"} || 1); + } + + unlink $Config{agent_pidfile}; +} } diff -r 4b86595adb4b -r ba4d6515b8fd l3-frontend --- a/l3-frontend Wed Nov 02 19:25:39 2005 +0200 +++ b/l3-frontend Thu Nov 03 17:49:56 2005 +0200 @@ -306,15 +306,6 @@ $Result{"body"} .= "\n"; $Result{"body"} .= ""; } - else { - $Result{"body"} .= "". - "
"; - $Result{"body"} .= ""; - $Result{"body"} .= "commands ".join(" ",@new_commands)."
"; - $Result{"body"} .= "files ".join(" ",@new_files)."
"; - $Result{"body"} .= "
\n"; - $Result{"body"} .= "
"; - } } # Вывод очередной команды окончен diff -r 4b86595adb4b -r ba4d6515b8fd l3config.pm --- a/l3config.pm Wed Nov 02 19:25:39 2005 +0200 +++ b/l3config.pm Thu Nov 03 17:49:56 2005 +0200 @@ -40,6 +40,7 @@ "encoding" => "utf-8", "cache" => "/tmp/report.xml", + "cache_stat" => "/tmp/.report.dat", "output" => "/tmp/report.html", #"output" => "report.xml", @@ -55,7 +56,14 @@ comment_width => "300", time_width => "60", + + "mode" => "daemon", # daemon | normal + "daemon_sleep_interval" => "1", + "detach" => "yes", + "agent_pidfile" => "$ENV{HOME}/.labmaker/l3-agent.pid", + "l3-agent" => "l3-agent", + "course-name" => "", "course-code" => "", "course-date" => "",