The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

package NBI::Opts;
#ABSTRACT: A class for representing a the SLURM options for NBI::Slurm
use 5.012;
use Carp qw(confess);
$Data::Dumper::Sortkeys = 1;
$NBI::Opts::VERSION = $NBI::Slurm::VERSION;
my $SYSTEM_TEMPDIR = $ENV{'TMPDIR'} || $ENV{'TEMP'} || "/tmp";
require Exporter;
our @ISA = qw(Exporter);
sub _yell {
my $msg = shift @_;
my $col = shift @_ || "bold green";
say STDERR color($col), "[NBI::Opts]", color("reset"), " $msg";
}
sub new {
my $class = shift @_;
my ($queue, $memory, $threads, $opts_array, $tmpdir, $hours, $email_address, $email_when) = (undef, undef, undef, undef, undef, undef, undef);
# Descriptive instantiation with parameters -param => value
if (substr($_[0], 0, 1) eq '-') {
my %data = @_;
# Try parsing
for my $i (keys %data) {
# QUEUE
if ($i =~ /^-queue/) {
next unless (defined $data{$i});
$queue = $data{$i};
# THREADS
} elsif ($i =~ /^-threads/) {
next unless (defined $data{$i});
# Check it's an integer
if ($data{$i} =~ /^\d+$/) {
$threads = $data{$i};
} else {
confess "ERROR NBI::Seq: -threads expects an integer\n";
}
# MEMORY
} elsif ($i =~ /^-memory/) {
next unless (defined $data{$i});
$memory = _mem_parse_mb($data{$i});
# TMPDIR
} elsif ($i =~ /^-tmpdir/) {
next unless (defined $data{$i});
$tmpdir = $data{$i};
# MAIL ADDRESS
} elsif ($i =~ /^-(mail|email_address)/) {
next unless (defined $data{$i});
$email_address = $data{$i};
# WHEN MAIL
} elsif ($i =~ /^-(when|email_type)/) {
next unless (defined $data{$i});
$email_when = $data{$i};
# OPTS ARRAY
} elsif ($i =~ /^-opts/) {
next unless (defined $data{$i});
# in this case we expect an array
if (ref($data{$i}) ne "ARRAY") {
confess "ERROR NBI::Seq: -opts expects an array\n";
}
$opts_array = $data{$i};
# TIME
} elsif ($i =~ /^-time/) {
$hours = _time_to_hour($data{$i});
} else {
confess "ERROR NBI::Seq: Unknown parameter $i\n";
}
}
}
my $self = bless {}, $class;
# Set attributes
$self->queue = defined $queue ? $queue : "nbi-short";
$self->threads = defined $threads ? $threads : 1;
$self->memory = defined $memory ? $memory : 100;
$self->hours = defined $hours ? $hours : 1;
$self->tmpdir = defined $tmpdir ? $tmpdir : $SYSTEM_TEMPDIR;
$self->email_address = defined $email_address ? $email_address : undef;
$self->email_type = defined $email_when ? $email_when : "none";
# Set options
$self->opts = defined $opts_array ? $opts_array : [];
return $self;
}
sub queue : lvalue {
# Update queue
my ($self, $new_val) = @_;
$self->{queue} = $new_val if (defined $new_val);
return $self->{queue};
}
sub threads : lvalue {
# Update threads
my ($self, $new_val) = @_;
$self->{threads} = $new_val if (defined $new_val);
return $self->{threads};
}
sub memory : lvalue {
# Update memory
my ($self, $new_val) = @_;
$self->{memory} = _mem_parse_mb($new_val) if (defined $new_val);
return $self->{memory};
}
sub email_address : lvalue {
# Update memory
my ($self, $new_val) = @_;
$self->{email_address} = $new_val if (defined $new_val);
return $self->{email_address};
}
sub email_type : lvalue {
# Update memory
my ($self, $new_val) = @_;
$self->{email_type} = $new_val if (defined $new_val);
return $self->{email_type};
}
sub hours : lvalue {
# Update memory
my ($self, $new_val) = @_;
$self->{hours} = _time_to_hour($new_val) if (defined $new_val);
return $self->{hours};
}
sub tmpdir : lvalue {
# Update tmpdir
my ($self, $new_val) = @_;
$self->{tmpdir} = $new_val if (defined $new_val);
return $self->{tmpdir};
}
sub opts : lvalue {
# Update opts
my ($self, $new_val) = @_;
if (not defined $self->{opts}) {
$self->{opts} = [];
return $self->{opts};
}
# check newval is an array
confess "ERROR NBI::Opts: opts must be an array, got $new_val\n" if (ref($new_val) ne "ARRAY");
$self->{opts} = $new_val if (defined $new_val);
return $self->{opts};
}
sub add_option {
# Add an option
my ($self, $new_val) = @_;
push @{$self->{opts}}, $new_val;
return $self->{opts};
}
sub opts_count {
# Return the number of options
my $self = shift @_;
return defined $self->{opts} ? scalar @{$self->{opts}} : 0;
}
sub view {
# Return a string representation of the object
my $self = shift @_;
my $str = " --- NBI::Opts object ---\n";
$str .= " queue:\t" . $self->{queue} . "\n";
$str .= " threads:\t" . $self->{threads} . "\n";
$str .= " memory MB:\t" . $self->{memory} . "\n";
$str .= " time (h):\t" . $self->{hours} . "\n";
$str .= " tmpdir:\t" . $self->{tmpdir} . "\n";
$str .= " ---------------------------\n";
for my $o (@{$self->{opts}}) {
$str .= "#SBATCH $o\n";
}
return $str;
}
sub header {
# Return a header for the script based on the options
my $self = shift @_;
my $str = "#!/bin/bash\n";
# Queue
$str .= "#SBATCH -p " . $self->{queue} . "\n";
# Nodes: 1
$str .= "#SBATCH -N 1\n";
# Time
$str .= "#SBATCH -t " . $self->timestring() . "\n";
# Memory
$str .= "#SBATCH --mem=" . $self->{memory} . "\n";
# Threads
$str .= "#SBATCH -c " . $self->{threads} . "\n";
# Mail
if (defined $self->{email_address}) {
$str .= "#SBATCH --mail-user=" . $self->{email_address} . "\n";
$str .= "#SBATCH --mail-type=" . $self->{email_type} . "\n";
}
# Custom options
for my $o (@{$self->{opts}}) {
next if not defined $o;
$str .= "#SBATCH $o\n";
}
return $str;
}
sub timestring {
my $self = shift @_;
my $hours = $self->{hours};
my $days = 0+ int($hours / 24);
$hours = $hours % 24;
# Format hours to be 2 digits
$hours = sprintf("%02d", $hours);
return "${days}-${hours}:00:00";
}
sub _mem_parse_mb {
my $mem = shift @_;
if ($mem=~/^(\d+)$/) {
# bare number: interpret as MB
return $mem;
} elsif ($mem=~/^(\d+)\.?(MB?|GB?|TB?|KB?)$/i) {
if (substr(uc($2), 0, 1) eq "G") {
$mem = $1 * 1024;
} elsif (substr(uc($2), 0, 1) eq "T") {
$mem = $1 * 1024 * 1024;
} elsif (substr(uc($2), 0, 1) eq "M") {
$mem = $1;
} elsif (substr(uc($2), 0, 1) eq "K") {
continue;
} else {
# Consider MB
$mem = $1;
}
} else {
confess "ERROR NBI::Opts: Cannot parse memory value $mem\n";
}
return $mem;
}
sub _time_to_hour {
# Get an integer (hours) or a string in the format \d+D \d+H \d+M
my $time = shift @_;
$time = uc($time);
if ($time =~/^(\d+)$/) {
# Got an integer
return $1;
} else {
my $hours = 0;
while ($time =~/(\d+)([DHM])/g) {
my $val = $1;
my $unit = $2;
if ($unit eq "D") {
$hours += $val * 24;
} elsif ($unit eq "M") {
$val /= 60;
$hours += $val;
} elsif ($unit eq "H") {
$hours += $val;
} elsif ($unit eq "S") {
continue;
} else {
confess "ERROR NBI::Opts: Cannot parse time value $time\n";
}
}
return $hours;
}
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
NBI::Opts - A class for representing a the SLURM options for NBI::Slurm
=head1 VERSION
version 0.4.4
=head1 SYNOPSIS
SLURM Options for L<NBI::Slurm>, to be passed to a L<NBI::Job> object.
use NBI::Opts;
my $opts = NBI::Opts->new(
-queue => "short",
-threads => 4,
-memory => 8,
-opts => []
);
=head1 DESCRIPTION
The C<NBI::Opts> module provides a class for representing the SLURM options used by L<NBI::Slurm> for job submission.
It allows you to set various options such as the queue, number of threads, allocated memory, execution time, and more.
=head1 METHODS
=head2 new()
Create a new instance of CNBI::Opts.
my $opts = NBI::Opts->new(
-queue => "short",
-threads => 4,
-memory => 8,
-opts => ["--option=Value"],
);
This method creates a new C<NBI::Opts> object with the specified options. The following parameters are supported:
=over 4
=item * B<-queue> (string, optional)
The SLURM queue to submit the job to. If not provided, the default queue will be used.
=item * B<-threads> (integer, optional)
The number of threads to allocate for the job. If not provided, the default value is 1.
=item * B<-memory> (string or integer (Mb), optional)
The allocated memory for the job. It can be specified as a bare number representing megabytes (e.g., 1024), or with a unit suffix (e.g., 1GB). If not provided, the default value is 100 megabytes.
=item * B<-opts> (arrayref, optional)
An array reference containing additional SLURM options to be passed to the job script. Each option should be specified as a string. For example, ["--output=TestJob.out", "--mail-user user@nmsu.edu"]. If not provided, no additional options will be added.
B<NOTE> that some options are set by other methods (like C<output_file> and C<email_address>): do not specify them manually.
=back
=head2 queue
Accessor method for the SLURM queue.
$opts->queue = "long";
my $queue = $opts->queue;
This method allows you to get or set the SLURM queue for the job. If called with an argument, it sets the queue to the specified value. If called without an argument, it returns the current queue value.
=head2 threads
Accessor method for the number of threads.
$opts->threads = 8;
my $threads = $opts->threads;
This method allows you to get or set the number of threads allocated for the job. If called with an argument, it sets the number of threads to the specified value. If called without an argument, it returns the current number of threads.
=head2 memory
Accessor method for the allocated memory.
$opts->memory = 16;
my $memory = $opts->memory;
This method allows you to get or set the allocated memory for the job. If called with an argument, it sets the memory to the specified value. If called without an argument, it returns the current allocated memory.
=head2 email_address
Accessor method for the email address.
$opts->email_address = "user@example.com";
my $email_address = $opts->email_address;
This method allows you to get or set the email address to which job notifications will be sent. If called with an argument, it sets the email address to the specified value. If called without an argument, it returns the current email address.
=head2 email_type
Accessor method for the email type.
$opts->email_type = "end";
my $email_type = $opts->email_type;
This method allows you to get or set the type of email notifications to receive. Possible values are "none" (no email notifications), "begin" (send email at the start of the job), "end" (send email at the end of the job), or "all" (send email for all job events). If called with an argument, it sets the email type to the specified value. If called without an argument, it returns the current email type.
=head2 hours
Accessor method for the execution time in hours.
$opts->hours = 24;
my $hours = $opts->hours;
This method allows you to get or set the execution time for the job in hours. If called with an argument, it sets the execution time to the specified value. If called without an argument, it returns the current execution time.
=head2 tmpdir
Accessor method for the temporary directory.
$opts->tmpdir = "/path/to/tmpdir";
my $tmpdir = $opts->tmpdir;
This method allows you to get or set the temporary directory path where temporary files for the job will be stored. If called with an argument, it sets the temporary directory to the specified value. If called without an argument, it returns the current temporary directory.
=head2 opts
Accessor method for the additional SLURM options.
$opts->opts = ["--output=TestJob.out", "--mail-user user@nmsu.edu"];
my $opts_array = $opts->opts;
This method allows you to get or set the additional SLURM options for the job. The options should be specified as an array reference, where each element is a string representing a single option. If called with an argument, it sets the additional options to the specified array reference. If called without an argument, it returns the current additional options.
=head2 add_option
Add an additional SLURM option.
$opts->add_option("--output=TestJob.out");
This method allows you to add an additional SLURM option to the options list.
=head2 opts_count
Get the number of additional SLURM options.
my $count = $opts->opts_count;
This method returns the number of additional SLURM options specified for the job.
=head2 view
Get a string representation of the options.
my $str = $opts->view;
This method returns a string representation of the CNBI::Opts object, including all the options and their values.
=head2 header
Generate the SLURM header for the job script.
my $header = $opts->header;
This method generates the SLURM header for the job script based on the current options and returns it as a string.
=head2 timestring
Get the execution time as a formatted string.
my $timestring = $opts->timestring;
This method returns the execution time as a formatted string in the format "DD-HH:MM:SS", where "DD" is the number of days, "HH" is the number of hours (in 2 digits), "MM" is the number of minutes, and "SS" is the number of seconds.
=head1 AUTHOR
Andrea Telatin <proch@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2023 by Andrea Telatin.
This is free software, licensed under:
The MIT (X11) License
=cut