[Perl] Same entries in file, but treat them different.


 
Thread Tools Search this Thread
Top Forums Shell Programming and Scripting [Perl] Same entries in file, but treat them different.
# 1  
Old 05-11-2011
Question [Perl] Same entries in file, but treat them different.

Hi,

I could use some help with a program that examines a log file.
It should output the duration of the steps in minutes.
My problem is that there is no end of a step given, only the begin of a next step.
Actually the problem is that this line comes 3 times, but marks 3 different events:

"Starting Table table loading"

This is the log file (every table_load entry should be 1 line):

Code:
table_load,1304943291,Mon May  9 12:14:51 GMT 2011,STATUS: Sorting of Table data file started.
table_load,1304943750,Mon May  9 12:22:30 GMT 2011,STATUS: Starting Table table loading.
table_load,1304947100,Mon May  9 13:18:20 GMT 2011,STATUS: Sorting of Table1 data file started.
table_load,1304947546,Mon May  9 13:25:46 GMT 2011,STATUS: Starting Table table loading.
table_load,1304950974,Mon May  9 14:22:54 GMT 2011,STATUS: Sorting of Table2 data file started.
table_load,1304951297,Mon May  9 14:28:17 GMT 2011,STATUS: Starting Table table loading.
table_load,1304954040,Mon May  9 15:14:00 GMT 2011,STATUS: Creating Table indexes.
table_load,1304956993,Mon May  9 16:03:13 GMT 2011,STATUS: Analyzing table Table.
table_load,1304957476,Mon May  9 16:11:16 GMT 2011,STATUS: End of table_load.

This is the program:

Code:
#!/bin/perl -w
use strict;

my @log_lines;
my $log_line;

my $SortTable_Start = 0;
my $SortTable_End = 0;
my $SortTable_Duration = 0;
my $LoadTable_Start = 0;
my $LoadTable_End = 0;
my $LoadTable_Duration = 0;
my $SortTable1_Start = 0;
my $SortTable1_End = 0;
my $SortTable1_Duration = 0;
my $LoadTable1_Start = 0;
my $LoadTable1_End = 0;
my $LoadTable1_Duration = 0;
my $SortTable2_Start = 0;
my $SortTable2_End = 0;
my $SortTable2_Duration = 0;
my $LoadTable2_Start = 0;
my $LoadTable2_End = 0;
my $LoadTable2_Duration = 0;
my $IndexTable_Start = 0;
my $IndexTable_End = 0;
my $IndexTable_Duration = 0;
my $AnalyzeTable_Start = 0;
my $AnalyzeTable_End = 0;
my $AnalyzeTable_Duration = 0;

open (LOGOUT, "</tmp/log.out");
@log_lines = <LOGOUT>;

foreach $log_line (@log_lines) {

 if ( $log_line =~ m/Sorting of Table data file started/ ) {
      $SortTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
    }
 if ( $log_line =~ m/Starting Table table loading/ ) {
      $LoadTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable_End = $LoadTable_Start;
    }
 if ( $log_line =~ m/Sorting of Table1 data file started/ ) {
      $SortTable1_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable_End = $SortTable1_Start;
    }
 if ( $log_line =~ m/Starting Table table loading/ ) {
      $LoadTable1_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable1_End = $LoadTable1_Start;
    }
 if ( $log_line =~ m/Sorting of Table2 data file started/ ) {
      $SortTable2_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable1_End = $SortTable2_Start;
    }
 if ( $log_line =~ m/Starting Table table loading/ ) {
      $LoadTable2_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable2_End = $LoadTable2_Start;
    }
 if ( $log_line =~ m/Creating Table indexes/ ) {
      $IndexTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable2_End = $IndexTable_Start;
    }
 if ( $log_line =~ m/Analyzing table Table/ ) {
      $AnalyzeTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $IndexTable_End = $AnalyzeTable_Start;
    }
 if ( $log_line =~ m/End of table_load/ ) {
      $AnalyzeTable_End = $1 if ( $log_line =~ m/(\d+)/ );
    }
}
close (LOGOUT);

$SortTable_Duration = $SortTable_End - $SortTable_Start;
$SortTable_Duration = sprintf ("%.0f", ($SortTable_Duration / 60));
$LoadTable_Duration = $LoadTable_End - $LoadTable_Start;
$LoadTable_Duration = sprintf ("%.0f", ($LoadTable_Duration / 60));
$SortTable1_Duration = $SortTable1_End - $SortTable1_Start;
$SortTable1_Duration = sprintf ("%.0f", ($SortTable1_Duration / 60));
$LoadTable1_Duration = $LoadTable1_End - $LoadTable1_Start;
$LoadTable1_Duration = sprintf ("%.0f", ($LoadTable1_Duration / 60));
$SortTable2_Duration = $SortTable2_End - $SortTable2_Start;
$SortTable2_Duration = sprintf ("%.0f", ($SortTable2_Duration / 60));
$LoadTable2_Duration = $LoadTable2_End - $LoadTable2_Start;
$LoadTable2_Duration = sprintf ("%.0f", ($LoadTable2_Duration / 60));
$IndexTable_Duration = $IndexTable_End - $IndexTable_Start;
$IndexTable_Duration = sprintf ("%.0f", ($IndexTable_Duration / 60));
$AnalyzeTable_Duration = $AnalyzeTable_End - $AnalyzeTable_Start;
$AnalyzeTable_Duration = sprintf ("%.0f", ($AnalyzeTable_Duration / 60));

printf "SortTable_Duration : $SortTable_Duration minutes\n";
printf "LoadTable_Duration : $LoadTable_Duration minutes\n";
printf "SortTable1_Duration : $SortTable1_Duration minutes\n";
printf "LoadTable1_Duration : $LoadTable1_Duration minutes\n";
printf "SortTable2_Duration : $SortTable2_Duration minutes\n";
printf "LoadTable2_Duration : $LoadTable2_Duration minutes\n";
printf "IndexTable_Duration : $IndexTable_Duration minutes\n";
printf "AnalyzeTable_Duration : $AnalyzeTable_Duration minutes\n";

# 2  
Old 05-11-2011
What exactly is your problem?
If you just need to measure the times between the lines, this should work:
Code:
$ perl -F, -ane 'if($lastTime==0){ #first line; initialize 
  $lastTime=$F[1];
  $lastWhat=$F[$#F]; #last field
  next;
} 
$diff=$F[1]-$lastTime; #difference in seconds
($lastWhat=$F[$#F])=~s/STATUS: //;   #get rid of 'STATUS: ' 
$lastWhat=~s/\.\n//;   #strip trailing period and newline
$lastTime=$F[1];  #remember the time 
print "Event \"$lastWhat\" took $diff seconds\n";
' log 
Event "Starting Table table loading" took 459 seconds
Event "Sorting of Table1 data file started" took 3350 seconds
Event "Starting Table table loading" took 446 seconds
Event "Sorting of Table2 data file started" took 3428 seconds
Event "Starting Table table loading" took 323 seconds
Event "Creating Table indexes" took 2743 seconds
Event "Analyzing table Table" took 2953 seconds
Event "End of table_load" took 483 seconds

But I might have misunderstood what you're trying to accomplish....
# 3  
Old 05-12-2011
Thanks.
I always envy persons that can produce a one-liner for something where I need a full page.

Quote:
Originally Posted by mirni
What exactly is your problem?
My problem is that the line with "Starting Table table loading" appears 3 times and that always the last entry is taken for calculation.
So I got 3 times the same start time for 3 different events.

And the problem with this example is that I took out some lines for simplicity.
But you got my intentions right.

What if the log.out file is something like this:

Quote:
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Sorting of Table data file started.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943750,Mon May 9 12:22:30 GMT 2011,STATUS: Starting Table table loading.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304947100,Mon May 9 13:18:20 GMT 2011,STATUS: Sorting of Table1 data file started.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304947546,Mon May 9 13:25:46 GMT 2011,STATUS: Starting Table table loading.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304950974,Mon May 9 14:22:54 GMT 2011,STATUS: Sorting of Table2 data file started.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304951297,Mon May 9 14:28:17 GMT 2011,STATUS: Starting Table table loading.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304954040,Mon May 9 15:14:00 GMT 2011,STATUS: Creating Table indexes.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304956993,Mon May 9 16:03:13 GMT 2011,STATUS: Analyzing table Table.
table_load,1304943291,Mon May 9 12:14:51 GMT 2011,STATUS: Blah blah blah.
table_load,1304957476,Mon May 9 16:11:16 GMT 2011,STATUS: End of table_load.
Probably I need to write the lines I need to an array and use your code.
# 4  
Old 05-12-2011
You can use a flag to mark the first.
Code:
#!/bin/perl -w

use strict;

my @log_lines;
my $log_line;

my $flag = 0;
my $SortTable_Start = 0;
my $SortTable_End = 0;
my $SortTable_Duration = 0;
my $LoadTable_Start = 0;
my $LoadTable_End = 0;
my $LoadTable_Duration = 0;
my $SortTable1_Start = 0;
my $SortTable1_End = 0;
my $SortTable1_Duration = 0;
my $LoadTable1_Start = 0;
my $LoadTable1_End = 0;
my $LoadTable1_Duration = 0;
my $SortTable2_Start = 0;
my $SortTable2_End = 0;
my $SortTable2_Duration = 0;
my $LoadTable2_Start = 0;
my $LoadTable2_End = 0;
my $LoadTable2_Duration = 0;
my $IndexTable_Start = 0;
my $IndexTable_End = 0;
my $IndexTable_Duration = 0;
my $AnalyzeTable_Start = 0;
my $AnalyzeTable_End = 0;
my $AnalyzeTable_Duration = 0;

open (LOGOUT, "<log.out");
@log_lines = <LOGOUT>;

foreach $log_line (@log_lines) {

 if ( $log_line =~ m/Sorting of Table data file started/ ) {
      $SortTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
    }
 if ( $log_line =~ m/Starting Table table loading/ ) {
      $LoadTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable_End = $LoadTable_Start;
    }
 if ( $log_line =~ m/Sorting of Table1 data file started/ ) {
      $SortTable1_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable_End = $SortTable1_Start;
    }
 if ( $log_line =~ m/Starting Table table loading/ ) {
      $LoadTable1_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable1_End = $LoadTable1_Start;
    }
 if ( $log_line =~ m/Sorting of Table2 data file started/ ) {
      $SortTable2_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable1_End = $SortTable2_Start;
    }
 if ( $log_line =~ m/Starting Table table loading/ && ! $flag) {
      $flag=1;
      $LoadTable2_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $SortTable2_End = $LoadTable2_Start;
    }
 if ( $log_line =~ m/Creating Table indexes/ ) {
      $IndexTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $LoadTable2_End = $IndexTable_Start;
    }
 if ( $log_line =~ m/Analyzing table Table/ ) {
      $AnalyzeTable_Start = $1 if ( $log_line =~ m/(\d+)/ );
      $IndexTable_End = $AnalyzeTable_Start;
    }
 if ( $log_line =~ m/End of table_load/ ) {
      $AnalyzeTable_End = $1 if ( $log_line =~ m/(\d+)/ );
    }
}
close (LOGOUT);

$SortTable_Duration = $SortTable_End - $SortTable_Start;
$SortTable_Duration = sprintf ("%.0f", ($SortTable_Duration / 60));
$LoadTable_Duration = $LoadTable_End - $LoadTable_Start;
$LoadTable_Duration = sprintf ("%.0f", ($LoadTable_Duration / 60));
$SortTable1_Duration = $SortTable1_End - $SortTable1_Start;
$SortTable1_Duration = sprintf ("%.0f", ($SortTable1_Duration / 60));
$LoadTable1_Duration = $LoadTable1_End - $LoadTable1_Start;
$LoadTable1_Duration = sprintf ("%.0f", ($LoadTable1_Duration / 60));
$SortTable2_Duration = $SortTable2_End - $SortTable2_Start;
$SortTable2_Duration = sprintf ("%.0f", ($SortTable2_Duration / 60));
$LoadTable2_Duration = $LoadTable2_End - $LoadTable2_Start;
$LoadTable2_Duration = sprintf ("%.0f", ($LoadTable2_Duration / 60));
$IndexTable_Duration = $IndexTable_End - $IndexTable_Start;
$IndexTable_Duration = sprintf ("%.0f", ($IndexTable_Duration / 60));
$AnalyzeTable_Duration = $AnalyzeTable_End - $AnalyzeTable_Start;
$AnalyzeTable_Duration = sprintf ("%.0f", ($AnalyzeTable_Duration / 60));

printf "SortTable_Duration : $SortTable_Duration minutes\n";
printf "LoadTable_Duration : $LoadTable_Duration minutes\n";
printf "SortTable1_Duration : $SortTable1_Duration minutes\n";
printf "LoadTable1_Duration : $LoadTable1_Duration minutes\n";
printf "SortTable2_Duration : $SortTable2_Duration minutes\n";
printf "LoadTable2_Duration : $LoadTable2_Duration minutes\n";
printf "IndexTable_Duration : $IndexTable_Duration minutes\n";
printf "AnalyzeTable_Duration : $AnalyzeTable_Duration minutes\n";

# 5  
Old 05-12-2011
Thanks.
Setting a flag indeed did the trick.
# 6  
Old 05-13-2011
It works?
Code:
 if ( $log_line =~ m/Starting Table table loading/ ) {       $LoadTable_Start = $1 if ( $log_line =~ m/(\d+)/ );       $SortTable_End = $LoadTable_Start;     }
...
if ( $log_line =~ m/Starting Table table loading/ ) {       $LoadTable1_Start = $1 if ( $log_line =~ m/(\d+)/ );       $SortTable1_End = $LoadTable1_Start;     }

These two if clauses have the same condition, but apply different actions. It's probably not what you wanted in this case.

What if you had a log with more entries -- 4 tables? You'd have to add more variables. And hundreds of entries like that? I'd recommend you try improve your coding skills -- this is a great opportunity.

Use perl as a filter, that is processing your file line-by-line, that's what it does really really well. No need to store (the whole file!) into an array.

You can make use of hashes (associative arrays) to your great advantage, and it is actually quite simple. Like this :
Code:
$ cat doItSimple.pl
#!/usr/bin/perl -w

my %H = (); #initialize hash data structure

while(<>) { #process each line in a loop
    if($_ =~ /: (.*Table.*)\./ ) { #capture the string containing 'Table' from colon to period.
      $what=$1; #and remember it
      $H{$what}=$1 if($_ =~ /(\d+)/); #store the number into an array with index $what
    }
}
END{
    foreach $k (keys %H) { #this is in random order!
       print "key $k , value " . $H{$k} . "\n";
    }
    
    print "\nNow sorted:\n";
    foreach my $k (sort {$H{$a}<=>$H{$b} }(keys(%H) )) {
       print "key $k , value " . $H{$k} . "\n";
       #do whatever 
    }
}
sh $ ./doItSimple.pl data
key Analyzing table Table. , value 1304956993
key Sorting of Table data file started. , value 1304943291
key Sorting of Table2 data file started. , value 1304950974
key Sorting of Table1 data file started. , value 1304947100
key Starting Table table loading. , value 1304951297
key Creating Table indexes. , value 1304954040

Now sorted:
key Sorting of Table data file started. , value 1304943291
key Sorting of Table1 data file started. , value 1304947100
key Sorting of Table2 data file started. , value 1304950974
key Starting Table table loading. , value 1304951297
key Creating Table indexes. , value 1304954040
key Analyzing table Table. , value 1304956993

When looping through sorted hash, you can apply your logic to subtract the numbers. E.g.:
Code:
#!/usr/bin/perl -w

use Switch;  #alternatively if{}construct
...

  foreach my $k (sort {$H{$a}<=>$H{$b} }(keys(%H) )) {
      switch ($k) {
        case /Sorting of Table\d* data file started/ {
          $last = $H{$k};
        }
        case /Starting Table\d* table loading/ {
          printf "SortTable_Duration : " . $H{$k}-$last . " sec \n";
          $last = $H{$k};
        }
        case /Sorting of Table\d* data file started/ {
          printf "LoadTable_Duration : " . $H{$k}-$last . " sec \n";
          $last = $H{$k};        
        }
        case /Creating Table indexes/ {
          printf "LoadTable_Duration : " . $H{$k}-$last . " sec \n";
          $last = $H{$k};        
        }
        case /Analyzing table Table/ {
          printf "IndexTable_Duration : " . $H{$k}-$last . " sec \n";
          $last = $H{$k};        
        }
        case /End of table_load/ {
          printf "Analyze_Duration : " . $H{$k}-$last . " sec \n";
        }
     }
  }

Now this
Code:
$last = $H{$k};

is incorrect -- it'll subtract the last remembered number, from possibly different table.
If we force table numbering in "Starting Table table loading.", then we could subtract the appropriate times of appropriate tables.
If we use counter, we can build the hash H so that key always contains a number after 'Table':
Code:
while(<>) { 
    if($_ =~ /: (.*Table)(\d*)(.*)\./ ) { #suss the number after 'Table', too
      if($2 eq "") { $what = $1 . $counter{$1 . $3}++ . $3; } #no number captured -- insert one.
#$counter is indexed by the whole captured string, so 
# we keep separate counters for 'Starting Table table loading' 
# and 'Creating Table indexes' e.g.
      else { $what = $1 . $2 . $3; } #keep it as is
      print "WHAT: $what \n"; #just a debug output
      $H{$what}=$1 if($_ =~ /(\d+)/); # the '$_ =~' part can be omitted
    }

Now we can find the corresponding start event and implement the logic:
Code:
#!/usr/bin/perl -w

my %counter = {};
while(<>) {
    if( /: (.*Table)(\d*)(.*)\./ )  {
      $what = $2;
      if($2 eq "") { $what = $1 . $counter{$1 . $3}++ . $3; }
      else            { $what = $1 . $2 . $3; }
      if( /(\d+)/ ) { 
      #        print "WHAT: $what : $1\n"; 
         $H{$what}=$1; 
      }
    }
}
END{
    foreach (sort {$H{$a}<=>$H{$b} }(keys(%H)) ) {
      if (/Starting Table(\d*) table loading/) {
        print "SortTable$1_Duration : " . ($H{$_}-$H{"Sorting of Table". $1 . " data file started"}) . " sec \n";
      }
      if (/Sorting of Table(\d*) data file started/) {
        print "LoadTable$1_Duration : " . ($H{"Starting Table". $1 . " table loading"}-$H{$_}) . " sec \n" unless ($1 == 0);
      }
      #etc. etc.
    }
}

This could be further improved by defining variables for the strings 'Starting Table', ... and referencing the hash elements through these variables, so that the search strings can be changed easily.
This is just to encourage you to try to write code that is more general and sufficiently robust; and easy to maintain.

Cheers,
mirni

Last edited by mirni; 05-13-2011 at 07:57 AM..
# 7  
Old 05-13-2011
mirni,

Thanks for this lesson in coding.
I really appreciate it.

You are right it could be much better, but you know how it is, you start with a begin and end time for one set of log entries and then using copy-paste you add and add in order to end up with an ugly looking piece of code.
Plus the time pressure of someone wanting this data.
Then it is too late to change it.

The data I got from this ugly script however was very valuable for some investigation, so it served its purpose.

But I will listen to your advise and will use your examples as training.
Login or Register to Ask a Question

Previous Thread | Next Thread

8 More Discussions You Might Find Interesting

1. Shell Programming and Scripting

Treat Command Output as a File

Hi. Before I've post this question, I have spent hours looking for the solutions but to no avail. Because I think it is possible but I just don't know what is the right keyword to search for. Ok, basically what I want to achieve is really simple. It's just that I don't want to write... (20 Replies)
Discussion started by: aimy
20 Replies

2. Shell Programming and Scripting

awk script modification - treat certain files differently

awk 'BEGIN{OFS=","} FNR == 1 {if (NR > 1) {print fn,fnr,nl} fn=FILENAME; fnr = 1; nl = 0} {fnr = FNR} /UNUSUAL/ && /\.gz/ ~ /FILENAME/ {nl++} <'{system ("gunzip -cd FILENAME")}' END ... (2 Replies)
Discussion started by: SkySmart
2 Replies

3. Shell Programming and Scripting

Perl's buffered I/O is causing me to miss latest log file entries in log colorizer. How to fix?

I've been finding myself using a log file colorizer written in perl to reformat and colorize the output from many different programs. Mainly, however, I use it to make the output from "tail -f" commands more readable. The base perl script I use is based on "colorlogs.pl" available from the... (1 Reply)
Discussion started by: rcsteiner
1 Replies

4. UNIX for Dummies Questions & Answers

doubt to treat plaintext script

Hi everyone! first of all thank you all for the forum. My question is, is there a bash or java program, which addresses the existing text in the html that is visible in the web page for editing by another string, eg name1: flakjsdlñfjas Name of father: fdfjaksdfjskdfsd Well it... (1 Reply)
Discussion started by: xavilito
1 Replies

5. UNIX for Dummies Questions & Answers

make script treat * as a regular character

I need a script that reads the out put of a command (softwareupdate --list) and will tally up the number of asterisks in the output and tell me how many there were. How do I go about getting my script to treat asterisks as a regular character and not a wildcard or some other operator? (8 Replies)
Discussion started by: glev2005
8 Replies

6. Shell Programming and Scripting

while read loop w/ a nested if statement - doesn't treat each entry individually

Hi - Trying to take a list of ldap suffixes in a file, run an ldapsearch command on them, then run a grep command to see if it's a match, if not, then flag that and send an email alert. The list file (ldaplist) would look like - *********** o=company a o=company b *********** **... (7 Replies)
Discussion started by: littlefrog
7 Replies

7. Shell Programming and Scripting

How to get the difference between dates?Can i treat as string?

Hi All , I have an output value with two columns like this... Days User 10 A 500 B 1 C How i can compare the first column value and passing the user name as parameter? For example : while read -r days If (days<=30) ; then value=days/30 x100 ... (3 Replies)
Discussion started by: EDBGSK
3 Replies

8. Shell Programming and Scripting

awk - treat multiple delimiters as one

Is there anyway to get awk to treat multiple delimiters as one? Particularly spaces... (6 Replies)
Discussion started by: peter.herlihy
6 Replies
Login or Register to Ask a Question