#$Id: Runtime.pm 46153 2007-10-19 00:26:07Z wsnyder $ ###################################################################### # # This program is Copyright 2002-2007 by Wilson Snyder. # # This program is free software; you can redistribute it and/or modify # it under the terms of either the GNU General Public License or the # Perl Artistic License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ###################################################################### require 5.006_001; package Make::Cache::Runtime; use DirHandle; use Digest::MD5; use File::Copy; use File::Path; use Storable; use Carp; use strict; our $VERSION = '1.043'; ####################################################################### our $Cache_Dir = undef; # Set/accessed with cache_dir function ####################################################################### sub cache_dir { $Cache_Dir ||= $ENV{OBJCACHE_RUNTIME_DIR} || "/usr/local/common/lib/runtime"; $Cache_Dir = shift if $_[0]; return $Cache_Dir; } sub format_time { my $secs = shift; my $include_hours = shift; # Hopefully, no hours! if ($include_hours || $secs>=3600) { return sprintf ("%02d:%02d:%02d", int($secs/3600), int(($secs%3600)/60), $secs % 60); } else { return sprintf ("%02d:%02d", int(($secs)/60), $secs % 60); } } sub write { my %params = (dir => cache_dir(), # Directory to put results into #key => , # Key to be stored #persist => {}, # Reference to hash to be stored @_); $params{key} or die "%Error: Expecting key=> parameter, "; $params{persist} or die "%Error: Expecting key=> parameter, "; # We store it under a digest to allow for non-filename allowed characters my $key_digest = Digest::MD5::md5_hex($params{key}); (my $key_prefix = $key_digest) =~ s/^(..).*$/$1/; my $path = "$params{dir}/${key_prefix}"; # Ignore errors, as two processes may be doing this at once # If the output dir isn't made the storable creation will catch it eval { my_mkpath ($path); }; my $filename = "$path/${key_digest}.runtime"; print "Make::Cache::Runtime::write $filename\n" if $::Debug; my $newfile = "${filename}.new$$"; Storable::nstore ($params{persist}, $newfile); chmod 0777, $newfile; # Do the copy as one atomic op to prevent a race case with another reader move ($newfile, "$filename"); } sub read { my %params = (dir => cache_dir(), # Directory to put results into #key => , # Key to be stored @_); # Returns reference to persistent structure, undef if no data known my $key_digest = Digest::MD5::md5_hex($params{key}); (my $key_prefix = $key_digest) =~ s/^(..).*$/$1/; my $filename = "$params{dir}/${key_prefix}/${key_digest}.runtime"; # We can't have storable open the file for us, as there may be a race # where the file exists with -r, then gets replaced. my $fd = IO::File->new("<$filename"); return undef if !$fd; my $persistref; eval { $persistref = Storable::fd_retrieve($fd); }; # Ignore errors return $persistref; } sub dump { my %params = (dir => cache_dir(), # Directory to put results into @_); my $path = $params{dir}; my $dir = new DirHandle $path or return; my %lines; while (defined(my $basefile = $dir->read)) { my $file = "$path/$basefile"; if ($file =~ /\.runtime$/) { my $persistref = Storable::retrieve($file); my $date = (stat($file))[9]; my ($a,$b,$c,$day,$month,$year,$d,$e,$f) = localtime($date); $date = sprintf("%04d/%02d/%02d", $year+1900,$month+1,$day); (my $showfile = $basefile) =~ s/\.runtime//; $lines{$persistref->{prog}.$persistref->{key}} = sprintf ("\t%-9s %-8s %-11s %s %s\n", $persistref->{prog}||'?', format_time($persistref->{runtime},1), $date, $showfile, $persistref->{key}); } } foreach (sort(keys %lines)) { print $lines{$_}; } } sub my_mkpath { my($paths, $verbose, $mode) = @_; # $paths -- either a path string or ref to list of paths # $verbose -- optional print "mkdir $path" for each directory created # $mode -- optional permissions, defaults to 0777 # # Like File::Path::mkpath, where this is from. # But that insists on printing messages and failing when it doesn't # work, which can happen when many processes are executing in parallel local($")="/"; $mode = 0777 unless defined($mode); $paths = [$paths] unless ref $paths; my (@created,$path); foreach $path (@$paths) { $path .= '/' if $^O eq 'os2' and $path =~ /^\w:\z/s; # feature of CRT next if -d $path; my $parent = File::Basename::dirname($path); unless (-d $parent or $path eq $parent) { push(@created,my_mkpath($parent, $verbose, $mode)); } print "mkdir $path\n" if $verbose; unless (mkdir($path,$mode)) { my $e = $!; # allow for another process to have created it meanwhile #croak "mkdir $path: $e" unless -d $path; } push(@created, $path); } @created; } ####################################################################### 1; __END__ =pod =head1 NAME Make::Cache::Runtime - Simple database of completion times =head1 SYNOPSIS $string = format_time($seconds, $true_to_including_hours); Make::Cache::Runtime::write (key=>$key, persist=>{testing_persistence=>1}, ); my $persistref = Make::Cache::Runtime::read (key=>'make::runtime testing',); =head1 DESCRIPTION Make::Cache::Runtime allows for storing and retrieving persistent state, namely expected runtime of gcc and tests. Data is stored in a global directory, presumably under NFS mount across all systems. While this does not allow atomic access to files, it does provide fast, catchable access to the database. =head1 METHODS =over 4 =item cache_dir Return the default directory name for the cache. With optional argument, set the cache directory name. Defaults to OBJCACHE_RUNTIME_DIR. =item $string = format_time($seconds, $true_to_including_hours); Return the time in seconds as a string in MM:SS format. With true second argument, return as HH:MM:SS. =item write (key=>$key, persist=>$ref); Hash the key, and write a database entry file with a copy of the data in the persist reference. With dir=> named parameter, use database in that directory. Note to prevent problems between different versions of perl, you may want to include the Perl version number in the key hash. =item my $ref = read (key=>$key); Return a object reference for the data stored under the given key, or undef if not found. With dir=> named parameter, use database in that directory. =item dump() Print a summary of the runtime database for debugging. With dir=> named parameter, use database in that directory. =back =head1 FILES /usr/local/common/lib/runtime Default for cache_dir() =head1 ENVIRONMENT =over 4 =item OBJCACHE_RUNTIME_DIR Specifies the directory containing the runtime database. Defaults to /usr/local/common/lib/runtime. =back =head1 DISTRIBUTION The latest version is available from CPAN and from L. Copyright 2000-2007 by Wilson Snyder. This package is free software; you can redistribute it and/or modify it under the terms of either the GNU Lesser General Public License or the Perl Artistic License. =head1 AUTHORS Wilson Snyder =head1 SEE ALSO L, L =cut ######################################################################