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
8 changes: 6 additions & 2 deletions test/functional/api/cas/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from connection.utils.output import Output
from storage_devices.device import Device
from test_tools.os_tools import sync
from type_def.size import Size
from type_def.size import Size, Unit


class Cache:
Expand Down Expand Up @@ -210,7 +210,11 @@ def set_params_alru(self, alru_params: FlushParametersAlru) -> Output:
else None
),
(alru_params.dirty_ratio_threshold if alru_params.dirty_ratio_threshold else None),
(alru_params.dirty_ratio_inertia if alru_params.dirty_ratio_inertia else None),
(
int(alru_params.dirty_ratio_inertia.get_value(Unit.MebiByte))
if alru_params.dirty_ratio_inertia
else None
),
)

def set_promotion_policy(self, policy: PromotionPolicy) -> Output:
Expand Down
4 changes: 2 additions & 2 deletions test/functional/api/cas/casadm.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,15 @@ def set_param_cleaning_alru(
flush_max_buffers: int | None = None,
activity_threshold: int | None = None,
dirty_ratio_threshold: int | None = None,
dirty_ratio_inertia: Size | None = None,
dirty_ratio_inertia: int | None = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I would change it I would change it the other way around - that is staleness_time: Time and so on
casadm.py for real life units, cas.py for string-converted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the high level api in cache.py uses Time and Size. Do we need the real life units on two layers of the api?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

casadm.py is the everyday api - cache.py just has helpers

Copy link
Contributor Author

@mmichal10 mmichal10 Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by every day api? If a test is testing the behavior of alru, what would be the benefit of using casadm.py api instead of the helper? casadm.py api can be used to test if casadm doesn't accept incorrect values (e.g. negative size) as it's more flexible, but apart from that tests should use the high level api. Besides, all the other functions in this file accept ints as params; how are alru params different from the other cache params?

shortcut: bool = False,
) -> Output:
_wake_up = str(wake_up) if wake_up is not None else None
_staleness_time = str(staleness_time) if staleness_time is not None else None
_flush_max_buffers = str(flush_max_buffers) if flush_max_buffers is not None else None
_activity_threshold = str(activity_threshold) if activity_threshold is not None else None
_dirty_ratio_threshold = str(dirty_ratio_threshold) if dirty_ratio_threshold is not None else None
_dirty_ratio_inertia = str(dirty_ratio_inertia.get_value(Unit.MebiByte)) if dirty_ratio_inertia is not None else None
_dirty_ratio_inertia = str(dirty_ratio_inertia) if dirty_ratio_inertia is not None else None
output = TestRun.executor.run(
set_param_cleaning_alru_cmd(
cache_id=str(cache_id),
Expand Down
225 changes: 216 additions & 9 deletions test/functional/tests/lazy_writes/cleaning_policy/test_alru.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#
# Copyright(c) 2020-2021 Intel Corporation
# Copyright(c) 2024 Huawei Technologies Co., Ltd.
# Copyright(c) 2024-2025 Huawei Technologies Co., Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#

Expand All @@ -10,7 +10,13 @@
from datetime import timedelta

from api.cas import casadm
from api.cas.cache_config import CacheMode, CleaningPolicy, FlushParametersAlru, SeqCutOffPolicy
from api.cas.cache_config import (
CacheMode,
CleaningPolicy,
FlushParametersAlru,
SeqCutOffPolicy,
CacheLineSize,
)
from core.test_run import TestRun
from storage_devices.disk import DiskType, DiskTypeSet, DiskTypeLowerThan
from test_tools.fio.fio import Fio
Expand All @@ -25,13 +31,13 @@
@pytest.mark.require_disk("core", DiskTypeLowerThan("cache"))
def test_alru_no_idle():
"""
title: Test ALRU with activity threshold set to 0
description: |
Verify that ALRU is able to perform cleaning if cache is under constant load and
activity threshold is set to 0. Constant load is performed by using fio instance running
in background.
pass_criteria:
- Dirty cache lines are cleaned successfully.
title: Test ALRU with activity threshold set to 0
description: |
Verify that ALRU is able to perform cleaning if cache is under constant load and
activity threshold is set to 0. Constant load is performed by using fio instance running
in background.
pass_criteria:
- Dirty cache lines are cleaned successfully.
"""

with TestRun.step("Prepare configuration"):
Expand Down Expand Up @@ -118,3 +124,204 @@ def prepare():
)

return cache, core


@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand]))
@pytest.mark.require_disk("core", DiskTypeLowerThan("cache"))
@pytest.mark.parametrize("inertia", [Size.zero(), Size(1500, Unit.MiB)])
@pytest.mark.parametrizex(
"cache_line_size",
[CacheLineSize.LINE_4KiB, CacheLineSize.LINE_64KiB],
)
def test_alru_dirty_ratio_inertia_no_cleaning_if_dirty_below_threshold(
cache_line_size: CacheLineSize, inertia: Unit
):
"""
title: Test ALRU dirty ratio inertia — no cleaning below threshold
description: |
Verify that ALRU cleaning is not triggered when the number of dirty cache lines is lower than
(threshold - intertia)
pass_criteria:
- The cleaning is not triggered when dirty data doesn't exceed the specified threshold.
"""
with TestRun.step("Prepare disks for cache and core"):
cache_dev = TestRun.disks["cache"]
core_dev = TestRun.disks["core"]

cache_dev.create_partitions([Size(3, Unit.GiB)])
core_dev.create_partitions([Size(10, Unit.GiB)])

with TestRun.step("Disable udev"):
Udev.disable()

with TestRun.step("Start cache and add core"):
cache = casadm.start_cache(
cache_dev.partitions[0],
cache_line_size=cache_line_size,
force=True,
cache_mode=CacheMode.WB,
)
core = cache.add_core(core_dev.partitions[0])

with TestRun.step("Set ALRU and disable sequential cutoff"):
cache.set_seq_cutoff_policy(SeqCutOffPolicy.never)
cache.set_cleaning_policy(CleaningPolicy.alru)

with TestRun.step(f"Set alru params"):
cache.set_params_alru(
FlushParametersAlru(
staleness_time=Time(seconds=3600),
wake_up_time=Time(seconds=1),
activity_threshold=Time(milliseconds=1000),
dirty_ratio_threshold=90,
dirty_ratio_inertia=inertia,
)
)

with TestRun.step("Run write workload to reach ~2GiB (≈66% of 3GiB) dirty data"):
fio = (
Fio()
.create_command()
.io_engine(IoEngine.libaio)
.size(cache.size * 0.66)
.block_size(Size(4, Unit.KiB))
.target(core)
.direct()
.read_write(ReadWrite.randwrite)
)
fio.run()

with TestRun.step("Capture baseline dirty usage after I/O settles"):
time.sleep(2)
dirty_before_pct = cache.get_statistics(percentage_val=True).usage_stats.dirty
dirty_before = cache.get_statistics().usage_stats.dirty
if dirty_before_pct <= 60:
TestRun.fail(
f"Exception: Precondition not met: dirty cache lines must exceed 60% after "
f"I/O settles (dirty={dirty_before}, dirty%={dirty_before_pct}%). Aborting test."
)

with TestRun.step("Idle and verify dirty cache lines do not change and remain below threshold"):
time.sleep(30)
dirty_after = cache.get_statistics().usage_stats.dirty

if dirty_before > dirty_after:
TestRun.fail(
f"No flushing shall occur when dirty < threshold (dirty before={dirty_before}, "
f"dirty after={dirty_after}, threshold={dirty_ratio_threshold}%)"
)


@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand]))
@pytest.mark.require_disk("core", DiskTypeLowerThan("cache"))
@pytest.mark.parametrize("inertia", [Size.zero(), Size(600, Unit.MiB)])
def test_alru_dirty_threshold_cleans_until_threshold_minus_inertia(inertia: Size):
"""
title: Test ALRU threshold cleaning – start > 50%, stop at (threshold - inertia)
description: |
Verify that with ALRU cleaning configured with 50% dirty threshold and
specified inertia in MiB, cleaning starts when dirty exceeds the threshold
and stops after reaching (threshold - inertia).
pass_criteria:
- Cleaning starts once dirty exceeds 50%.
- Dirty decreases over time after stopping I/O.
- Cleaning stops at or below (threshold - inertia).
- If dirty does not decrease for 20 consecutive checks, fail the test.
"""

target_dirty_ratio_threshold = 50
# 3 GiB cache -> 50% = 1536 MiB; inertia is in MiB
threshold_size = Size(1536, Unit.MiB)
target_cleaning_threshold = threshold_size - inertia

with TestRun.step("Prepare 3GiB cache and 10GiB core, disable udev, start cache WB, add core"):
cache_dev = TestRun.disks["cache"]
core_dev = TestRun.disks["core"]

cache_dev.create_partitions([Size(3, Unit.GiB)])
core_dev.create_partitions([Size(10, Unit.GiB)])

with TestRun.step("Disable udev"):
Udev.disable()

with TestRun.step("Start cache and add core"):
cache = casadm.start_cache(cache_dev.partitions[0], force=True, cache_mode=CacheMode.WB)
core = cache.add_core(core_dev.partitions[0])

with TestRun.step("Set ALRU and disable sequential cutoff"):
cache.set_seq_cutoff_policy(SeqCutOffPolicy.never)
cache.set_cleaning_policy(CleaningPolicy.alru)

with TestRun.step(
f"Configure ALRU: dirty threshold=50%, inertia={inertia}, "
f"default wakeup, staleness_time=3600s"
):
cache.set_params_alru(
FlushParametersAlru(
staleness_time=Time(seconds=3600),
dirty_ratio_threshold=target_dirty_ratio_threshold,
dirty_ratio_inertia=inertia,
)
)

with TestRun.step("Start background fio (4 GiB randwrite)"):
fio = (
Fio()
.create_command()
.io_engine(IoEngine.libaio)
.size(Size(4, Unit.GiB))
.block_size(Size(4, Unit.KiB))
.target(core)
.direct()
.read_write(ReadWrite.randwrite)
)
fio.run_in_background()

with TestRun.step("Wait until cache dirty >= 90%, then stop workload"):
start_ts = time.time()
while True:
time.sleep(2)
dirty_pct = cache.get_statistics(percentage_val=True).usage_stats.dirty
if dirty_pct >= 90:
break
if time.time() - start_ts > 5 * 60:
TestRun.fail(
f"Exception: Cache dirty level did not reach 90% within 5 minutes "
f"(current dirty={dirty_pct}%). Aborting test."
)

with TestRun.step("Stop the I/O"):
kill_all_io()

with TestRun.step(
"Observe cleaning: ensure monotonic decrease and stop at (threshold - inertia)"
):
# Initial dirty after IO stop
last_dirty = cache.get_statistics().usage_stats.dirty

not_decreasing = 0
while last_dirty > target_cleaning_threshold:
time.sleep(5)
dirty_now = cache.get_statistics().usage_stats.dirty

if dirty_now < last_dirty:
not_decreasing = 0
else:
not_decreasing += 1
if not_decreasing >= 20:
TestRun.fail(
f"Exception: Dirty amount not decreasing for 20 consecutive checks "
f"(stuck at {dirty_now}, target stop {target_cleaning_threshold})."
)

last_dirty = dirty_now

with TestRun.step(
"Wait 30 seconds and confirm that the cleaning stopped after reaching the target threshold"
):
time.sleep(30)
if last_dirty > target_cleaning_threshold:
TestRun.fail(
f"Cleaning did not stop at expected level "
f"(dirty={last_dirty}, expected <= {target_cleaning_threshold})."
)
Loading