#!/usr/bin/perl -w # Generate graphs for ClearCase, Attache and MultiSite # license usage, v2.0 # Ed Finch (efinch@eos.hitc.com) March, 1998 use File::Basename; use Getopt::Long; use GD; use Time::Local; use strict; # Variables set by Getopt use vars '$opt_debug'; # debugging flag use vars '$opt_dir'; # write files to this directory use vars '$opt_month'; # generate a graph for the last month use vars '$opt_prefix'; # prefix to use instead of $opt_product use vars '$opt_product'; # the product whose usage is being graphed use vars '$opt_style'; # the style for plotting use vars '$opt_today'; # generate a graph for today use vars '$opt_week'; # generate a graph for the last week use vars '$opt_year'; # generate a graph for the last year my( $me, # basename(1) of this script @license_files, # the list of files containing license samples $license_file, # an iterator $sample_time, # time a sample was taken $clearcase_avail, # max ClearCase licenses, from a sample $clearcase_used, # ClearCase licenses used, from a sample $multisite_avail, # max MultiSite licenses, from a sample $multisite_used, # MultiSite licenses used, from a sample $attache_avail, # max Attache licenses, from a sample $attache_used, # Attache licenses used, from a sample $licenses_avail, # from a sample $licenses_used, $max_sample_time, $min_sample_time, %licenses_used, %licenses_avail, $max_used_this_period, # maximum licenses used in the graph's time period $max_used_ever, # maximum licenses ever in use $max_licenses_avail, $x_start_time, # start and end values for the x-axis $x_end_time, $x_scale, $y_scale, # scaling factors for point values $max_y_val, $month, $day, $year, $seconds_on_graph, # how many seconds worth of information will be graphed $arg_cnt, # used to process arguments $graph_file, # write the plot to this file $x_val, $y_val, # the coordinates of a point $prev_x_val, # the previous point, for the "lines" type $prev_y_val, $prev_y_avail, $y_avail, $image, # pointer to the image $width, $height, # width and height of the image $font, # the current font $label_font, # font for the labels $legend_font, # font for the legend $title_font, # font for the title $hash_length, # length of a hash mark $text, # for putting text on the graph $title_x, # x value for the title @start_date,@end_date, # dates for the graph title $left_margin, # margins for the graph - not text, etc. $right_margin, $top_margin, $bottom_margin, $x_origin, $y_origin, # location of the origin (point 0,0) $poly, $axis_color, $max_licenses_avail_color, $max_used_ever_color, $max_used_this_period_color, $title_color, $usage_color, $background_color, # misc. colors for the graph $black, $blue, $brown, $green, $grey, $light_grey, $medium_green, $navy_blue, $olive, $purple, $red, $violet, $white, $yellow, ); $me = basename($0); process_arguments(); initialize(); load_license_samples(); initialize_image(); calculate_scale_values(); add_title_to_graph(); add_legend_to_graph(); label_x_axis(); label_y_axis(); generate_graph(); add_highest_usage_to_graph(); # Write the image open GIF, "> $graph_file" or die "Can't open $graph_file: $!\n"; binmode GIF; print GIF $image->gif; close GIF; # All done exit 0; # # Mark the highest usage # sub add_highest_usage_to_graph() { my( $start_x, $end_x, $y_val, ); # Graph the "highest ever" usage $start_x = $x_origin + (($min_sample_time - $x_start_time) / $x_scale); $end_x = $x_origin + (($max_sample_time - $x_start_time) / $x_scale); $y_val = ($height - $bottom_margin) - ($max_used_ever / $y_scale); $image->dashedLine($start_x, $y_val, $end_x, $y_val, $max_used_ever_color); } # # Put a legend/key on the graph. Strings are right-justified # sub add_legend_to_graph() { $text = "Available "; $title_x = $width - $right_margin - (length($text) * $legend_font->width); $image->string($legend_font, $title_x, $top_margin + ($legend_font->height * 0), $text, $max_licenses_avail_color); $text = sprintf("Highest ever: %3d", $max_used_ever); $title_x = $width - $right_margin - (length($text) * $legend_font->width); $image->string($legend_font, $title_x, $top_margin + ($legend_font->height * 1), $text, $max_used_ever_color); $text = sprintf("Highest this period: %3d", $max_used_this_period); $title_x = $width - $right_margin - (length($text) * $legend_font->width); $image->string($legend_font, $title_x, $top_margin + ($legend_font->height * 2), $text, $max_used_this_period_color); $text = "Usage "; $title_x = $width - $right_margin - (length($text) * $legend_font->width); $image->string($legend_font, $title_x, $top_margin + ($legend_font->height * 3), $text, $usage_color); } # # Put a title on the graph # sub add_title_to_graph() { # Create the title if ($opt_today) { @start_date = split /\s+/, localtime; $text = "$opt_prefix disk usage for "; $text .= "$start_date[1] $start_date[2], $start_date[4]"; } else { @start_date = split /\s+/, localtime($x_start_time); @end_date = split /\s+/, localtime($x_end_time); $text = "$opt_prefix disk usage for "; $text .= "$start_date[1] $start_date[2], $start_date[4] " . "thru $end_date[1] $end_date[2], $end_date[4]"; } # Center the title. $title_x = ($width - (length($text) * $title_font->width)) / 2; $image->string($title_font, $title_x, ($top_margin - $title_font->height) / 2, $text, $title_color); } # # Calculate scaling factors. # sub calculate_scale_values() { $x_scale = ($x_end_time - $x_start_time) / ($width - $left_margin - $right_margin); # Subtract the 4 lines for the legend. This prevents it from being # overwritten. # Warning: Sloppy code ahead! We want 5 hash marks on the y-axis, # but they all should be whole numbers. Round the max. up until # it works. $max_y_val = $max_licenses_avail; debug("max_y_val $max_y_val"); $max_y_val += 5 - ($max_y_val % 5) if ($max_y_val % 5); debug("max_y_val $max_y_val"); $y_scale = ($max_y_val - 0) / ($height - $top_margin - $bottom_margin - ($legend_font->height * 4)); } # # Print debugging information # sub debug($) { my($str) = @_; return if (! (defined $opt_debug)); # remove extraneous \n characters while (chomp($str)) { ; } print STDERR " debug> $str\n"; } # # Generate the graph # sub generate_graph() { foreach $sample_time (sort keys %licenses_avail) { # The x and y-coordinates of the data point $x_val = $x_origin + (($sample_time - $x_start_time) / $x_scale); $y_val = ($height - $bottom_margin) - ($licenses_used{$sample_time} / $y_scale); # The y-coordinate of the "available" point $y_avail = ($height - $bottom_margin) - ($licenses_avail{$sample_time} / $y_scale); if ($opt_style eq "points") { # Plot the data point $image->setPixel($x_val, $y_val, $usage_color); # Plot the "available" point $image->setPixel($x_val, $y_avail, $max_licenses_avail_color); } elsif ($opt_style eq "lines") { if ($prev_x_val != -1) { # Plot the data point $image->line($prev_x_val, $prev_y_val, $x_val, $y_val, $usage_color); # Plot the "available" point $image->line($prev_x_val, $prev_y_avail, $x_val, $y_avail, $max_licenses_avail_color); } $prev_x_val = $x_val; $prev_y_val = $y_val; $prev_y_avail = $y_avail; } elsif ($opt_style eq "impulses") { # Plot the data point if ($licenses_used{$sample_time} > 0) { # Don't draw on the x-axis $image->line($x_val, $height - $bottom_margin - 1, $x_val, $y_val, $usage_color); } else { $image->line($x_val, $height - $bottom_margin, $x_val, $y_val, $usage_color); } # Plot the "available" point $image->setPixel($x_val, $y_avail, $max_licenses_avail_color); } # If this point is (one of) the highest for this period, # mark it with a diamond. # Draw from west of the point, clockwise. if ($licenses_used{$sample_time} == $max_used_this_period) { $poly = new GD::Polygon; $poly->addPt($x_val - 3, $y_val ); $poly->addPt($x_val , $y_val - 3); $poly->addPt($x_val + 3, $y_val ); $poly->addPt($x_val , $y_val + 3); $image->polygon($poly, $max_used_this_period_color); } } } # # Initialize stuff # sub initialize() { $opt_prefix = $opt_product unless ($opt_prefix); $width = 600; $height = 300; $left_margin = 40; $right_margin = 40; $top_margin = 40; $bottom_margin = 40; # Coordinates of the origin $x_origin = $left_margin; $y_origin = $height - $bottom_margin; # The length of a hash mark $hash_length = 4; # Coordinates of the last point plotted $prev_x_val = -1; $prev_y_val = -1; # Build the output filename if ($opt_today) { $graph_file = "$opt_dir/${opt_prefix}_today.gif"; } elsif ($opt_week) { $graph_file = "$opt_dir/${opt_prefix}_week.gif"; } elsif ($opt_month) { $graph_file = "$opt_dir/${opt_prefix}_month.gif"; } else { $graph_file = "$opt_dir/${opt_prefix}_year.gif"; } # calculate the number of seconds of information that will be displayed # 86400 is the number of seconds in a day $seconds_on_graph = 86400 if ($opt_today); $seconds_on_graph = 86400 * 7 if ($opt_week); $seconds_on_graph = 86400 * 30 if ($opt_month); $seconds_on_graph = 86400 * 365 if ($opt_year); # keep track of maximum license usages $max_used_this_period = 0; $max_used_ever = 0; $max_licenses_avail = 0; # calculate the beginning x value for the graph. initally, set it to # 12am today ($month,$day,$year) = split /\//, `date '+%D'`; chop($year); $x_start_time = timelocal(0,0,0,$day,$month - 1,$year); # go back in time, if necessary $x_start_time -= 86400 * 6 if ($opt_week); $x_start_time -= 86400 * 29 if ($opt_month); $x_start_time -= 86400 * 364 if ($opt_year); # the end position is always midnight tonight $x_end_time = $x_start_time + $seconds_on_graph; $max_sample_time = 0; $min_sample_time = 0; } # # Initialize the image # sub initialize_image() { # Create a new image $image = new GD::Image($width, $height); # Allocate colors. Values are from the "rgbcolors" command $black = $image->colorAllocate( 0, 0, 0); $blue = $image->colorAllocate( 0, 0, 255); $brown = $image->colorAllocate(165, 42, 42); # $green = $image->colorAllocate( 0, 255, 0); $green = $image->colorAllocate( 0, 235, 12); $grey = $image->colorAllocate(180, 180, 180); $light_grey = $image->colorAllocate(194, 194, 194); $medium_green = $image->colorAllocate( 0, 166, 33); $navy_blue = $image->colorAllocate( 0, 0, 80); $olive = $image->colorAllocate(107, 142, 35); $purple = $image->colorAllocate(160, 32, 240); $red = $image->colorAllocate(255, 0, 0); $violet = $image->colorAllocate(255, 0, 255); $white = $image->colorAllocate(255, 255, 255); $yellow = $image->colorAllocate(255, 255, 0); #==================================== # Configurable parameters start here #==================================== $axis_color = $black; $background_color = $white; $title_color = $black; $usage_color = $blue; $max_licenses_avail_color = $red; $max_used_ever_color = $purple; $max_used_this_period_color = $black; $label_font = gdSmallFont; $legend_font = gdSmallFont; $title_font = gdLargeFont; #==================================== # End of configurable parameters #==================================== # Set the background $image->filledRectangle(0, 0, $width, $height, $background_color); # Draw the left y-axis $image->line($x_origin, $top_margin, $x_origin, $height - $bottom_margin, $axis_color); # Draw the bottom x-axis $image->line($x_origin, $height - $bottom_margin, $width - $right_margin, $height - $bottom_margin, $axis_color); } # # Put hash marks and labels on the graph # sub label_x_axis() { my( @start_date, $tic_time, $tic_offset, $tic_val, $time, ); if ($opt_today) { # Possibly put a label every hour (3600 seconds) for ($tic_time = $x_start_time; $tic_time <= $x_end_time; $tic_time += 3600) { $tic_val = ""; $tic_offset = ($tic_time - $x_start_time); # Midnight? if ( $tic_offset == 0 || $tic_offset == 86400) { $tic_val = "M"; } elsif ($tic_offset % ($opt_today * 3600) == 0) { if ($tic_offset == 43200) { $tic_val = "Noon"; } elsif ($tic_offset < 43200) { $tic_val = $tic_offset/3600 . "am"; } else { $tic_val = ($tic_offset - 43200)/3600 . "pm"; } } if (length($tic_val)) { label_x_axis_point($tic_time, $tic_val, 1); } } } elsif ($opt_week) { # Put tics every 12 hours; label them at noon for ($tic_time = $x_start_time; $tic_time <= $x_end_time; $tic_time += 43200) { $tic_offset = ($tic_time - $x_start_time); if ($tic_offset % 86400 == 0) { label_x_axis_point($tic_time, "", 1); } elsif ($tic_offset % 43200 == 0) { @start_date = split /\s+/, localtime($tic_time); $tic_val = $start_date[1] . " " . $start_date[2]; label_x_axis_point($tic_time, $tic_val, 0); } } } elsif ($opt_month) { # Put tics every 12 hours; label them at noon for ($tic_time = $x_start_time; $tic_time <= $x_end_time; $tic_time += 43200) { $tic_offset = ($tic_time - $x_start_time); if ($tic_offset % 86400 == 0) { label_x_axis_point($tic_time, "", 1); } elsif ($tic_offset % 43200 == 0) { @start_date = split /\s+/, localtime($tic_time); $tic_val = $start_date[2]; label_x_axis_point($tic_time, $tic_val, 0); } } } elsif ($opt_year) { # Put tics at the first of each month; label them on the 15th # This is really inefficient, but we have to go 1 hour at a time # because daylight savings time can throw us off. for ($tic_time = $x_start_time; $tic_time <= $x_end_time; $tic_time += 3600) { $tic_offset = ($tic_time - $x_start_time); if ($tic_offset == 0) { label_x_axis_point($tic_time, "", 1); } else { $time = localtime($tic_time); $time =~ tr/:/ /; @start_date = split /\s+/, $time; if ($start_date[2] == 1 && $start_date[3] == 12) { label_x_axis_point($tic_time, "", 1); } elsif ($start_date[2] == 15 && $start_date[3] == 12) { $tic_val = substr($start_date[1],0,3); label_x_axis_point($tic_time, $tic_val, 0); } } } # Put a tic at the end of the x-axis label_x_axis_point($tic_time, "", 1); } } # # Label a point on the x-axis # sub label_x_axis_point($$$) { my($tic_time, $tic_val, $put_hash_mark) = @_; $x_val = $x_origin + (($tic_time - $x_start_time) / $x_scale); $y_val = ($height - $bottom_margin) + $hash_length; if ($put_hash_mark) { $image->line($x_val, $y_origin, $x_val, $y_origin + $hash_length, $axis_color); } if (length($tic_val)) { # Put the label on the graph. Center the text around the point $x_val = $x_origin + (($tic_time - $x_start_time) / $x_scale) - ((length($tic_val) * $label_font->width) / 2); $image->string($label_font, $x_val, $y_val, $tic_val, $axis_color); } } # # Label the y-axis # sub label_y_axis() { my($delta, $val); # There are 5 hash marks on the y-axis; calculate the difference # between them. $delta = ($max_y_val / 5); debug("delta $delta"); for ($val = 0; $val <= $max_y_val; $val += $delta) { debug("val $val"); $y_val = ($height - $bottom_margin) - ($val / $y_scale); debug("y_val $y_val"); # Put the label on the graph $text = sprintf("%3d ", $val); $title_x = $x_origin - $hash_length - (length($text) * $legend_font->width); $image->string($legend_font, $title_x, $y_val - ($legend_font->height / 2), $text, $axis_color); # Draw the hash mark $image->line($x_origin - $hash_length, $y_val, $x_origin, $y_val, $axis_color); } } # # Process the license samples # sub load_license_samples() { foreach $license_file (@license_files) { # Open the file open SAMPLE, $license_file or die "Can't open $license_file: $!\n"; # process the samples while () { # Skip comments and whitespace next if (m/^#/); next if (m/^\s*$/); ($sample_time, $clearcase_avail, $clearcase_used, $multisite_avail, $multisite_used, $attache_avail, $attache_used) = split(" "); if ($opt_product eq "ClearCase") { $licenses_avail = $clearcase_avail; $licenses_used = $clearcase_used; } elsif ($opt_product eq "Attache") { $licenses_avail = $attache_avail; $licenses_used = $attache_used; } elsif ($opt_product eq "MultiSite") { $licenses_avail = $multisite_avail; $licenses_used = $multisite_used; } # Is this sample within the graph's time period? if ($sample_time >= $x_start_time && $sample_time <= $x_end_time) { # Save the minimum and maximum sample times if ($min_sample_time == 0) { $min_sample_time = $sample_time; } elsif ($sample_time < $min_sample_time) { $min_sample_time = $sample_time; } if ($sample_time > $max_sample_time) { $max_sample_time = $sample_time; } # Is there a new maximum for this graph? if ($licenses_used > $max_used_this_period) { $max_used_this_period = $licenses_used; } $licenses_avail{$sample_time} = $licenses_avail; $licenses_used{$sample_time} = $licenses_used; } # Is there new overall maximums? if ($licenses_used > $max_used_ever) { $max_used_ever = $licenses_used; } if ($licenses_avail > $max_licenses_avail) { $max_licenses_avail = $licenses_avail; } } close SAMPLE; } if ($max_used_ever == 0) { print "No valid samples were found for $opt_product\n"; exit 1; } } # # Process command-line arguments # sub process_arguments() { # We must have some arguments if (scalar @ARGV == 0) { &usage; } # Process command-line parameters GetOptions("debug", "dir|d=s", "month|m", "prefix|p=s", "product|r=s", "style|s=s", "today|t:i", "week|w", "year|y") or die "$me: bad options\n"; # There must be at least 1 argument, a license sample file if (scalar @ARGV == 0) { print "Bad arguments\n"; &usage; } else { @license_files = @ARGV; } if (defined($opt_product)) { if ($opt_product ne "ClearCase" && $opt_product ne "Attache" && $opt_product ne "MultiSite") { print "$opt_product is not a valid product\n"; &usage; } } else { print "A product of ClearCase, Attache or MultiSite must be specified\n"; &usage; } if ($opt_dir) { if (! -d $opt_dir) { print "$opt_dir is not a directory\n"; &usage; } } else { $opt_dir = "."; } if (defined($opt_style)) { if ($opt_style ne "points" && $opt_style ne "impulses" && $opt_style ne "lines") { print "$opt_style is not a valid style\n"; &usage; } } else { print "A style of points, lines or impulses must be specified\n"; &usage; } if (defined @license_files) { foreach $license_file (@license_files) { if (! -f $license_file) { print "$license_file is not a regular file\n"; &usage; } elsif (! -r $license_file) { print "$license_file is not readable\n"; &usage; } } } else { print "A file license sample file must be specified\n"; &usage; } $opt_today = 3 if (defined $opt_today and $opt_today == 0); # Check the -t, -w, -m and -y switches $arg_cnt = 0; $arg_cnt += 1 if ($opt_today); $arg_cnt += 1 if ($opt_week); $arg_cnt += 1 if ($opt_month); $arg_cnt += 1 if ($opt_year); if ($arg_cnt == 0) { print "One of -t, -w, -m or -y must be specified\n"; exit 1; } elsif ($arg_cnt > 1) { print "Only one of -t, -w, -m or -y may be specified\n"; exit 1; } } # # Print usage # sub usage() { print <<"EOF"; usage: $me license_file... Options: -d str Write the graphs to this directory -m Generate a graph for the last month -p str Use as the prefix for graph filenames. Default: The product specified via -r -r str The product whose usage is to be graphed. Must be one of ClearCase, Attache or MultiSite -s str The style used for plotting points. Must be points, impulses or lines. -t num Generate a graph for today, and label it every hours By default, the graph is labelled every 3 hours -w Generate a graph for the last week -y Generate a graph for the last year license_file One or more files containing license samples EOF exit; }