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

Hi perlmonks, I'm relativley new to perl and I have a problem which I cannot seem to get my head around. I need to create an ASCII chart, which displays jobs that are running, or a list of jobs that were queuing at a particular time. The perl program will read a file which contains the following information:
Queue Queued Start End queuea 1161611468 1161612964 1161657611 queueb 1161612303 1161648292 1161674374 queuec 1161620067 1161620070 1161651772 queued 1161622109 1161622114 1161662607 queueb 1161626408 1161626414 1161650011 queueb 1161628081 1161628085 1161658563 queued 1161629319 1161651912 1161652722 queuea 1161629632 1161630900 1161649776 queuea 1161630141 1161646804 1161656606 queuec 1161632731 1161632986 1161670456 queuec 1161633898 1161633929 1161656900 queuea 1161635242 1161635247 1161703541 queueb 1161635268 1161635275 1161653284 queueb 1161635472 1161635479 1161657667 queueb 1161636392 1161636399 1161652133 queueb 1161636691 1161636698 1161652313 queueb 1161636780 1161636785 1161654656 queueb 1161638253 1161638258 1161652124 queued 1161638845 1161638851 1161652164 queueb 1161639646 1161639656 1161656089 queuea 1161639811 1161672455 1161672927 queued 1161639955 1161639958 1161650644 queued 1161640162 1161640165 1161651353 queued 1161640340 1161640343 1161650555 queued 1161640545 1161640547 1161652089 queued 1161641176 1161641181 1161651069 queued 1161641484 1161641488 1161652164 queued 1161643078 1161643083 1161653237
I've cut the above file down, it would be a lot longer for a whole day. as you can see the times are all in epoch time. I will need to display a chart, that has the time going down vertically on the chart and the name of the queue at the top. The problem is I need to be able to set the time unit, for example if I wanted to see how many jobs were queued each hour for a day, I would set the time variable to 60 and it would list every hour of the day down the Y axis with how many jobs were queued and how many jobs were running at that time, as well as the queue the job belonged to. The chart would look like this:
Date: 26/10/06 Time Queue Queued Running ----------------------------------------------------- 0:00 queuea 2 1 queueb 3 0 queuec 4 4 queued 0 6 ----------------------------------------------------- 1:00 queuea 0 1 queueb 0 10 queuec 5 2 queued 3 5 -----------------------------------------------------
and so on for the rest of the hours of the day. I just need to know how to build a program in perl to obtain those results. I know basic perl, for example I know how to read the file in and pass parameters with use getopt, but im not sure how to go about getting the results. I hope I explained this clearly enough. Can anybody help please?

Replies are listed 'Best First'.
Re: ASCII chart that displays jobs that are running and jobs that are queued for a day
by madbombX (Hermit) on Oct 26, 2006 at 11:55 UTC
    Since you have a few goals that you are trying to accomplish here, you are going to need modules for each one of the goals. In order to create an ASCII table, I would look into using the Perl module Perl6::Form. As far as taking the epoch times and manipulating them and organizing them, you can check out some of the following modules: Date::Manip, Time::Format, Time::Period. Using these modules will allow you to compare times and create functions to compare the jobs and put them where they need to go.

    What I would do is to write a function to determine what time the job is running and push it into a complex data structrue. Then iterate over that data structure to create the table you are looking for. You may want to check out planetscape's Re: How can I visualize my complex data structure?.

    However, if you looking for more specific help...the best way to get it is to actually start the work. Attempt some code (ie a specific part of your program). If you are having trouble, show us what you have tried and the errors it gives you and we will do our best to help you along and explain why what happened happened. But just asking for general help with a problem may not quite yield the results you are looking for.

Re: ASCII chart that displays jobs that are running and jobs that are queued for a day
by husker (Chaplain) on Oct 26, 2006 at 14:31 UTC
    OK, I'm assuming those values in each column are some time designator (seconds since epoch or something similar?)

    Since you are going to be doing a lot of time comparisons to figure out "did this job queue up but not start before this given time" or "did this job start but not finish as of this given time", seems to me your data structure needs to be something that's going to be ordered by time to simplify scanning the structure for the jobs.

    Take the simplest case: how would you do this for just one queue? I'd be tempted to create three ordered arrays: one containing all the queue times, one with all the start time, and one with all the end times. The algorithm for stepping through the arrays, looking for jobs that werre queued up but not started, or started but not finished, as of a given time is pretty simple. In fact, I'd probably collect all three arrays into a hash, so I'd have a hash of arrays. There would be three keys: q_time, s_time, e_time, with the array of those times hanging off each key.

    Extending this to 4 queues (or 7 queues or 2 .. maybe you can't predict how many queues you'll have each time?), then you just add another key to the front of your hash, with this key being the queue name. So now you have a hash of a hash of arrays.

    Iterating over that to generate the report should not be too awful hard.

    If a hash of a hash of arrays gives you a headache, start simple: do your algorithm with three separate arrays. Then put those arrays into a hash and see how it works. Then put another key in front of that hash and look again. It's not that tricky, really.

    Hope that helps.

    Edit: Actually, be very careful how you order your arrays. If you sort each array independently, you'll hose yourself good. You have to keep the queue, start, and end times for each job *together*. I realized I didn't point that out.

      Thank's, thats really helpful. That makes sense. I'm going to try all those suggestions at the weekend. Looks like I've got a busy weekend in front of me lol.
Re: ASCII chart that displays jobs that are running and jobs that are queued for a day
by Anonymous Monk on Oct 26, 2006 at 12:10 UTC
    Me and my Buddy YAWPS can help.

    code to get time with time offset if needed.
    my $date = get_date(0);
    # -------------------------------------------------------------------- +- # Get the current date and time in epoch seconds. # -------------------------------------------------------------------- +- sub get_date { my $cfg{time_offset} = shift; return time + 3600 * $cfg{time_offset}; }


    formats the date to many viewing styles
    my $formated_date($date, 3);
    # -------------------------------------------------------------------- +- # Format date output. # -------------------------------------------------------------------- +- sub format_date { my $date = shift || &get_date; my $type = shift || 1; # Get user profile. my $query = new CGI; my %user_data = authenticate(); # Get selected date format. my $sel_date_format = (exists $user_data{date_format}) ? $user_data{date_format} : $cfg{date_format}; $sel_date_format = ($type || $type ne '') ? $type : $cfg{date_ +format}; $date = ($date || $date ne '') ? $date : get_date() +; my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isds +t) = localtime($date + 3600 * $cfg{time_offset}); my ($formatted_date, $cmon, $cday, $syear); $year += 1900; $cmon = $mon + 1; $syear = sprintf("%02d", $year % 100); if ($hour < 10) { $hour = 0 . $hour; } if ($min < 10) { $min = 0 . $min; } if ($sec < 10) { $sec = 0 . $sec; } if ($cmon < 10) { $cmon = 0 . $cmon; } $cday = ($mday < 10) ? 0 . $mday : $mday; # Format: 01/15/00, 15:15:30 if (!$sel_date_format || $sel_date_format == 0) { $formatted_date = "$cmon/$cday/$syear, $hour:$min:$sec +"; } # Format: 15.01.00, 15:15:30 if ($sel_date_format == 1) { $formatted_date = "$cday.$cmon.$syear, $hour:$min:$sec +"; } # Format: 15.01.2000, 15:15:30 if ($sel_date_format == 2) { $formatted_date = "$cday.$cmon.$year, $hour:$min:$sec" +; } # Format: Jan 15th, 2000, 3:15pm if ($sel_date_format == 3) { my $ampm = 'am'; if ($hour > 11) { $ampm = 'pm'; } if ($hour > 12) { $hour = $hour - 12; } if ($hour == 0) { $hour = 12; } if ($mday > 10 && $mday < 20) { $cday = '<sup>th</sup> +'; } elsif ($mday % 10 == 1) { $cday = '<sup>st</sup>'; } elsif ($mday % 10 == 2) { $cday = '<sup>nd</sup>'; } elsif ($mday % 10 == 3) { $cday = '<sup>rd</sup>'; } else { $cday = '<sup>th</sup>'; } $formatted_date = "$months{$mon} $mday$cday, $year, $h +our:$min$ampm"; } # Format: 15. Jan 2000, 15:15 if ($sel_date_format == 4) { $formatted_date = "$wday. $months{$mon} $year, $hour:$ +min"; } # Format: 01/15/00, 3:15pm if ($sel_date_format == 5) { my $ampm = 'am'; if ($hour > 11) { $ampm = 'pm'; } if ($hour > 12) { $hour = $hour - 12; } if ($hour == 0) { $hour = 12; } $formatted_date = "$cmon/$cday/$syear, $hour:$min$ampm +"; } # Format: Sunday, 15 January, 2000 if ($sel_date_format == 6) { $formatted_date = "$week_days{$wday}, $mday $months{$m +on} $year"; } my $new_date22 = ''; my $new_mon = ''; # Format: year,month,day 20000 if ($sel_date_format == 7) { if($mday <= 9) { $new_date22 = "0$mday"; } else { $new_date22 += "$mday"; } if($mon <= 9) { $new_mon = "0$mon"; } else { $new_mon = "$mon" +; } #$cmon = $cmon - 1; $formatted_date = "$year$new_mon$new_date22"; } # Format: year,month,day 20000 if ($sel_date_format == 8) { #if(length($wday) < 2) { $wday = "0$wday"; } #$cmon = $cmon - 1; if($mon <= 9) { $new_mon = "0$mon"; } else { $new_mon += "$mon"; } $formatted_date = "$year$new_mon"; } # Format: 15/01/2000 - 03:15:30 (internal stats logfile format +). if ($sel_date_format == -1) { $formatted_date = "$cday/$cmon/$year - $hour:$min:$sec +"; } return $formatted_date; }


    my $time_dif = calc_time_diff($in_date1, $in_date2, '');
    This code will show the difference in hours or day
    # -------------------------------------------------------------------- +- # Calculate difference between two dates. # -------------------------------------------------------------------- +- sub calc_time_diff { my ($in_date1, $in_date2, $type) = @_; my $result = $in_date1 - $in_date2; # Calculate difference in hours. if (!$type) { $result = int($result / 3600); } # Calculate difference in days. else { $result = int($result / (24 * 3600)); } return $result; }


    Hope that helps! and you can Tank Yawps befor thanking me!
      The size of your format_date subroutine is too big; you should cut that down into smaller more readable blocks. You should also avoid the use of numbers as switchs to determine the output. Use CONSTANTS or Readonly variables to improve the readability.

      Horrendous. Try this:

      use Lingua::EN::Numbers::Ordinate; my @standard_formats = ( 'dd/mm/yyyy - HH:mm:ss', # -1: 15/01/2000 - 03:15:30 'MM/dd/yy, HH:mm:ss', # 0: 01/15/00, 15:15:30 'dd.MM.yy, HH:mm:ss', # 1: 15.01.00, 15:15:30 'dd.MM.yyyy, HH:mm:ss', # 2: 15.01.2000, 15:15:30 'MMM D, yyyy, h:mm tt', # 3: Jan 15th, 2000, 3:15pm 'd. MMM yyyy, hh:mm', # 4: 15. Jan 2000, 15:15 'MM/dd/yy, h:mm tt', # 5: 01/15/00, 3:15pm 'ddd, d MMMM, yyyy', # 6: Sunday, 15 January, 2000 'yyyyMMdd', # 7 'yyyyMM', # 8 ); my @months = qw( January February March April May June July August September October November December ); my @wdays = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday ); sub d2($) { sprintf '%02d', $_[0] } sub format_date { my( $date, $date_format ) = @_; # either could be undef defined $date or $date = get_date(); defined $date_format or $date_format = get_date_format(); # from user data, cfg file $date_format++; # since they start at -1 defined $standard_formats[$date_format] or $date_format = 0; # default my( $sec, $min, $hour, $mday, $mon, $year, $wday ) = localtime $da +te; my $month = $months[$mon]; my $weekday = $wdays[$wday]; $mon++; $year += 1900; # set up the substitutions: my %v; $v{'d'} = $mday; $v{'dd'} = d2 $v{'d'}; $v{'dddd'} = $weekday; $v{'ddd'} = substr $v{'dddd'}, 0, 3; $v{'D'} = ordinate($v{'d'}); $v{'M'} = $mon; $v{'MM'} = d2 $v{'M'}; $v{'MMMM'} = $month; $v{'MMM'} = substr $v{'MMMM'}, 0, 3; $v{'y'} = $year % 100; $v{'yy'} = d2 $v{'y'}; $v{'yyyy'} = $year; $v{'H'} = $hour; $v{'HH'} = d2 $v{'H'}; $v{'h'} = $hour % 12; $v{'hh'} = d2 $v{'h'}; $v{'m'} = $min; $v{'mm'} = d2 $v{'m'}; $v{'s'} = $sec; $v{'ss'} = d2 $v{'s'}; $v{'t'} = $hour>=12?'P':'A'; $v{'tt'} = $v{'t'}.'M'; my $tf = $standard_formats[$date_format]; # do the substitutions: $tf =~ s/\b([dDMyHhmst]+)\b/ $v{$1} || $1 /ge; # this seems to be sflex's preference: $tf =~ s/ ([AP]M)/\L$1/; $tf }
      We're building the house of the future together.
      sub get_date { my $cfg{time_offset} = shift;
      syntax error at - line 4, near "$cfg{time_offset" Execution of - aborted due to compilation errors.
      Ahhh... I wasnt loged in when i posted the above. hehehe..
Re: ASCII chart that displays jobs that are running and jobs that are queued for a day
by brian_d_foy (Abbot) on Oct 26, 2006 at 22:13 UTC

    This doesn't solve your configurable time slice problem, but it doesn't limit it either. Just change time_bin for that. I got you started so you just have to take it the rest of the way. Good luck :)

    The first part of your program needs to digest the data and store it in a way in which you can conveniently access it. Since you want dates, then hours, then queues, I'd use a multi-level hash with the keys in that order. The Perl Data Structures Cookbook perldsc has some good examples.

    #!/usr/bin/perl use strict; my %hash; while( <DATA> ) { chomp; my( $name, @times ) = split; # convert each time to [ date, time bin ] @times = map { [ date($_), time_bin( $_ ) ] } @times; =pod The multi-level hash looks like this: $hash{ date }{ time bin }{ queue name }{'running'} {'queued'} Look at it with: use Data::Dumper; print Dumper( \%hash ); =cut # count the bin it was queued, even if it runs in that bin $hash{ $times[0][0] }{ $times[0][1] }{ $name }{'queued'}++; # count the bin it starts running $hash{ $times[1][0] }{ $times[1][1] }{ $name }{'running'}++; # count the bin it ended, unless it's the same bin it started # might want to check that it starts and ends on the same date too $hash{ $times[2][0] }{ $times[2][1] }{ $name }{'running'}++ if $times[1][1] ne $times[2][1]; }

    Once you have the data structure, you go through it level by level and print things however you like at the last level:

    { DATE: foreach my $date ( sort keys %hash ) { print_header( $date ); my $hour_hash = $hash{ $date }; HOUR: foreach my $hour ( sort { $a <=> $b } keys %$hour_hash ) { my $queue_hash = $hour_hash->{$hour}; my $string = ''; QUEUE: foreach my $queue ( sort keys %$queue_hash ) { $string .= sprintf " %6s %4d %4d\n", $queue, $queue_hash->{$queue}{'queued'}, $queue_hash->{$queue}{'running'}; } # add the hour last to avoid a special case substr( $string, 0, 5 ) = $hour; print $string, "-" x 54, "\n"; } } }

    Finally, the utility subroutines which you can adjust to taste:

    sub time_bin { # this just assumes an hour, such as 19. # adjust for whatever time bin you'd like to use sprintf "%02d:00", (localtime( $_[0] ) )[2]; } sub date { my @times = localtime( $_[0] ); $times[5] += 1900; $times[4] += 1; # join on / and make two digits for easy sorting return join "/", map { sprintf "%02d", $_ } @times[3,4,5]; } sub print_header { print <<"HERE"; Date: $_[0] Time Queue Queued Running ------------------------------------------------------ HERE }

    For your input data, my script gives this output. If you want to show all of the hours and all of the queues even if they have nothing running, you just have to adjust the looping through the values. Instead of looping through only what's defined (foreach my $queue ( sort keys %$queue_hash )), loop through all of the possible values (foreach my $queue ( @queue_names )). With a bit more trickery, pagination is easy to add, too.

    Date: 23/10/2006 Time Queue Queued Running ------------------------------------------------------ 08:00 queuea 1 0 ------------------------------------------------------ 09:00 queuea 0 1 queueb 1 0 ------------------------------------------------------ 11:00 queuec 1 1 queued 1 1 ------------------------------------------------------ 13:00 queuea 1 0 queueb 2 2 queued 1 0 ------------------------------------------------------ 14:00 queuea 1 1 queuec 1 1 ------------------------------------------------------ 15:00 queuea 1 1 queueb 5 5 queuec 1 1 ------------------------------------------------------ 16:00 queuea 1 0 queueb 2 2 queued 5 5 ------------------------------------------------------ 17:00 queued 3 3 ------------------------------------------------------ 18:00 queuea 0 1 ------------------------------------------------------ 19:00 queuea 0 1 queueb 0 2 queued 0 4 ------------------------------------------------------ 20:00 queueb 0 5 queuec 0 1 queued 0 5 ------------------------------------------------------ 21:00 queuea 0 2 queueb 0 3 queuec 0 1 ------------------------------------------------------ 23:00 queued 0 1 ------------------------------------------------------ Date: 24/10/2006 Time Queue Queued Running ----------------------------------------------------- 01:00 queuea 0 1 queuec 0 1 ------------------------------------------------------ 02:00 queueb 0 1 ------------------------------------------------------ 10:00 queuea 0 1 ------------------------------------------------------
      I reworked your code to allow for any time periods (15mins, 30mins, 60mins, ...)

      Chris

      #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my $interval = shift || 60; my %hash; while( <DATA> ) { my( $name, @times ) = split; # convert the 3 times into their time slot. # (eg) if the interval is 60 minutes (1 hour) # a queued time of 9:26 would be converted to 9:00. $_ -= $_ % ($interval * 60) for @times; for (my $slot = $times[0]; $slot < $times[1]; $slot += $interval * + 60) { $hash{ date($slot) }{ time_bin($slot) }{ $name }{queued}++; } for (my $slot = $times[1]; $slot < $times[2]; $slot += $interval * + 60) { $hash{ date($slot) }{ time_bin($slot) }{ $name }{running}++; } } { DATE: foreach my $date ( sort keys %hash ) { print_header( $date ); my $hour_hash = $hash{ $date }; HOUR: foreach my $hour ( sort keys %$hour_hash ) { my $queue_hash = $hour_hash->{$hour}; my $string = ''; QUEUE: foreach my $queue ( sort keys %$queue_hash ) { $string .= sprintf " %6s %4d %4d\n", $queue, $queue_hash->{$queue}{'queued'}||0, $queue_hash->{$queue}{'running'}||0; } # add the hour last to avoid a special case substr( $string, 0, 5 ) = $hour; print $string, "-" x 54, "\n"; } } } sub time_bin { sprintf "%02d:%02d", (localtime( $_[0] ) )[2,1]; } sub date { my @times = localtime( $_[0] ); $times[5] += 1900; $times[4] += 1; # join on / and make two digits for easy sorting return join "/", map { sprintf "%02d", $_ } @times[3,4,5]; } sub print_header { print <<"HERE"; Date: $_[0] Time Queue Queued Running ------------------------------------------------------ HERE }
      Thank you so much, that is just what I need.
        Hi monks, I've been trying to understand the program in the above post, but there is a part that I just cannot understand. I can see that the program works, I just don't know how. I cannot figure out how it knows when the queue is running a job, or queueing a job. I thought I would need to put some 'if statements' in there somewhere, otherwise how does it know, without conditionals. Please excuse my ignorance, i'm new to programming and perl and trying to understand, how this works. Could somebody try and explain to me how the hashes are being built with the correct information because to me, it looks like the times are not being compared so how does it know. Thanks.