3

I have a suite of small Java app that all compiles/packages to <name-of-the-app>.jar and run on my server. Occasionally one of them will throw an exception, choke and die. I am trying to write a quick-n-dirty Perl script that will periodically poll to see if all of these executable JARs are still running, and if any of them are not, send me an email and notify me which one is dead.

To determine this manually, I have to run a ps -aef | grep <name-of-app> for each app I want to check. For example, to see if myapp.jar is running as a process, I run ps -aef | grep myapp, and look for a grep result that describes the JVM process representing myapp.jar. This manual checking is now getting tedious and is a prime candidate for automation!

I am trying to implement the code that checks to see whether a process is running or not. I'd like to make this a sub that accepts the name of the executable JAR and returns true or false:

sub isAppStillRunning($appName) {
    # Somehow run "ps -aef | grep $appName"

    # Somehow count the number of processes returned by the grep

    # Since grep always returns itself, determine if (count - 1) == 1.
    # If so, return true, otherwise, false.
}

I need to be able to pass the sub the name of an app, run my normal command, and count the number of results returned by grep. Since running a grep always results in at least one result (the grep command itself), I need logic that says if the (# of results - 1) is equal to 1, then we know the app is running.

I'm brand new to Perl and am having a tough time figuring out how to implement this logic. Here's my best attempt so far:

sub isAppStillRunning($appName) {
    # Somehow run "ps -aef | grep $appName"
    @grepResults = `ps -aef | grep $appName`;

    # Somehow count the number of processes returned by the grep
    $grepResultCount = length(@grepResults);

    # Since grep always returns itself, determine if (count - 1) == 1.
    # If so, return true, otherwise, false.
    if(($grepResultCount - 1) == 1)
        true
    else
        false
}

Then to call the method, from inside the same Perl script, I think I would just run:

&isAppStillRunning("myapp");

Any help with defining the sub and then calling it with the right app name is greatly appreciated. Thanks in advance!

jtahlborn
  • 52,909
  • 5
  • 76
  • 118
IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756
  • Perl can do this, but this is reinventing the wheel. There are [many sysadmin solutions](http://serverfault.com/q/192302/17985) for restarting processes. – pilcrow Sep 26 '12 at 01:11
  • Re: grep matching its argument in the process table, the usual way to sidestep this is to `grep some[a]pp` rather than `grep someapp`. Also, `grep -c some[a]pp` will portably give you the count of matches. – pilcrow Sep 26 '12 at 01:15
  • To add to what @pilcrow says, another thing I've done in the past is `grep process_name | grep -v grep` to avoid getting the grep process in its output. – laaph Sep 27 '12 at 02:54

3 Answers3

4

It would be about a billion times easier to use the Proc::ProcessTable module from CPAN. Here's an example of what it might look like:

use strict;
use warnings;
use Proc::ProcessTable;

...
sub isAppStillRunning { 
    my $appname = shift;
    my $pt = Proc::ProcessTable->new;
    my @procs = grep { $_->fname =~ /$appname/ } @{ $pt->table };

    if ( @procs ) { 
        # we've got at least one proc matching $appname. Hooray!
    } else { 
        # everybody panic!
    }
}

isAppStillRUnning( "myapp" );

Some notes to keep in mind:

  • Turn on strict and warnings. They are your friends.
  • You don't specify subroutine arguments with prototypes. (Prototypes in Perl do something completely different, which you don't want.) Use shift to get arguments off the @_ array.
  • Don't use & to call subroutines; just use its name.
  • An array evaluated in scalar context (including if its inside an if) gives you its size. length doesn't work on arrays.
friedo
  • 65,762
  • 16
  • 114
  • 184
3

Your sub is almost there, but the final if-else construct has to be corrected, and in some cases Perl idiom can make your life easier.

Perl Has Prototypes, But They Suck

sub isAppStillRunning($appName) {

will not work. Instead use

sub isAppStillRunning {
  my ($appName) = @_;

The @_ array holds the arguments to the function. Perl has some simple prototypes (the sub name(&$@) {...} syntax), but they are broken, and an advanced topic, so don't use them.

Perl Has Built-In Grep

`ps -aef | grep $appName`;

This returns one (1) string, possibly containing multiple lines. You could split the output at newlines, and grep manually, which is safer than interpolating variables:

my @lines   = split /\n/ `ps -aef`;
my @grepped = grep /$appName/, @lines;

You could also use the open function to explicitly open a pipe to ps:

my @grepped = ();
open my $ps, '-|', 'ps -aef' or die "can't invocate ps";
while (<$ps>) {
  push @grepped if /$appName/;
}

This is exactly equal, but better style. It reads all lines from the ps output and then pushes all lines with your $appName into the @grepped array.

Scalar vs. List Context

Perl has this unusual thing called "context". There is list context and scalar context. For example, subroutine calls take argument lists - so these lists (usually) have list context. Concatenating two strings is a scalar context, in contrast.

Arrays behave differently depending on their context. In list context, they evaluate to their elements, but in scalar context, they evaluate to the number of their elements. So there is no need to manually count elements (or use the length function that works on strings).

So we have:

 my @array = (1, 2, 3);
 print "Length: ", scalar(@array), "\n"; # prints "Length: 3\n"
 print "Elems: ", @array, "\n";          # prints "Elems: 123\n";
 print "LastIdx: ", $#array, "\n";       # prints "LastIdx: 2\n";

The last form, $#array, is the last index in the array. Unless you meddle with special variables, this is the same as @array - 1.

The scalar function forces scalar context.

Perl Has No Booleans

Perl has no boolean data type, and therefore no true or false keywords. Instead, all values are true, unless stated otherwise. False values are:

The empty string "", the number zero 0, the string zero "0", the special value undef, and some other oddities you won't run into.

So generally use 1 as true and 0 as false.

The if/else constructs require curly braces

So you probably meant:

if (TEST) {
  return 1;
} else {
  return 0;
}

which is the same as return TEST, where TEST is a condition.

The Ultimate reduction

Using these tricks, your sub could be written as short as

sub isAppStillRunning {
   return scalar grep /$_[0]/, (split /\n/, `ps -aef`);
}

This returns the number of lines that contain your app name.

amon
  • 57,091
  • 2
  • 89
  • 149
  • Wow! Thanks @amon (+1) - great pointers! So, I assume I would then call the sub and check if the app is running or not by calling `if(isAppStillRunning('myapp') == 1)`? If the return value is 1 we know we have a match, right? Thanks again! – IAmYourFaja Sep 26 '12 at 00:47
  • 1
    @4herpsand7derpsago No, the return value will not always be `1`. Treat the return value as a true or false value, i.e. `if (isAppStillRunning("yourapp"))` without further conditions. – amon Sep 26 '12 at 00:50
  • Ahh, I see. But if "myapp" isn't running, I can always count on it being 0, right? – IAmYourFaja Sep 26 '12 at 00:51
  • Sorry @amon, last question! If I wanted to check if "myapp" *isn't* running, how would I call `isAppStillRunning` if Perl doesn't support boolean? Could I use `if(!isAppStillRunning("myapp"))`, or would I have to change the logic inside the sub? – IAmYourFaja Sep 26 '12 at 00:56
  • Just negate the return value (as you did). The `!` operator is possible; if you like it verbose, you can also use the `not` function that has the same effect. – amon Sep 26 '12 at 01:00
  • 1
    Regarding checking for a negative condition, an elegant option in Perl is `unless (isAppStillRunning("myapp")) { ... }`. This is exactly the same as `if (!...)`. – dan1111 Sep 26 '12 at 08:02
1

You could modify your routine like this:

sub isAppRunning {
    my $appName = shift;
    @grepResults = `ps -aef | grep $appName`;
    my $items = 0;
    for $item(@grepResults){
        $items++;
    }
    return $items;
}

This will iterate over the @grepResults and allow you to inspect the $item if necessary.

Calling it like this should return the number of processes:

print(isAppRunning('myapp') . "\n"); 
jcern
  • 7,798
  • 4
  • 39
  • 47
  • Thanks @jcern (+1) - 3 quick followups: (1) can you please explain the first line `my @appName = shift`? The `shift` is throwing me off!! (2) Are the curly braces around the body of the for-loop mandatory, or just good practice? Not sure if Perl enforces the braces even if there's only 1 statement. And (3) Does Perl not support boolean true/false values? I am wondering why you have the sub returning the item count and not true/false is all. Thanks again! – IAmYourFaja Sep 26 '12 at 00:44
  • 1
    Running `grep $appName` as a subprocess will include the grep process in the count. It would be more accurate to capture `ps -aef` and grep the results in perl. – Phssthpok Sep 26 '12 at 00:45
  • @4herpsand7derpsago (2) braces are almost always mandatory, as Perl is defined in terms of blocks, not statements, and blocks are delimited by braces. (3) see my answer to your question – amon Sep 26 '12 at 00:48
  • For point 1, you can think of it this way, the arguments to the function are in the form of an array. You can pop the items from that array (or shift them) and assign them to a variable. For (2) the braces are usually necessary. And (3) seems like it was answered pretty well in amon's answer. – jcern Sep 26 '12 at 00:54