#!/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_light $opt_autostart $opt_repeat $opt_startregexp $opt_padding $opt_stopregexp $opt_timegranularity $opt_debug/; our $VERSION = '1.24'; # geometry 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; $opt_startregexp = '^ClockStart\$'; $opt_stopregexp = '^ClockStop\$'; my $padding = 2; #---------------------------------------------------------------------------------- # command line options management #---------------------------------------------------------------------------------- Tk::CmdLine::SetArguments(-font => '-Misc-Fixed-Medium-R-Normal--14-130-75-75-C-70-ISO8859-1'); Tk::CmdLine::SetArguments(); if (not GetOptions('-help', '-b=s', '-bus=s', '-light', '-repeat', '-startregexp=s', '-debug', '-timegranularity=s', '-autostart', '-stopregexp=s', '-padding=s') or $opt_help) { print "\n"; print "IvyReplay version $VERSION\n"; print "\n"; print "Usage: ivyreplay [-b[us] bus] [-help]\n"; print " [-autostart] [-light] [-padding int>0]\n"; print " [-repeat] [-timegranularity float>0]\n"; print " [-startregexp regexp] [-stopregexp regexp]\n"; print " [standard X11 options...]\n"; print " inputfile [... inpufileN]\n"; print "\n"; print "Options :\n"; print " -b <[addr]:port> Ivy bus (\$IVYBUS by default)\n"; print " -light If set, does not display messages\n"; print " -autostart If set, messages are replayed as soon as the\n"; print " input file is loaded\n"; print " -repeat If set, repeat infinitely the replay sequence\n"; print " -padding Additionnal space in px above and below text lines\n"; print " (2 by default)\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 ; } my ($minW, $minH) = (defined $opt_light) ? (850, 50) : (850, 200); $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(); $padding = $opt_padding if $opt_padding > 0; #================================================================================= # # H M I # #================================================================================= my $mw = MainWindow->new(); $mw->title($title); $mw->geometry($minW."x".$minH); $mw->minsize($minW, $minH); #---------------------------------------------------------------------------------- # 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 @files = @ARGV; my $lines = 0; my $steps = 0; if (@files == 0) { $mw->Tk::Error("Error : an input file is expected"); } else { &showProgressbar(); for my $file (@files) { if (not open(IN, $file)) { $mw->Tk::Error("Can't open file '$file' ($!)"); next; } else { my ($l, $timefound) = &stepsnumber; $lines += $l; if ($timefound) { &loadfileForReplay(); } else { $mw->Tk::Error("No time information in file '$file'. ". "Can't be replayed."); next; } } close(IN); } $steps = int($lines/10); $progressbar->configure(-to => 10); &displayMessages() unless $opt_light; &hideProgressbar(); &replayStart() if $opt_autostart; } 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 /^\#/ or /^delay=/); my ($sender, $message) = split(/\s+/, $_, 2); if ($message =~ /^(\d[\d\.]+\d)\s+.*/) { $timefound = 1; } $lc++; } $step = $lc; seek(IN, 0, 0); return ($step, $timefound); } # end stepsnumber sub build { %replay_msg = (); $replay_time = undef; my $ctrl_fm = $mw->Frame()->pack(-side => 'bottom', -pady => 5); unless ($opt_light) { # build replay window $replay_text = $mw->Scrolled('Text', -scrollbars => 'e', -spacing1 => $padding, -spacing2 => 0, -spacing3 => $padding, )->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; } # end build sub loadfileForReplay { # display messsages to replay my ($sender, $time, $message); my $delay = 0; while() { chomp; next if /^\#/ or /^applications=/ or /^messages_number=/ or /^\s*$/ or /^(marker\d+)$/; if (/^delay=(\d.*)/) { if ($1 < 0) { carp "delay (=$1) must be >=0\n"; } else { $delay += $1; } #print "delay=$delay\n"; next; } ($sender, $time, $message) = split(/\s+/, $_, 3); $time += $delay; 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; } $message =~ s/^\"//; $message =~ s/\"$//; push(@{$replay_msg{$time}}, $message); } $mw->raise; $replay_time = $replay_min_time; # ivy bindings $ivy->bindRegexp($opt_startregexp, [\&replayStart]) if $opt_startregexp; $ivy->bindRegexp($opt_stopregexp, [\&replayStop]) if $opt_stopregexp; } # end loadfileForReplay sub displayMessages { my $line = 0; for my $time (sort {$a <=> $b} keys(%replay_msg)) { #print "time=$time\n"; for my $message (@{$replay_msg{$time}}) { $replay_text->insert('end', &replayTime($time)." ".$message."\n", $time); $line++; if ($steps != 0) { &setProgressbar($line/$steps) if $line % $steps == 0; } # 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]); # when user double-click on a message, he sends it. $replay_text->tagBind($time, '', sub { $ivy->sendMsgs($message); }); } } $replay_text->configure(-state => 'disabled') if $replay_text; } 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) = @_; $progressbar->value($line); $progressbar->toplevel->raise($mw); $progressbar->update; } # 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; $replay_data_t0 = $replay_time; $replay_current_t0 = gettimeofday(); } else { $t->tagConfigure($replay_last_time, -foreground => $replay_fg_orig, -background => $replay_bg_orig) if $t and 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 $t and defined $replay_last_time; $t->tagConfigure($replay_time, -foreground => $replay_fg, -background => $replay_bg) if $t; } $replay_last_time = $replay_time; my ($i) = $t->tagRanges($replay_time) if $t; $t->see($i) if defined $i and $t; } $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 $replay_text and defined $replay_timer; ®ulationReset(); } # end replayStop sub replayClose { $ivy->bindRegexp($opt_startregexp); $ivy->bindRegexp($opt_stopregexp); &replayStop(); if ($replay_text) { $replay_text->after(400, sub {$mw->destroy}); } else { $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<-light>] [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<-autostart> If set, messages are replayed as soon as the input file is loaded. =item B<-light> If set, messages are not displayed. =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