#!/usr/bin/perl #================================================================================= # # M A I N # #================================================================================= use Tk; use Tk::Font; use Tk::ErrorDialog; use Tk::Dialog; use Tk::ProgressBar; use Time::HiRes qw(gettimeofday); use Ivy; use Carp; use strict; use Getopt::Long; use Tk::CmdLine; use vars qw/$opt_help $opt_b $opt_bus $opt_size $opt_undersize $opt_autostart $opt_repeat $opt_startregexp $opt_stopregexp $opt_timegranularity $opt_debug/; # geometry my $minW = 1024; my $minH = 768; my $smallsized = 0; my $coef = 1; my $ivy_port; # undef:=> default value is treated by ivy-perl my $appname = 'IvyReplay'; $appname =~ s/ /_/g; # replay my $replay_current_t0; my $replay_current_t; my $replay_data_t0; my $replay_data_t; my $replay_maxgap = 0.2; # sec my $replay_regulpct = [50, 20]; # % my $replay_factor = 1000; # msec my $replay_current_factor = $replay_factor; my $replay_speed = 1; my $replay_runnable = 1; my $replay_running = 0; my $replay_time_granularity = 1; my $replay_time_decimalplaces = 0; my $replay_timer; my $mw; my $replay_text; my $replay_repeat; my %replay_msg; my $replay_time; my $replay_min_time; my $replay_max_time; my $replay_last_time; my $replay_stepbystep; my $replay_bg = 'gray75'; my $replay_fg = 'black'; my $replay_bg_orig; my $replay_fg_orig; my $replay_hour; #---------------------------------------------------------------------------------- # command line options management #---------------------------------------------------------------------------------- Tk::CmdLine::SetArguments(-font => '7x14'); Tk::CmdLine::SetArguments(); if (not GetOptions('-help', '-b=s', '-bus=s', '-size=s', '-undersize', '-repeat', '-startregexp=s', '-debug', '-timegranularity=s', '-autostart', '-stopregexp=s') or $opt_help) { print "\n"; print "Usage: ivyreplay [-b[us] bus] [-help]\n"; print " [-size size] [-undersize] [-autostart]\n"; print " [-repeat] [-timegranularity float>0]\n"; print " [-startregexp regexp] [-stopregexp regexp]\n"; print " [standard X11 options...]\n"; print " inputfile\n"; print "\n"; print "Options :\n"; print " -b <[addr]:port> Ivy bus (\$IVYBUS by default)\n"; print " -size Size of the main window\n"; print " -undersize If set, slightly reduces the main window\n"; print " -autostart If set, messages are replayed as soon as the\n"; print " -repeat If set, repeat infinitely the replay sequence\n"; print " -timegranularity Time granularity (in second) for replaying\n"; print " messages (1 by default)\n"; print " -startregexp Regexp to match for starting replay\n"; print " ('^ClockStart\$'' by default)\n"; print " -stopregexp Regexp to match for stopping replay\n"; print " ('^ClockStop\$'' by default)\n"; print "\n"; exit ; } croak "Error : an input file is expected\n" unless @ARGV > 0; $replay_repeat = $opt_repeat; if ($opt_bus) { $ivy_port = $opt_bus; } elsif ($opt_b) { $ivy_port = $opt_b; } my $title; my $ivy_version = $Ivy::VERSION; my $ivy_cvsrevision = 0; my $ivy_versionstring = "v$ivy_version"; if ($ivy_version =~ s/Revision: (.*)//) { $ivy_version = $1; $ivy_cvsrevision = 1; $ivy_versionstring = "v$ivy_version (cvs revision)"; } if ($ivy_port) { $title = "Ivyreplay ($ivy_port) - Ivy $ivy_versionstring"; } else { $title = "Ivyreplay (default port) - Ivy $ivy_versionstring"; } $replay_time_granularity = $opt_timegranularity if $opt_timegranularity > 0; if ($replay_time_granularity >= 1) { $replay_time_decimalplaces = 0; } else { $replay_time_decimalplaces = $replay_time_granularity; $replay_time_decimalplaces =~ s/^\+?\d*\.//; $replay_time_decimalplaces = length($replay_time_decimalplaces); } $replay_hour = &replayTime(); #=================================== # # Size options if($opt_size eq "UXGA" || $opt_size eq "1600") { $minW = 1600; $minH = 1200; $smallsized = 0; $coef = 1.2; Tk::CmdLine::SetArguments(-font => '10x20'); Tk::CmdLine::SetArguments(); } elsif ($opt_size eq "SXGA" || $opt_size eq "1280") { $minW = 1280; $minH = 1024; $smallsized = 0; $coef = 1.1; Tk::CmdLine::SetArguments(-font => '8x13'); Tk::CmdLine::SetArguments(); } elsif ($opt_size eq "XGA" || $opt_size eq "1024") { $minW = 1024; $minH = 768; $smallsized = 1; $coef = 1; Tk::CmdLine::SetArguments(-font => '7x14'); Tk::CmdLine::SetArguments(); } elsif ($opt_size eq "SVGA" || $opt_size eq "800") { $minW = 800; $minH = 600; $smallsized = 1; $coef = 0.64; Tk::CmdLine::SetArguments(-font => '6x10'); Tk::CmdLine::SetArguments(); } elsif ($opt_size eq "VGA" || $opt_size eq "640"){ $minW = 640; $minH = 480; $smallsized = 1; $coef = 0.48; Tk::CmdLine::SetArguments(-font => '5x7'); Tk::CmdLine::SetArguments(); } if ($opt_undersize) { $minW -= 10; $minH -= 30; } #================================================================================= # # H M I # #================================================================================= my $mw = MainWindow->new(); # Set default min size $mw->geometry($minW."x".$minH); $mw->minsize($minW, $minH); $mw->title($title); # Key-Tab binding is deactivated, because this event will be used by entries # for completion functionality $mw->bind('', sub {Tk->break}); my $focusedtext; $mw->bind("Tk::Text", "", [sub { &clearSearch() if $_[1] ne $focusedtext; $focusedtext = $_[1];}, Ev('W')]); #---------------------------------------------------------------------------------- # Progress bar #---------------------------------------------------------------------------------- my $tpl = $mw->Toplevel; $tpl->Popup; $tpl->raise($mw); $tpl->title(""); $tpl->geometry("300x50"); my $progressbar = $tpl->ProgressBar(-from => 0, -length => 200, -borderwidth => 2, -colors => [ 0 => 'yellow'], -relief => 'sunken', -resolution => 0, -anchor => 'w', )->pack(-fill => 'both', -expand => 1, ); $progressbar->value(0); $tpl->withdraw; #================================================================================= # # Ivy initialisation and Ivy bindings # #================================================================================= # init Ivy bus and start it. Ivy->init(-loopMode => 'TK', -appName => $appname, -ivyBus => $ivy_port, -onDieFunc => [\&quit], ); my $ivy = Ivy->new(); $ivy->start; #================================================================================= # # Options and arguments test # #================================================================================= &build(); # load input files my $file = $ARGV[0]; if (not open(IN, $file)) { $mw->Tk::Error("Can't open file '$file' ($!)"); } else { &showProgressbar(); my ($step, $timefound) = &stepsnumber; if ($timefound) { &loadfileForReplay($step); } else { $mw->Tk::Error("No time information in file '$file'. ". "Can't be replayed."); } &replayStart() if $opt_autostart; &hideProgressbar(); close(IN); } MainLoop; #================================================================================== # # Functions # #================================================================================== #---------------------------------------------------------------------------------- # Functions related to input/output files #---------------------------------------------------------------------------------- sub stepsnumber { my $step = 0; my $timefound; my $lc = 0; # get lines number and test if exists time field while() { chomp; next if (/^applications=/ or /^(marker\d+)$/ or /^(messages_number=)/ or /^\#/); my ($sender, $message) = split(/\s+/, $_, 2); if ($message =~ /^(\d[\d\.]+\d)\s+.*/) { $timefound = 1; } $lc++; } $step = int($lc/10); $progressbar->configure(-to => $step*10); seek(IN, 0, 0); return ($step, $timefound); } # end stepsnumber sub build { %replay_msg = (); $replay_time = undef; # build replay window my $ctrl_fm = $mw->Frame()->pack(-side => 'bottom', -pady => 5); $replay_text = $mw->Scrolled('Text', -scrollbars => 'e', -spacing1 => 2, -spacing2 => 0, -spacing3 => 2, )->pack(-fill => 'both', -expand => 1, -side => 'bottom'); # colors $replay_bg_orig = $replay_text->cget(-background); $replay_fg_orig = $replay_text->cget(-foreground); &wheelmousebindings($replay_text); # build speed control buttons my $realspeedrate = 1; my $ctrl_fm1 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); $ctrl_fm1->Radiobutton(-text => "x0.1", -indicatoron => 0, -height => 2, -width => 4, -value => 0.1, -variable => \$replay_speed, -command => sub { $realspeedrate = 0 }, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm1->Radiobutton(-text => "x0.5", -indicatoron => 0, -height => 2, -width => 4, -value => 0.5, -variable => \$replay_speed, -command => sub { $realspeedrate = 0 }, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm1->Radiobutton(-text => "x1", -indicatoron => 0, -height => 2, -width => 4, -value => 1, -variable => \$replay_speed, -command => sub { return if $realspeedrate == 1; $realspeedrate = 1; ®ulationReset(); }, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm1->Radiobutton(-text => "x2", -indicatoron => 0, -height => 2, -width => 4, -value => 2, -variable => \$replay_speed, -command => sub { $realspeedrate = 0 }, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm1->Radiobutton(-text => "x5", -indicatoron => 0, -height => 2, -width => 4, -value => 5, -variable => \$replay_speed, -command => sub { $realspeedrate = 0 }, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm1->Radiobutton(-text => "x10", -indicatoron => 0, -height => 2, -width => 4, -value => 10, -variable => \$replay_speed, -command => sub { $realspeedrate = 0 }, -selectcolor => 'white')->pack(-side => 'left'); # build hour label my $ctrl_fm2 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); my $hour_lab = $ctrl_fm2->Label(-borderwidth => 1, -relief => 'ridge', -height => 2, -width => ($replay_time_decimalplaces > 0) ? 10 + $replay_time_decimalplaces : 9, -textvariable => \$replay_hour, )->pack(-side => 'left'); # build control buttons my $ctrl_fm3 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); $ctrl_fm3->Radiobutton(-text => "Play", -indicatoron => 0, -width => 6, -height => 2, -value => 0, -command => \&replayStart, -variable => \$replay_runnable, -selectcolor => 'white')->pack(-side => 'left'); $ctrl_fm3->Radiobutton(-text => "Pause", -indicatoron => 0, -width => 6, -height => 2, -value => 1, -command => \&replayStop, -variable => \$replay_runnable, -selectcolor => 'white')->pack(-side => 'left'); # build loop and step by step checkbuttons my $ctrl_fm4 = $ctrl_fm->Frame(-relief => 'ridge', -borderwidth => 1 )->pack(-side => 'left', -padx => 10, -fill => 'y', -expand => 1); $ctrl_fm4->Checkbutton(-text => "Repeat", -variable => \$replay_repeat, )->pack(-side => 'left'); $ctrl_fm4->Checkbutton(-text => "Step by\nstep", -variable => \$replay_stepbystep, -command => \&replayStop, )->pack(-side => 'left', -padx => 10); # build close button $ctrl_fm->Button(-text => "Close", -command => \&replayClose, -height => 1)->pack(-side => 'left', -padx => 10, -fill => 'y', -expand => 1); $mw->update; $mw->minsize($mw->width, $mw->height); } # end build sub loadfileForReplay { my $step = shift; my $line = 0; # display messsages to replay my ($sender, $time, $message); while() { chomp; next if /^\#/ or /^applications=/ or /^messages_number=/ or /^\s*$/ or /^(marker\d+)$/; ($sender, $time, $message) = split(/\s+/, $_, 3); if ($replay_time_granularity >= 1) { $time = int($time); } else { $time = sprintf("%.".$replay_time_decimalplaces."f", $time); } if (defined $replay_max_time) { $replay_max_time = $time if $time > $replay_max_time; } else { $replay_max_time = $time; } if (defined $replay_min_time) { $replay_min_time = $time if $time < $replay_min_time; } else { $replay_min_time = $time; } $line++; $message =~ s/^\"//; $message =~ s/\"$//; push(@{$replay_msg{$time}}, $message); $replay_text->insert('end', &replayTime($time)." ".$message."\n", $time); # when user click on a message the begin time changes. $replay_text->tagBind($time, '<1>', [sub { my $ti = $_[1]; my $replay_was_running; if ($replay_running) { $replay_was_running = 1; &replayStop; } $replay_text->tagConfigure($replay_last_time, -foreground => $replay_fg_orig, -background => $replay_bg_orig) if defined $replay_last_time; $replay_text->tagConfigure($ti, -foreground => $replay_fg, -background => $replay_bg); $replay_last_time = $ti; $replay_hour = &replayTime($ti); $replay_time = $ti; ®ulationReset(); &replayStart if $replay_was_running; }, $time]); &setProgressbar($line, $step); } $mw->raise; $replay_time = $replay_min_time; $replay_text->configure(-state => 'disabled'); # ivy bindings $ivy->bindRegexp($opt_startregexp, [\&replayStart]); $ivy->bindRegexp($opt_stopregexp, [\&replayStop]); } # end loadfileForReplay sub showProgressbar { $progressbar->value(0); $progressbar->toplevel->deiconify; $progressbar->toplevel->raise($mw); } # end showProgressbar sub hideProgressbar { $progressbar->toplevel->withdraw; } # end hideProgressbar sub setProgressbar { my ($line, $step) = @_; $progressbar->value($line); $progressbar->toplevel->raise($mw); return unless defined $step; $progressbar->update if ($step == 0 or $line % $step == 0); } # end setProgressbar #---------------------------------------------------------------------------------- # Functions related to replay #---------------------------------------------------------------------------------- sub replayStart { my $loopflag = ($_[0] == 1) ? 1 : undef; $replay_data_t0 = $replay_time unless defined $replay_data_t0; $replay_current_t0 = gettimeofday() unless defined $replay_current_t0; my $t = $replay_text; return if $replay_running and not $loopflag; return if $loopflag and not $replay_running; $replay_runnable = 0; $replay_running = 1; if ($replay_time > $replay_max_time) { if ($replay_repeat == 1) { $replay_time = $replay_min_time; } else { $t->tagConfigure($replay_last_time, -foreground => $replay_fg_orig, -background => $replay_bg_orig) if defined $replay_last_time; $replay_running = 0; $replay_runnable = 1; return; } } $replay_hour = &replayTime($replay_time); my $data_dt = $replay_time - $replay_data_t0; if (defined $replay_msg{$replay_time}) { for my $msg (@{$replay_msg{$replay_time}}) { $ivy->sendMsgs($msg); $t->tagConfigure($replay_last_time, -foreground => $replay_fg_orig, -background => $replay_bg_orig) if defined $replay_last_time; $t->tagConfigure($replay_time, -foreground => $replay_fg, -background => $replay_bg); } $replay_last_time = $replay_time; my ($i) = $t->tagRanges($replay_time); $t->see($i) if defined $i; } $replay_time += $replay_time_granularity; $replay_time = sprintf("%.".$replay_time_decimalplaces."f", $replay_time) if $replay_time_decimalplaces > 0; # step by step mode if ($replay_stepbystep) { while (not defined $replay_msg{$replay_time}) { $replay_time += $replay_time_granularity; $replay_time = sprintf("%.".$replay_time_decimalplaces."f", $replay_time) if $replay_time_decimalplaces > 0; last if $replay_time > $replay_max_time; } $replay_running = 0; $replay_runnable = 1; # continuous mode } else { my $dt = gettimeofday() - $replay_current_t0; my $dt2 = sprintf("%.2f", $dt); my $d = sprintf("%.3f", $dt-$data_dt); # if speed rate != 1, time regulation is deactivated if ($replay_speed != 1) { $replay_current_factor = $replay_factor; print "replay_speed=$replay_speed => time regulation deactivated\n" if $opt_debug; # if replay is running behind schedule (retard) } elsif ($dt > $data_dt + $replay_maxgap and $replay_current_factor <= $replay_factor) { $replay_current_factor *= (1 - $replay_regulpct->[0]/100); print "data_dt=$data_dt dt=$dt2 delay=$d [--] ". "replay_factor=$replay_current_factor\n" if $opt_debug; # if replay is getting ahead of schedule (avance) } elsif ($dt < $data_dt - $replay_maxgap and $replay_current_factor >= $replay_factor) { $replay_current_factor *= (1 + $replay_regulpct->[1]/100); print "data_dt=$data_dt dt=$dt2 delay=$d [++] ". "replay_factor=$replay_current_factor\n" if $opt_debug; # if replay is on time } else { print "data_dt=$data_dt dt=$dt2 delay=$d\n" if $opt_debug; $replay_current_factor = $replay_factor; } $replay_timer = $mw->after($replay_current_factor*$replay_time_granularity/$replay_speed, [\&replayStart, 1]); } } # end replayStart sub replayStop { $replay_running = 0; $replay_runnable = 1; $replay_text->afterCancel($replay_timer) if defined $replay_timer; ®ulationReset(); } # end replayStop sub replayClose { $ivy->bindRegexp($opt_startregexp); $ivy->bindRegexp($opt_stopregexp); &replayStop(); $replay_text->after(400, sub {$mw->destroy}); $replay_speed = 1; $replay_repeat = $opt_repeat; $replay_stepbystep = undef; $replay_hour = '--:--:--'; } # end replayClose sub replayTime { my $time = shift; if (defined $time) { my ($s, $m, $h) = localtime($time); if ($replay_time_decimalplaces > 0) { my $dec = substr($time, -$replay_time_decimalplaces, $replay_time_decimalplaces); return sprintf("%02d:%02d:%02d.%s", $h, $m, $s, $dec); } else { return sprintf("%02d:%02d:%02d", $h, $m, $s); } } elsif ($replay_time_decimalplaces > 0) { return "--:--:--."."-" x $replay_time_decimalplaces; } else { return "--:--:--"; } } # end replayTime #---------------------------------------------------------------------------------- # General functions #---------------------------------------------------------------------------------- sub regulationReset { $replay_data_t0 = undef; $replay_current_t0 = undef; } # end regulationReset sub wheelmousebindings { my $w = shift; my $count = shift; my $count = 3 unless $count > 0; $w->bind('', sub {$w->yview('scroll', -1, 'page')}); $w->bind('', sub {$w->yview('scroll', -1, 'unit')}); $w->bind('', sub {$w->yview('scroll', -$count, 'unit')}); $w->bind('', sub {$w->yview('scroll', 1, 'page')}); $w->bind('', sub {$w->yview('scroll', 1, 'unit')}); $w->bind('', sub {$w->yview('scroll', $count, 'unit')}); } # end wheelmousebindings sub quit { print "Quit\n"; exit; } # end quit __END__ =head1 NAME ivyreplay - a graphical application for replaying Ivy messages =head1 SYNOPSIS B [B<-b> ivybus] [B<-help>] [B<-size> window size] [B<-undersize>] [B<-autostart>] [B<-repeat>] [B<-timegranularity> float>0] [B<-startregexp> regexp] [B<-stopregexp> regexp] [standard X11 options...] inputfile =head1 DESCRIPTION IvyReplay is dedicated to replay Ivy messages. Input file must have been generated with ivymon v1.6 or later (it must contain time information). =head1 OPTIONS =over =item B<-b> ip:port Set the bus domain and port number to be used. Use $IVYBUS variable if defined. Default is 127.255.255.255:2010. =item B<-help> Get some help =item B<-size> window size Set the size of the IvyMon window. Can be VGA or 640, SVGA or 800, XGA or 1024, SXGA or 1280, UXGA or 1600. Default is XGA (1024x768). =item B<-undersize> Slightly reduce the IvyMon window to fit it in screen with borders of the window manager. Option not set by default. =item B<-repeat> If set, the replay sequence will be repetitive. =item B<-timegranularity> float>0 Specify the time granularity in second for replaying messages. Set to 1 by default. =item B<-startregexp> regexp Specify the ivy regular expression to match in order to start the replay sequence. Default is '^ClockStart'. You can do manually the same using the B button of the Replay window. =item B<-stopregexp> regexp Specify the ivy regular expression to match in order to stop the replay sequence. Default is '^ClockStop'. You can do manually the same using the B button of the Replay window. =back =head1 EXAMPLE ivyreplay -b 10.192.36.255:3456 -repeat -startregexp '^ReplayOn' -stopregexp '^ReplayOff' /my/dir/log.ivy =head1 AUTHORS Daniel Etienne