http://qs321.pair.com?node_id=580915

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I'm having a performance issue updating my Tk app. I've simplified the code examples for my scenario, but basically I have a windows Tk app the launches an external program and this program continually writes all of it's output to a text file until it completes. (To emulate this you can run the first block of code, write.pl).

After the Tk app has launched this program it sits idle listening for new ouput and updates the GUI accordingly (To emulate this you can run the second block of code, capture.pl and push the "Listen" button). I have two issues really, the first is that using the nowait flag in File::Tail causes the CPU to be hammered, but I need the method to be non-blobking so the user can still interact with the GUI. My second issue is that after the method is called, it takes several (maybe 5) seconds for the method to start tailing the file. I hope I've simplified this enough for it to make sense so you can see my issue. Maybe using File::Tail isn't the best way to go...any help would be greatly appreiciated.

# write.pl

use strict; use warnings; use IO::Handle; open(WRITE, ">>C:\\File\\test.dat"); WRITE->autoflush(1); my $num = 1; while(1) { print "$num\n"; print WRITE "Some new text $num\n"; # using sleep to try and emulate the speed of the actual program sleep 1; $num ++; } close WRITE;

# capture

use strict; use warnings; use Tk; use File::Tail; my $mw; my $name = 'C:\File\test.dat'; my $message; init_ui(); MainLoop; sub init_ui { $mw = MainWindow->new( -title => 'Test' ); $mw->resizable( 0, 0 ); my $top = $mw->Frame( )->pack( -side => 'top', -expand => '1', -fill => 'both' ); my $button1 = $top->Button( -text => "Listen", -width => '10', -command => sub { file_listen(); } )->pack( -side => 'left'); my $button2 = $top->Button( -text => "Button2", -width => '10' )->pack( -side => 'left'); my $bottom = $mw->Frame( )->pack( -side => 'top', -expand => '1', -fill => 'both' ); my $status = $bottom->Label( -textvariable => \$message, )->pack( -side => 'left' ); } sub file_listen { $message = 'Listening ... '; $mw->update; my $file=File::Tail->new(name=>$name, nowait=>1); while (defined(my $line=$file->read)) { if ($line =~ /\d+/) { chomp($line); $message = $line; } $mw->update; } }

Replies are listed 'Best First'.
Re: Tk GUI and Listen?
by zentara (Archbishop) on Oct 27, 2006 at 14:07 UTC
    I don't use windows, but this works very fast, and uses low cpu. It requires that you have a version of tail that works on windows, you can groups.google.com for "tail for windows".

    Just run this as your "capture" script.

    #!/usr/bin/perl use strict; use Tk; use IO::Handle; my $H=IO::Handle->new; open($H,"tail -f -n 1 test.dat |") or die $!; my $main = MainWindow->new; my $t = $main->Text( -wrap=>'none', -height => 2, )->pack(-expand=>1); $main->fileevent(\*$H,'readable',[\&fill,$t]); MainLoop; sub fill { my ($w) = @_; my $text; my $text =<$H>; $w->delete('0.0','end'); $w->insert('end',$text); $w->yview('end'); }

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum
      That might work...but the fill method never seems to get called?
        Oh yeah, you have a windows version that dosn't like tk's fileevent method. Some windows versions can use it, others can't. In that case, instead of a fileevent, you can setup a timer to do the read. You would need the timer to be faster than the log-line-writing speed, so you don't miss a line.

        But you are probably better off using the win32 modules that jdtoronto showed you. See Perl/Tk App and Interprocess Communication for a discussion. BrowserUk shows some nice threaded methods that work on win32.

        Also use the Search Box and search for "win32 tail" for some other ideas.


        I'm not really a human, but I play one on earth. Cogito ergo sum a bum
Re: Tk GUI and Listen?
by jdtoronto (Prior) on Oct 27, 2006 at 13:39 UTC
    There is an elegant solution, Win32::ChangeNotify

    use Win32; use Win32::ChangeNotify; my $appdir = Win32::GetFolderPath(Win32::CSIDL_APPDATA); my $notify_dir = $appdir . '/Provider/Application/'; my $notify_file = $notify_dir . 'yourfile.txt'; my $notifyObj; if ( $notifyObj = Win32::ChangeNotify->new( $notify_dir, 0, "LAST_WRIT +E" ) ) { print "We have a notifyObj object\n"; $mw->repeat( 2000, \&checkNotify ); } else { print "Application not installed on this machine\n"; } sub checkNotify { my $rv = $notifyObj->wait(0); print "Found a change\n" if $rv == 1; $notifyObj->reset; if ($rv) { # DO what you need to do } }
    jdtoronto

    updated added code sample

      I've looked at this module before, and I seem to run into a similar peformance issue.
        A couple of simple things if you had performance difficulties. After you have the $notifyObj you will notice that I don't actually use the Notify portion of Win32::ChangeNotify. You need to use the Tk timer event to check the file, you will see from the line:
        $mw->repeat( 2000, \&checkNotify );
        That I use the Tk internal timer to call checkNotify, then in that sub I do:
        my $rv = $notifyObj->wait(0);
        So I call the notify object with a timeout of 0 - i.e. I ask it to return immediately with the status. Just to check, I have Benchmark already running in the application the code came from. Here are the timings for three runs through the changeNotofy:
        A3G took: 8.82149e-005 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 C +PU) A3G took: 0.000113964 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CP +U) A3G took: 8.60691e-005 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 C +PU)
        At a maximum of 114us I don't see a performance issue here.

        jdtoronto

Re: Tk GUI and Listen?
by BrowserUk (Patriarch) on Oct 28, 2006 at 00:41 UTC

    Here's a simple threaded tail and a simple Tk app that uses it.

    The threaded tail doesn't chase inodes or anything fancy like that. It only displays new lines not a few existing ones (though that could be easily changed).

    It uses negligable cpu with the default setting of 100 milliseconds sleep after a failed attempt to read a new line.

    #!perl -slw use strict; package threads::Tail; use threads; use threads::shared; use Thread::Queue; our @ISA = 'Thread::Queue'; my $die:shared = 0; sub tail{ my( $Q, $filename, $delay ) = @_; open my $fh, '<', $filename or die "$filename: $!"; seek $fh, 0, 2; until( $die ) { my $line = <$fh>; Win32::Sleep( $delay ), next unless defined $line; $Q->enqueue( $line ); } } sub new { my( $class, $filename, %args ) = @_; my $Q = new Thread::Queue; threads->new( \&tail, $Q, $filename, $args{ delay } || 100 )->deta +ch; return bless $Q, $class; } sub DESTROY { $die = 1; sleep 1; return; } 1; package main; #use threads::Tail; my $Q = threads::Tail->new( $ARGV[ 0 ], delay => 100 ); require Tk::Text; my $mw = MainWindow->new( -width => 400, -height => 400 ); my $text = $mw->Text( -wrap => 'none', -height => 20, )->pack( -expand => 1 ); my $repeat; $repeat = $mw->repeat( 100 => sub { while( $Q->pending ) { my $line = $Q->dequeue; return unless $line; $text->insert( 'end', $line ); $text->yview( 'end' ); } } ); $mw->MainLoop;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.