Skip to content

Commit d9f19c9

Browse files
authored
Merge pull request #53 from nikstuckenbrock/feature/more-detailed-cache-control
Add more detailed cache control
2 parents 1729f9b + 13edd50 commit d9f19c9

7 files changed

Lines changed: 242 additions & 80 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Cache command for batch-tamarin.
3+
4+
This module handles cache interaction commands.
5+
"""
6+
7+
import typer
8+
9+
from ..modules.cache_manager import CacheManager
10+
from ..utils.system_resources import get_human_readable_volume_size
11+
12+
cache_command = typer.Typer(name="cache", help="Interaction with the cache")
13+
14+
15+
@cache_command.command()
16+
def clear(
17+
errors_only: bool = typer.Option(
18+
False, "--errors-only", "-e", help="Only clear failed/error tasks"
19+
)
20+
) -> None:
21+
"""Clear the cache.
22+
23+
Args:
24+
errors_only (bool, optional): Clear only error/failed tasks. Defaults to False.
25+
"""
26+
27+
CacheCommand.clear(errors_only=errors_only)
28+
29+
30+
class CacheCommand:
31+
"""Command class for interacting with the batch-tamarin cache."""
32+
33+
@staticmethod
34+
def clear(errors_only: bool = False) -> None:
35+
"""
36+
Clears the cache using the given options.
37+
38+
Args:
39+
errors_only (bool, optional): Only clear errors. Defaults to False.
40+
"""
41+
42+
try:
43+
cache_manager = CacheManager()
44+
stats_before = cache_manager.get_stats()
45+
cache_manager.clear_cache(errors_only=errors_only)
46+
stats_after = cache_manager.get_stats()
47+
entries = stats_before["size"] - stats_after["size"]
48+
volume = get_human_readable_volume_size(
49+
stats_before["volume"] - stats_after["volume"]
50+
)
51+
52+
print(f"Cleared cache: {entries} entries, {volume}")
53+
except Exception as e:
54+
print(f"Failed to clear cache: {e}")
55+
raise typer.Exit(1)
56+
return

src/batch_tamarin/main.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,24 @@
33
import typer
44

55
from . import __author__, __version__
6+
from .commands.cache import cache_command
67
from .commands.check import CheckCommand
78
from .commands.init import InitCommand
89
from .commands.report import ReportCommand
910
from .commands.run import RunCommand
1011
from .model.tamarin_recipe import SchedulingStrategy
11-
from .modules.cache_manager import CacheManager
1212
from .utils.notifications import notification_manager
1313

1414
app = typer.Typer(help="batch-tamarin")
15+
app.add_typer(cache_command)
1516

1617

1718
@app.callback(invoke_without_command=True)
1819
def main_callback(
1920
ctx: typer.Context,
2021
version: bool = typer.Option(
2122
False, "--version", "-v", help="Show version information"
22-
),
23-
rm_cache: bool = typer.Option(
24-
False, "--rm-cache", help="Remove all cached results"
25-
),
23+
)
2624
):
2725
"""
2826
Batch Tamarin - Protocol verification automation tool.
@@ -45,24 +43,6 @@ def main_callback(
4543
)
4644
return
4745

48-
if rm_cache:
49-
try:
50-
cache_manager = CacheManager()
51-
stats = cache_manager.get_stats()
52-
cache_manager.clear_cache()
53-
# Format volume in human-readable units
54-
volume = stats["volume"]
55-
unit = "bytes"
56-
for unit in ["bytes", "kB", "MB", "GB"]:
57-
if volume < 1024 or unit == "GB":
58-
break
59-
volume /= 1024
60-
print(f"Cleared cache: {stats['size']} entries, {volume:.2f} {unit}")
61-
except Exception as e:
62-
print(f"Failed to clear cache: {e}")
63-
raise typer.Exit(1)
64-
return
65-
6646
if ctx.invoked_subcommand is None:
6747
print(ctx.get_help())
6848
raise typer.Exit()

src/batch_tamarin/modules/cache_manager.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from diskcache import Cache # type: ignore
1414

15-
from ..model.executable_task import ExecutableTask, TaskResult
15+
from ..model.executable_task import ExecutableTask, TaskResult, TaskStatus
1616
from ..utils.notifications import notification_manager
1717

1818

@@ -86,9 +86,36 @@ def store_result(self, task: ExecutableTask, result: TaskResult) -> None:
8686
# Store in cache
8787
self.cache[key] = cached_data
8888

89-
def clear_cache(self) -> None:
90-
"""Clear all cached results."""
91-
self.cache.clear()
89+
def clear_cache(self, errors_only: bool = False) -> None:
90+
"""
91+
Clear all cached results.
92+
93+
Args:
94+
errors_only (bool, optional): Clear only failed/error tasks. Defaults to False.
95+
"""
96+
97+
if not errors_only:
98+
self.cache.clear()
99+
else:
100+
for key in self.cache:
101+
value = self.cache.get(key)
102+
if value.task_result.status in [
103+
TaskStatus.FAILED,
104+
TaskStatus.SIGNAL_INTERRUPTED,
105+
TaskStatus.MEMORY_LIMIT_EXCEEDED,
106+
TaskStatus.TIMEOUT,
107+
]:
108+
self._delete_cache_entry(key)
109+
110+
def _delete_cache_entry(self, key: str) -> None:
111+
"""
112+
Deletes a single cache entry by key.
113+
114+
Args:
115+
key (str): The key of the entry to delete
116+
"""
117+
118+
self.cache.delete(key=key)
92119

93120
def get_stats(self) -> dict[str, int]:
94121
"""

src/batch_tamarin/utils/notifications.py

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
from ..model.batch import Batch, LemmaResult
1616
from ..model.tamarin_recipe import TamarinRecipe
17-
from ..utils.system_resources import resolve_resource_value
17+
from ..utils.system_resources import (
18+
get_human_readable_volume_size,
19+
resolve_resource_value,
20+
)
1821

1922
if TYPE_CHECKING:
2023
from ..model.executable_task import ExecutableTask
@@ -306,11 +309,11 @@ def task_execution_summary(self, batch: Batch) -> None:
306309
)
307310
runtime_table.add_row(
308311
"Total peak memory used",
309-
f"{self._format_memory(batch.execution_metadata.total_memory)}",
312+
f"{get_human_readable_volume_size(batch.execution_metadata.total_memory, 'MB')}",
310313
)
311314
runtime_table.add_row(
312315
"Max peak memory used",
313-
f"{self._format_memory(batch.execution_metadata.max_memory)}",
316+
f"{get_human_readable_volume_size(batch.execution_metadata.max_memory, 'MB')}",
314317
)
315318
runtime_table.add_row(
316319
"Freshly executed tasks",
@@ -387,8 +390,8 @@ def task_execution_summary(self, batch: Batch) -> None:
387390
duration_display = self._format_duration(
388391
rich_executable.task_execution_metadata.exec_duration_monotonic
389392
)
390-
memory_display = self._format_memory(
391-
rich_executable.task_execution_metadata.peak_memory
393+
memory_display = get_human_readable_volume_size(
394+
rich_executable.task_execution_metadata.peak_memory, "MB"
392395
)
393396

394397
cache_display = (
@@ -627,48 +630,6 @@ def _format_duration(self, seconds: float) -> str:
627630
minutes = int((seconds % 3600) // 60)
628631
return f"{hours}h {minutes}m"
629632

630-
def _format_memory(self, memory_mb: float) -> str:
631-
"""
632-
Format memory usage in human-readable format.
633-
Automatically switches between MB and GB based on size.
634-
635-
Args:
636-
memory_mb: Memory usage in megabytes
637-
638-
Returns:
639-
Formatted memory string (e.g., "256 MB" or "1.5 GB")
640-
"""
641-
if memory_mb < 1024:
642-
return f"{memory_mb:.1f} MB"
643-
else:
644-
memory_gb = memory_mb / 1024
645-
return f"{memory_gb:.1f} GB"
646-
647-
def _format_bytes(self, size_bytes: int) -> str:
648-
"""
649-
Format bytes in human-readable format.
650-
651-
Args:
652-
size_bytes: Size in bytes
653-
654-
Returns:
655-
Formatted size string (e.g., "256 bytes", "1.5 kB", "2.0 MB", "1.2 GB")
656-
"""
657-
if size_bytes == 0:
658-
return "0 bytes"
659-
660-
volume = float(size_bytes)
661-
unit = "bytes"
662-
for unit in ["bytes", "kB", "MB", "GB"]:
663-
if volume < 1024 or unit == "GB":
664-
break
665-
volume /= 1024
666-
667-
if unit == "bytes":
668-
return f"{int(volume)} {unit}"
669-
else:
670-
return f"{volume:.1f} {unit}"
671-
672633
def check_report(
673634
self,
674635
recipe: TamarinRecipe,

src/batch_tamarin/utils/system_resources.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,31 @@ def resolve_executable_path(path_or_command: str) -> Path:
138138
raise FileNotFoundError(f"Command '{path_or_command}' not found in PATH")
139139

140140
return Path(resolved_command)
141+
142+
143+
def get_human_readable_volume_size(
144+
volume_size: int | float, start_unit: str = "bytes"
145+
) -> str:
146+
"""
147+
Converts a volume size in bytes into a human-readable unit.
148+
149+
Args:
150+
volume_size (int | float): The volume size in bytes
151+
start_unit (str): One of: 'bytes', 'kB', 'MB', 'GB'
152+
153+
Returns:
154+
str: The human-readable volume size with unit
155+
"""
156+
157+
units: list[str] = ["bytes", "kB", "MB", "GB"]
158+
159+
if start_unit not in units:
160+
raise ValueError(f"The value for start_unit must be one of {units}")
161+
index = units.index(start_unit)
162+
163+
for unit in units[index:]:
164+
if volume_size < 1024 or unit == "GB":
165+
break
166+
volume_size /= 1024
167+
168+
return f"{volume_size:.2f} {unit}"

0 commit comments

Comments
 (0)