Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 130 additions & 8 deletions polaris/provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import sys


def write(work_dir, tasks, config=None):
def write(work_dir, tasks, config=None, machine=None, baseline_dir=None):
"""
Write a file with provenance, such as the git version, conda packages,
command, and tasks, to the work directory
command, and tasks, to the work directory.

This function overwrites any existing provenance file in the work
directory rather than appending, so the provenance reflects the most
recent call to Polaris that used this work directory.

Parameters
----------
Expand All @@ -16,9 +20,15 @@ def write(work_dir, tasks, config=None):
tasks : dict
A dictionary describing all of the tasks and their steps

config : polaris.config.PolarisConfigParser
config : polaris.config.PolarisConfigParser, optional
Configuration options for this task, a combination of user configs
and the defaults for the machine and component

machine : str, optional
The machine on which Polaris is being run

baseline_dir : str, optional
The path to the baseline work directory, if any
"""
polaris_git_version = None
if os.path.exists('.git'):
Expand Down Expand Up @@ -50,11 +60,8 @@ def write(work_dir, tasks, config=None):
pass

provenance_path = f'{work_dir}/provenance'
if os.path.exists(provenance_path):
provenance_file = open(provenance_path, 'a')
provenance_file.write('\n')
else:
provenance_file = open(provenance_path, 'w')
# Always overwrite to ensure provenance reflects the latest setup/suite
provenance_file = open(provenance_path, 'w')

provenance_file.write(
'**************************************************'
Expand All @@ -69,6 +76,14 @@ def write(work_dir, tasks, config=None):
f'component git version: {component_git_version}\n\n'
)
provenance_file.write(f'command: {calling_command}\n\n')

# Add readily parsable, PR-friendly metadata discovered at setup time
_write_meta(provenance_file, 'machine', machine)
_write_scheduler_metadata(provenance_file, config)
_write_meta(provenance_file, 'compiler', _get_compiler(config))
_write_meta(provenance_file, 'work directory', work_dir)
_write_meta(provenance_file, 'build directory', _get_build_dir(config))
_write_meta(provenance_file, 'baseline work directory', baseline_dir)
provenance_file.write('tasks:\n')

for _, task in tasks.items():
Expand Down Expand Up @@ -126,3 +141,110 @@ def _get_component_git_version(config):
os.chdir(cwd)

return component_git_version


def _get_system(config):
if config is None:
return None
if config.has_option('parallel', 'system'):
return config.get('parallel', 'system')
return None


def _resolve_scheduler_fields(config, system):
"""Resolve partition/qos/constraint (slurm) or just constraint (pbs)."""
partition = None
qos = None
constraint = None

if config is None:
return partition, qos, constraint

if system == 'pbs':
# PBS: only constraint is relevant for our purposes
cons_val = (
config.get('job', 'constraint')
if config.has_option('job', 'constraint')
else None
)
if cons_val and cons_val != '<<<default>>>':
constraint = cons_val
elif config.has_option('parallel', 'constraints'):
cons_list = config.getlist('parallel', 'constraints')
if cons_list:
constraint = cons_list[0]
return partition, qos, constraint

# Default to slurm-like resolution
part_val = (
config.get('job', 'partition')
if config.has_option('job', 'partition')
else None
)
if part_val and part_val != '<<<default>>>':
partition = part_val
elif config.has_option('parallel', 'partitions'):
parts = config.getlist('parallel', 'partitions')
if parts:
partition = parts[0]

qos_val = (
config.get('job', 'qos') if config.has_option('job', 'qos') else None
)
if qos_val and qos_val != '<<<default>>>':
qos = qos_val
elif config.has_option('parallel', 'qos'):
qos_list = config.getlist('parallel', 'qos')
if qos_list:
qos = qos_list[0]

cons_val = (
config.get('job', 'constraint')
if config.has_option('job', 'constraint')
else None
)
if cons_val and cons_val != '<<<default>>>':
constraint = cons_val
elif config.has_option('parallel', 'constraints'):
cons_list = config.getlist('parallel', 'constraints')
if cons_list:
constraint = cons_list[0]

return partition, qos, constraint


def _get_compiler(config):
if config is None:
return None
if config.has_option('deploy', 'compiler'):
val = config.get('deploy', 'compiler')
return val or None
return None


def _get_build_dir(config):
if config is None:
return None
if config.has_option('paths', 'component_path'):
val = config.get('paths', 'component_path')
return val or None
return None


def _write_meta(provenance_file, label, value):
"""Write a simple 'label: value' line if value is provided."""
if value is None:
return
if isinstance(value, str) and value.strip() == '':
return
provenance_file.write(f'{label}: {value}\n\n')


def _write_scheduler_metadata(provenance_file, config):
"""Write partition/qos/constraint metadata when available."""
system = _get_system(config)
partition, qos, constraint = _resolve_scheduler_fields(config, system)
_write_meta(provenance_file, 'partition', partition)
_write_meta(provenance_file, 'qos', qos)
_write_meta(provenance_file, 'constraint', constraint)
# No return value; this function only writes metadata lines
Loading
Loading