Merge pull request #1619 from Deixx/io-direction-classifier
New IO class rule `io_direction`
This commit is contained in:
commit
d4de219fec
@ -221,6 +221,42 @@ static int _cas_cls_string_ctr(struct cas_classifier *cls,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* IO direction condition constructor. @data is expected to contain string
|
||||||
|
* translated to IO direction.
|
||||||
|
*/
|
||||||
|
static int _cas_cls_direction_ctr(struct cas_classifier *cls,
|
||||||
|
struct cas_cls_condition *c, char *data)
|
||||||
|
{
|
||||||
|
uint64_t direction;
|
||||||
|
struct cas_cls_numeric *ctx;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
CAS_CLS_MSG(KERN_ERR, "Missing IO direction specifier\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp("read", data, 5) == 0) {
|
||||||
|
direction = READ;
|
||||||
|
} else if (strncmp("write", data, 6) == 0) {
|
||||||
|
direction = WRITE;
|
||||||
|
} else {
|
||||||
|
CAS_CLS_MSG(KERN_ERR, "Invalid IO direction specifier '%s'\n"
|
||||||
|
" allowed specifiers: 'read', 'write'\n", data);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
|
||||||
|
if (!ctx)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ctx->operator = cas_cls_numeric_eq;
|
||||||
|
ctx->v_u64 = direction;
|
||||||
|
|
||||||
|
c->context = ctx;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Unsigned int numeric test function */
|
/* Unsigned int numeric test function */
|
||||||
static cas_cls_eval_t _cas_cls_numeric_test_u(
|
static cas_cls_eval_t _cas_cls_numeric_test_u(
|
||||||
struct cas_cls_condition *c, uint64_t val)
|
struct cas_cls_condition *c, uint64_t val)
|
||||||
@ -664,6 +700,14 @@ static cas_cls_eval_t _cas_cls_request_size_test(
|
|||||||
return _cas_cls_numeric_test_u(c, CAS_BIO_BISIZE(io->bio));
|
return _cas_cls_numeric_test_u(c, CAS_BIO_BISIZE(io->bio));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Request IO direction test function */
|
||||||
|
static cas_cls_eval_t _cas_cls_request_direction_test(
|
||||||
|
struct cas_classifier *cls, struct cas_cls_condition *c,
|
||||||
|
struct cas_cls_io *io, ocf_part_id_t part_id)
|
||||||
|
{
|
||||||
|
return _cas_cls_numeric_test_u(c, bio_data_dir(io->bio));
|
||||||
|
}
|
||||||
|
|
||||||
/* Array of condition handlers */
|
/* Array of condition handlers */
|
||||||
static struct cas_cls_condition_handler _handlers[] = {
|
static struct cas_cls_condition_handler _handlers[] = {
|
||||||
{ "done", _cas_cls_done_test, _cas_cls_generic_ctr },
|
{ "done", _cas_cls_done_test, _cas_cls_generic_ctr },
|
||||||
@ -689,6 +733,8 @@ static struct cas_cls_condition_handler _handlers[] = {
|
|||||||
_cas_cls_generic_dtr },
|
_cas_cls_generic_dtr },
|
||||||
{ "request_size", _cas_cls_request_size_test, _cas_cls_numeric_ctr,
|
{ "request_size", _cas_cls_request_size_test, _cas_cls_numeric_ctr,
|
||||||
_cas_cls_generic_dtr },
|
_cas_cls_generic_dtr },
|
||||||
|
{ "io_direction", _cas_cls_request_direction_test,
|
||||||
|
_cas_cls_direction_ctr, _cas_cls_generic_dtr },
|
||||||
#ifdef CAS_WLTH_SUPPORT
|
#ifdef CAS_WLTH_SUPPORT
|
||||||
{ "wlth", _cas_cls_wlth_test, _cas_cls_numeric_ctr,
|
{ "wlth", _cas_cls_wlth_test, _cas_cls_numeric_ctr,
|
||||||
_cas_cls_generic_dtr},
|
_cas_cls_generic_dtr},
|
||||||
@ -757,7 +803,7 @@ static struct cas_cls_condition * _cas_cls_create_condition(
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read single codnition from text input and return cas_cls_condition
|
/* Read single condition from text input and return cas_cls_condition
|
||||||
* representation. *rule pointer is advanced to point to next condition.
|
* representation. *rule pointer is advanced to point to next condition.
|
||||||
* Input @rule string is modified to speed up parsing (selected bytes are
|
* Input @rule string is modified to speed up parsing (selected bytes are
|
||||||
* overwritten with 0).
|
* overwritten with 0).
|
||||||
@ -765,7 +811,7 @@ static struct cas_cls_condition * _cas_cls_create_condition(
|
|||||||
* *l_op contains logical operator from previous condition and gets overwritten
|
* *l_op contains logical operator from previous condition and gets overwritten
|
||||||
* with operator read from currently parsed condition.
|
* with operator read from currently parsed condition.
|
||||||
*
|
*
|
||||||
* Returns pointer to condition if successfull.
|
* Returns pointer to condition if successful.
|
||||||
* Returns NULL if no more conditions in string.
|
* Returns NULL if no more conditions in string.
|
||||||
* Returns error pointer in case of syntax or runtime error.
|
* Returns error pointer in case of syntax or runtime error.
|
||||||
*/
|
*/
|
||||||
@ -1050,9 +1096,11 @@ int cas_cls_rule_create(ocf_cache_t cache,
|
|||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
r = _cas_cls_rule_create(cls, part_id, _rule);
|
r = _cas_cls_rule_create(cls, part_id, _rule);
|
||||||
if (IS_ERR(r))
|
if (IS_ERR(r)) {
|
||||||
|
CAS_CLS_DEBUG_MSG(
|
||||||
|
"Cannot create rule: %s => %d\n", rule, part_id);
|
||||||
ret = _cas_cls_rule_err_to_cass_err(PTR_ERR(r));
|
ret = _cas_cls_rule_err_to_cass_err(PTR_ERR(r));
|
||||||
else {
|
} else {
|
||||||
CAS_CLS_DEBUG_MSG("Created rule: %s => %d\n", rule, part_id);
|
CAS_CLS_DEBUG_MSG("Created rule: %s => %d\n", rule, part_id);
|
||||||
*cls_rule = r;
|
*cls_rule = r;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
@ -185,6 +185,9 @@ class IoClass:
|
|||||||
elif rule in ["extension", "process_name", "file_name_prefix"]:
|
elif rule in ["extension", "process_name", "file_name_prefix"]:
|
||||||
allowed_chars = string.ascii_letters + string.digits
|
allowed_chars = string.ascii_letters + string.digits
|
||||||
rule += f":{''.join(random.choices(allowed_chars, k=random.randint(1, 10)))}"
|
rule += f":{''.join(random.choices(allowed_chars, k=random.randint(1, 10)))}"
|
||||||
|
elif rule == "io_direction":
|
||||||
|
direction = random.choice(["read", "write"])
|
||||||
|
rule += f":{direction}"
|
||||||
if random.randrange(2):
|
if random.randrange(2):
|
||||||
rule += "&done"
|
rule += "&done"
|
||||||
return rule
|
return rule
|
||||||
|
141
test/functional/tests/io_class/test_io_class_io_direction.py
Normal file
141
test/functional/tests/io_class/test_io_class_io_direction.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#
|
||||||
|
# Copyright(c) 2025 Huawei Technologies Co., Ltd.
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
#
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from api.cas import ioclass_config, casadm
|
||||||
|
from api.cas.cache_config import CacheMode, CleaningPolicy, SeqCutOffPolicy
|
||||||
|
from core.test_run import TestRun
|
||||||
|
from storage_devices.disk import DiskType, DiskTypeSet, DiskTypeLowerThan
|
||||||
|
from test_tools.dd import Dd
|
||||||
|
from test_tools.os_tools import sync, drop_caches
|
||||||
|
from test_tools.udev import Udev
|
||||||
|
from type_def.size import Unit, Size
|
||||||
|
|
||||||
|
dd_bs = Size(1, Unit.Blocks4096)
|
||||||
|
dd_count = 1230
|
||||||
|
cached_mountpoint = "/tmp/ioclass_core_id_test/cached"
|
||||||
|
not_cached_mountpoint = "/tmp/ioclass_core_id_test/not_cached"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.require_disk("cache", DiskTypeSet([DiskType.optane, DiskType.nand]))
|
||||||
|
@pytest.mark.require_disk("core", DiskTypeLowerThan("cache"))
|
||||||
|
def test_ioclass_io_direction():
|
||||||
|
"""
|
||||||
|
title: Test for `io_direction` classification rule
|
||||||
|
description: |
|
||||||
|
Test if IO direction rule correctly classifies IOs based on their direction.
|
||||||
|
pass_criteria:
|
||||||
|
- Reads cached to IO class with 'io_direction:read' rule
|
||||||
|
- Writes cached to IO class with 'io_direction:write' rule
|
||||||
|
"""
|
||||||
|
|
||||||
|
with TestRun.step("Prepare cache and core devices"):
|
||||||
|
cache_device = TestRun.disks["cache"]
|
||||||
|
core_device = TestRun.disks["core"]
|
||||||
|
|
||||||
|
cache_device.create_partitions([Size(10, Unit.GibiByte)])
|
||||||
|
core_device.create_partitions([Size(5, Unit.GibiByte)])
|
||||||
|
|
||||||
|
cache_device = cache_device.partitions[0]
|
||||||
|
core_device = core_device.partitions[0]
|
||||||
|
|
||||||
|
with TestRun.step("Start cache, set NOP cleaning policy and disable sequential cutoff"):
|
||||||
|
cache = casadm.start_cache(cache_device, cache_mode=CacheMode.WT, force=True)
|
||||||
|
casadm.set_param_cleaning(cache_id=cache.cache_id, policy=CleaningPolicy.nop)
|
||||||
|
cache.set_seq_cutoff_policy(SeqCutOffPolicy.never)
|
||||||
|
|
||||||
|
with TestRun.step("Disable udev"):
|
||||||
|
Udev.disable()
|
||||||
|
|
||||||
|
with TestRun.step("Add core"):
|
||||||
|
cached_volume = casadm.add_core(cache, core_dev=core_device)
|
||||||
|
|
||||||
|
with TestRun.step("Create new IO direction based classification rules"):
|
||||||
|
ioclass_config.remove_ioclass_config()
|
||||||
|
ioclass_config.create_ioclass_config(
|
||||||
|
add_default_rule=False,
|
||||||
|
ioclass_config_path=ioclass_config.default_config_file_path,
|
||||||
|
)
|
||||||
|
ioclass_config.add_ioclass(
|
||||||
|
ioclass_id=1,
|
||||||
|
eviction_priority=22,
|
||||||
|
allocation="0.00",
|
||||||
|
rule="metadata",
|
||||||
|
ioclass_config_path=ioclass_config.default_config_file_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
read_ioclass_id = 11
|
||||||
|
write_ioclass_id = 12
|
||||||
|
|
||||||
|
ioclass_config.add_ioclass(
|
||||||
|
ioclass_id=read_ioclass_id,
|
||||||
|
eviction_priority=22,
|
||||||
|
allocation="1.00",
|
||||||
|
rule="io_direction:read&done",
|
||||||
|
ioclass_config_path=ioclass_config.default_config_file_path,
|
||||||
|
)
|
||||||
|
ioclass_config.add_ioclass(
|
||||||
|
ioclass_id=write_ioclass_id,
|
||||||
|
eviction_priority=22,
|
||||||
|
allocation="1.00",
|
||||||
|
rule="io_direction:write&done",
|
||||||
|
ioclass_config_path=ioclass_config.default_config_file_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
with TestRun.step("Load ioclass config file"):
|
||||||
|
casadm.load_io_classes(
|
||||||
|
cache_id=cache.cache_id, file=ioclass_config.default_config_file_path
|
||||||
|
)
|
||||||
|
|
||||||
|
with TestRun.step("Reset counters"):
|
||||||
|
sync()
|
||||||
|
drop_caches()
|
||||||
|
cache.purge_cache()
|
||||||
|
cache.reset_counters()
|
||||||
|
|
||||||
|
with TestRun.step("Trigger IO read requests"):
|
||||||
|
(Dd()
|
||||||
|
.input(cached_volume.path)
|
||||||
|
.output("/dev/null")
|
||||||
|
.count(dd_count)
|
||||||
|
.block_size(dd_bs)
|
||||||
|
.iflag("direct")
|
||||||
|
.run())
|
||||||
|
|
||||||
|
with TestRun.step("Check IO class reads"):
|
||||||
|
read_io_class_stat = cached_volume.get_io_class_statistics(io_class_id=read_ioclass_id).request_stats.read.total
|
||||||
|
write_io_class_stat = cached_volume.get_io_class_statistics(io_class_id=write_ioclass_id).request_stats.read.total
|
||||||
|
|
||||||
|
if read_io_class_stat != dd_count:
|
||||||
|
TestRun.LOGGER.error(
|
||||||
|
f"Wrong 'read' IO class stats! Expected {dd_count} total reads, actual: {read_io_class_stat}"
|
||||||
|
)
|
||||||
|
if write_io_class_stat != 0:
|
||||||
|
TestRun.LOGGER.error(
|
||||||
|
f"Wrong 'write' IO class stats! Expected 0 total reads, actual: {write_io_class_stat}"
|
||||||
|
)
|
||||||
|
|
||||||
|
with TestRun.step("Trigger IO write requests"):
|
||||||
|
(Dd()
|
||||||
|
.input("/dev/zero")
|
||||||
|
.output(cached_volume.path)
|
||||||
|
.count(dd_count)
|
||||||
|
.block_size(dd_bs)
|
||||||
|
.oflag("direct")
|
||||||
|
.run())
|
||||||
|
|
||||||
|
with TestRun.step("Check IO class writes"):
|
||||||
|
read_io_class_stat = cached_volume.get_io_class_statistics(io_class_id=read_ioclass_id).request_stats.write.total
|
||||||
|
write_io_class_stat = cached_volume.get_io_class_statistics(io_class_id=write_ioclass_id).request_stats.write.total
|
||||||
|
|
||||||
|
if read_io_class_stat != 0:
|
||||||
|
TestRun.LOGGER.error(
|
||||||
|
f"Wrong 'read' IO class stats! Expected 0 total writes, actual: {read_io_class_stat}"
|
||||||
|
)
|
||||||
|
if write_io_class_stat != dd_count:
|
||||||
|
TestRun.LOGGER.error(
|
||||||
|
f"Wrong 'write' IO class stats! Expected {dd_count} total writes, actual: {write_io_class_stat}"
|
||||||
|
)
|
@ -1,6 +1,6 @@
|
|||||||
#
|
#
|
||||||
# Copyright(c) 2022 Intel Corporation
|
# Copyright(c) 2022 Intel Corporation
|
||||||
# Copyright(c) 2024 Huawei Technologies Co., Ltd.
|
# Copyright(c) 2024-2025 Huawei Technologies Co., Ltd.
|
||||||
# SPDX-License-Identifier: BSD-3-Clause
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
#
|
#
|
||||||
|
|
||||||
@ -44,6 +44,7 @@ parametrized_keywords = [
|
|||||||
"pid",
|
"pid",
|
||||||
"file_offset",
|
"file_offset",
|
||||||
"request_size", # number - based
|
"request_size", # number - based
|
||||||
|
"io_direction",
|
||||||
]
|
]
|
||||||
parameterless_keywords = ["metadata", "direct", "done"]
|
parameterless_keywords = ["metadata", "direct", "done"]
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ def __get_fuzzed_parameters(parameters):
|
|||||||
for index, param in enumerate(parameters):
|
for index, param in enumerate(parameters):
|
||||||
if index < TestRun.usr.fuzzy_iter_count // 10:
|
if index < TestRun.usr.fuzzy_iter_count // 10:
|
||||||
param = param.decode("ascii", "ignore")
|
param = param.decode("ascii", "ignore")
|
||||||
i = index % len(parametrized_keywords) + len(parameterless_keywords) + 1
|
i = index % (len(parametrized_keywords) + len(parameterless_keywords) + 1)
|
||||||
match i:
|
match i:
|
||||||
case 1:
|
case 1:
|
||||||
fuzzed_parameters.append(f"directory:{param}")
|
fuzzed_parameters.append(f"directory:{param}")
|
||||||
@ -153,6 +154,8 @@ def __get_fuzzed_parameters(parameters):
|
|||||||
fuzzed_parameters.append(f"direct{'' if i % 10 == 0 else param}")
|
fuzzed_parameters.append(f"direct{'' if i % 10 == 0 else param}")
|
||||||
case 13:
|
case 13:
|
||||||
fuzzed_parameters.append(f"done{'' if i % 10 == 0 else param}")
|
fuzzed_parameters.append(f"done{'' if i % 10 == 0 else param}")
|
||||||
|
case 14:
|
||||||
|
fuzzed_parameters.append(f"io_direction:{param}")
|
||||||
case _:
|
case _:
|
||||||
fuzzed_parameters.append(param)
|
fuzzed_parameters.append(param)
|
||||||
else:
|
else:
|
||||||
@ -218,6 +221,8 @@ def __validate_single_condition(value: str):
|
|||||||
if condition_key in ["directory", "file_name_prefix", "extension", "process_name"]:
|
if condition_key in ["directory", "file_name_prefix", "extension", "process_name"]:
|
||||||
if 0 < len(condition_value) <= 255:
|
if 0 < len(condition_value) <= 255:
|
||||||
return True
|
return True
|
||||||
|
elif condition_key == "io_direction":
|
||||||
|
return condition_value in ["read", "write"]
|
||||||
elif condition_key in [
|
elif condition_key in [
|
||||||
"file_size",
|
"file_size",
|
||||||
"io_class",
|
"io_class",
|
||||||
|
Loading…
Reference in New Issue
Block a user