Skip to content

Commit

Permalink
CLI: Add cached and cached_from projections to verdi process list
Browse files Browse the repository at this point in the history
The `cached` projection will print a checkmark for nodes that were
cached from another node. The `cached_from` projection will show the
UUID of the cache source, if it exists.

The `cached` projection is added to the default projections for `verdi
process list`. The use of caching is becoming more prevalent, but often
users can still be surprised by certain behavior when processes are
taken from cache when they didn't expect it. By showing whether a
process was taken from cache or not by default in the output of `verdi
process list` should provide clarity since this is often the first place
that users check.
  • Loading branch information
sphuber committed Oct 21, 2023
1 parent 35fc3ae commit 3b445c4
Show file tree
Hide file tree
Showing 5 changed files with 27 additions and 10 deletions.
5 changes: 5 additions & 0 deletions aiida/cmdline/commands/cmd_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ def process_list(
tabulated = tabulate(projected, headers=headers)
echo.echo(tabulated)
echo.echo(f'\nTotal results: {len(projected)}\n')

if 'cached' in project:
echo.echo_report('\u267B Processes marked with check-mark were not run but taken from the cache.')
echo.echo_report('Add the option `-P pk cached_from` to the command to display cache source.')

print_last_process_state_change()

if not get_daemon_client().is_daemon_running:
Expand Down
4 changes: 2 additions & 2 deletions aiida/cmdline/utils/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ def print_last_process_state_change(process_type=None):
timestamp = get_process_state_change_timestamp(process_type)

if timestamp is None:
echo_report('last time an entry changed state: never')
echo_report('Last time an entry changed state: never')
else:
timedelta = timezone.delta(timestamp)
formatted = format_local_time(timestamp, format_str='at %H:%M:%S on %Y-%m-%d')
relative = str_timedelta(timedelta, negative_to_zero=True, max_num_fields=1)
echo_report(f'last time an entry changed state: {relative} ({formatted})')
echo_report(f'Last time an entry changed state: {relative} ({formatted})')


def get_node_summary(node):
Expand Down
7 changes: 4 additions & 3 deletions aiida/tools/query/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ class CalculationQueryBuilder:
# This tuple serves to mark compound projections that cannot explicitly be projected in the QueryBuilder, but will
# have to be manually projected from composing its individual projection constituents
_compound_projections = ('state',)
_default_projections = ('pk', 'ctime', 'process_label', 'state', 'process_status')
_default_projections = ('pk', 'ctime', 'process_label', 'cached', 'state', 'process_status')
_valid_projections = (
'pk', 'uuid', 'ctime', 'mtime', 'state', 'process_state', 'process_status', 'exit_status', 'exit_message',
'sealed', 'process_label', 'label', 'description', 'node_type', 'paused', 'process_type', 'job_state',
'scheduler_state', 'exception'
'scheduler_state', 'exception', 'cached', 'cached_from'
)

def __init__(self, mapper=None):
Expand Down Expand Up @@ -125,6 +125,7 @@ def get_query_set(self, relationships=None, filters=None, order_by=None, past_da
for projection in self._valid_projections
if projection not in self._compound_projections
]
unique_projections = list(set(projected_attributes))

if filters is None:
filters = {}
Expand All @@ -133,7 +134,7 @@ def get_query_set(self, relationships=None, filters=None, order_by=None, past_da
filters['ctime'] = {'>': timezone.now() - datetime.timedelta(days=past_days)}

builder = orm.QueryBuilder()
builder.append(cls=orm.ProcessNode, filters=filters, project=projected_attributes, tag='process')
builder.append(cls=orm.ProcessNode, filters=filters, project=unique_projections, tag='process')

if relationships is not None:
for tag, entity in relationships.items():
Expand Down
19 changes: 15 additions & 4 deletions aiida/tools/query/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class CalculationProjectionMapper(ProjectionMapper):
def __init__(self, projections, projection_labels=None, projection_attributes=None, projection_formatters=None):
# pylint: disable=too-many-locals
from aiida.orm import ProcessNode
from aiida.orm.nodes.caching import NodeCaching
from aiida.orm.utils.mixins import Sealable

self._valid_projections = projections
Expand All @@ -94,8 +95,16 @@ def __init__(self, projections, projection_labels=None, projection_attributes=No
exit_status_key = f'attributes.{ProcessNode.EXIT_STATUS_KEY}'
exit_message_key = f'attributes.{ProcessNode.EXIT_MESSAGE_KEY}'
exception_key = f'attributes.{ProcessNode.EXCEPTION_KEY}'

default_labels = {'pk': 'PK', 'uuid': 'UUID', 'ctime': 'Created', 'mtime': 'Modified', 'state': 'Process State'}
cached_from_key = f'extras.{NodeCaching.CACHED_FROM_KEY}'

default_labels = {
'pk': 'PK',
'uuid': 'UUID',
'ctime': 'Created',
'mtime': 'Modified',
'state': 'Process State',
'cached': '\u267B'
}

default_attributes = {
'pk': 'id',
Expand All @@ -109,15 +118,17 @@ def __init__(self, projections, projection_labels=None, projection_attributes=No
'exit_status': exit_status_key,
'exit_message': exit_message_key,
'exception': exception_key,
'cached': cached_from_key,
'cached_from': cached_from_key,
}

default_formatters = {
'ctime': lambda value: formatting.format_relative_time(value['ctime']),
'mtime': lambda value: formatting.format_relative_time(value['mtime']),
'state': lambda value: formatting.
format_state(value[process_state_key], value[process_paused_key], value[exit_status_key]),
'state': lambda v: formatting.format_state(v[process_state_key], v[process_paused_key], v[exit_status_key]),
'process_state': lambda value: formatting.format_process_state(value[process_state_key]),
'sealed': lambda value: formatting.format_sealed(value[sealed_key]),
'cached': lambda value: '\u2714' if value[cached_from_key] else '',
}

if projection_labels is not None:
Expand Down
2 changes: 1 addition & 1 deletion tests/cmdline/commands/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_list_non_raw(self, run_cli_command):
result = run_cli_command(cmd_process.process_list)

assert 'Total results:' in result.output
assert 'last time an entry changed state' in result.output
assert 'Last time an entry changed state' in result.output

def test_list(self, run_cli_command):
"""Test the list command."""
Expand Down

0 comments on commit 3b445c4

Please sign in to comment.