diff --git a/src/frostfs_testlib/load/k6.py b/src/frostfs_testlib/load/k6.py index 38167d24..1e98b989 100644 --- a/src/frostfs_testlib/load/k6.py +++ b/src/frostfs_testlib/load/k6.py @@ -61,7 +61,7 @@ class K6: self._k6_dir: str = k6_dir command = ( - f"{self._k6_dir}/k6 run {self._generate_env_variables()} " + f"{self._generate_env_variables()}{self._k6_dir}/k6 run {self._generate_k6_variables()} " f"{self._k6_dir}/scenarios/{self.load_params.scenario.value}.js" ) user = STORAGE_USER_NAME if self.load_params.scenario == LoadScenario.LOCAL else None @@ -75,12 +75,12 @@ class K6: def _get_fill_percents(self): fill_percents = self.shell.exec("df -H --output=source,pcent,target | grep frostfs").stdout.split("\n") return [line.split() for line in fill_percents][:-1] - + def check_fill_percent(self): fill_percents = self._get_fill_percents() percent_mean = 0 for line in fill_percents: - percent_mean += float(line[1].split('%')[0]) + percent_mean += float(line[1].split("%")[0]) percent_mean = percent_mean / len(fill_percents) logger.info(f"{self.loader.ip} mean fill percent is {percent_mean}") return percent_mean >= self.load_params.fill_percent @@ -125,9 +125,9 @@ class K6: self.preset_output = result.stdout.strip("\n") return self.preset_output - @reporter.step("Generate K6 command") - def _generate_env_variables(self) -> str: - env_vars = self.load_params.get_env_vars() + @reporter.step("Generate K6 variables") + def _generate_k6_variables(self) -> str: + env_vars = self.load_params.get_k6_vars() env_vars[f"{self.load_params.load_type.value.upper()}_ENDPOINTS"] = ",".join(self.endpoints) env_vars["SUMMARY_JSON"] = self.summary_json @@ -135,6 +135,14 @@ class K6: reporter.attach("\n".join(f"{param}: {value}" for param, value in env_vars.items()), "K6 ENV variables") return " ".join([f"-e {param}='{value}'" for param, value in env_vars.items() if value is not None]) + @reporter.step("Generate env variables") + def _generate_env_variables(self) -> str: + env_vars = self.load_params.get_env_vars() + if not env_vars: + return "" + reporter.attach("\n".join(f"{param}: {value}" for param, value in env_vars.items()), "ENV variables") + return " ".join([f"{param}='{value}'" for param, value in env_vars.items() if value is not None]) + " " + def get_start_time(self) -> datetime: return datetime.fromtimestamp(self._k6_process.start_time()) @@ -188,23 +196,25 @@ class K6: wait_interval = min_wait_interval if self._k6_process is None: assert "No k6 instances were executed" - + while timeout > 0: if not self.load_params.fill_percent is None: with reporter.step(f"Check the percentage of filling of all data disks on the node"): if self.check_fill_percent(): - logger.info(f"Stopping load on because disks is filled more then {self.load_params.fill_percent}%") + logger.info( + f"Stopping load on because disks is filled more then {self.load_params.fill_percent}%" + ) event.set() self.stop() return - + if event.is_set(): self.stop() return - + if not self._k6_process.running(): return - + remaining_time_hours = f"{timeout//3600}h" if timeout // 3600 != 0 else "" remaining_time_minutes = f"{timeout//60%60}m" if timeout // 60 % 60 != 0 else "" logger.info( diff --git a/src/frostfs_testlib/load/load_config.py b/src/frostfs_testlib/load/load_config.py index 7bde3991..b859971c 100644 --- a/src/frostfs_testlib/load/load_config.py +++ b/src/frostfs_testlib/load/load_config.py @@ -94,16 +94,18 @@ def metadata_field( string_repr: Optional[bool] = True, distributed: Optional[bool] = False, formatter: Optional[Callable] = None, + env_variable: Optional[str] = None, ): return field( default=None, metadata={ "applicable_scenarios": applicable_scenarios, "preset_argument": preset_param, - "env_variable": scenario_variable, + "scenario_variable": scenario_variable, "string_repr": string_repr, "distributed": distributed, "formatter": formatter, + "env_variable": env_variable, }, ) @@ -172,6 +174,20 @@ class Preset: local: Optional[bool] = metadata_field(grpc_preset_scenarios, "local", None, False) +@dataclass +class PrometheusParams: + # Prometheus server URL + server_url: Optional[str] = metadata_field( + all_load_scenarios, env_variable="K6_PROMETHEUS_RW_SERVER_URL", string_repr=False + ) + # Prometheus trend stats + trend_stats: Optional[str] = metadata_field( + all_load_scenarios, env_variable="K6_PROMETHEUS_RW_TREND_STATS", string_repr=False + ) + # Additional tags + metrics_tags: Optional[str] = metadata_field(all_load_scenarios, None, "METRIC_TAGS", False) + + @dataclass class LoadParams: # ------- CONTROL PARAMS ------- @@ -223,6 +239,10 @@ class LoadParams: fill_percent: Optional[float] = None # if set, the payload is generated on the fly and is not read into memory fully. streaming: Optional[int] = metadata_field(all_load_scenarios, None, "STREAMING", False) + # Output format + output: Optional[str] = metadata_field(all_load_scenarios, None, "K6_OUT", False) + # Prometheus params + prometheus: Optional[PrometheusParams] = None # ------- COMMON SCENARIO PARAMS ------- # Load time is the maximum duration for k6 to give load. Default is the BACKGROUND_LOAD_DEFAULT_TIME value. @@ -339,6 +359,17 @@ class LoadParams: if self.preset: self.preset.pregen_json = os.path.join(self.working_dir, f"{load_id}_prepare.json") + def get_k6_vars(self): + env_vars = { + meta_field.metadata["scenario_variable"]: meta_field.value + for meta_field in self._get_meta_fields(self) + if self.scenario in meta_field.metadata["applicable_scenarios"] + and meta_field.metadata["scenario_variable"] + and meta_field.value is not None + } + + return env_vars + def get_env_vars(self): env_vars = { meta_field.metadata["env_variable"]: meta_field.value diff --git a/tests/test_load_config.py b/tests/test_load_config.py index dc019b71..62339f61 100644 --- a/tests/test_load_config.py +++ b/tests/test_load_config.py @@ -157,6 +157,7 @@ class TestLoadConfig: "DELETERS": 8, "READ_AGE": 8, "STREAMING": 9, + "K6_OUT": "output", "PREGEN_JSON": "pregen_json", "PREPARE_LOCALLY": True, } @@ -181,6 +182,7 @@ class TestLoadConfig: expected_env_vars = { "DURATION": 9, "WRITE_OBJ_SIZE": 11, + "K6_OUT": "output", "REGISTRY_FILE": "registry_file", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", "K6_SETUP_TIMEOUT": "setup_timeout", @@ -221,6 +223,7 @@ class TestLoadConfig: "DURATION": 9, "WRITE_OBJ_SIZE": 11, "REGISTRY_FILE": "registry_file", + "K6_OUT": "output", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", "K6_SETUP_TIMEOUT": "setup_timeout", "WRITERS": 7, @@ -254,6 +257,7 @@ class TestLoadConfig: "DURATION": 183900, "WRITE_OBJ_SIZE": 11, "REGISTRY_FILE": "registry_file", + "K6_OUT": "output", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", "K6_SETUP_TIMEOUT": "setup_timeout", "NO_VERIFY_SSL": True, @@ -293,6 +297,7 @@ class TestLoadConfig: "DURATION": 9, "WRITE_OBJ_SIZE": 11, "REGISTRY_FILE": "registry_file", + "K6_OUT": "output", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", "K6_SETUP_TIMEOUT": "setup_timeout", "NO_VERIFY_SSL": True, @@ -332,6 +337,7 @@ class TestLoadConfig: expected_env_vars = { "DURATION": 9, "WRITE_OBJ_SIZE": 11, + "K6_OUT": "output", "NO_VERIFY_SSL": True, "REGISTRY_FILE": "registry_file", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", @@ -365,6 +371,7 @@ class TestLoadConfig: "CONFIG_FILE": "config_file", "DURATION": 9, "WRITE_OBJ_SIZE": 11, + "K6_OUT": "output", "REGISTRY_FILE": "registry_file", "K6_MIN_ITERATION_DURATION": "min_iteration_duration", "K6_SETUP_TIMEOUT": "setup_timeout", @@ -419,6 +426,7 @@ class TestLoadConfig: "DURATION": 0, "WRITE_OBJ_SIZE": 0, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "WRITERS": 0, @@ -449,6 +457,7 @@ class TestLoadConfig: "DURATION": 0, "WRITE_OBJ_SIZE": 0, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "MAX_WRITERS": 0, @@ -486,6 +495,7 @@ class TestLoadConfig: "DURATION": 0, "WRITE_OBJ_SIZE": 0, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "WRITERS": 0, @@ -516,6 +526,7 @@ class TestLoadConfig: "DURATION": 0, "WRITE_OBJ_SIZE": 0, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "NO_VERIFY_SSL": False, @@ -554,6 +565,7 @@ class TestLoadConfig: "WRITE_OBJ_SIZE": 0, "NO_VERIFY_SSL": False, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "WRITERS": 0, @@ -584,6 +596,7 @@ class TestLoadConfig: "DURATION": 0, "WRITE_OBJ_SIZE": 0, "REGISTRY_FILE": "", + "K6_OUT": "", "K6_MIN_ITERATION_DURATION": "", "K6_SETUP_TIMEOUT": "", "WRITERS": 0, @@ -655,7 +668,7 @@ class TestLoadConfig: assert sorted(preset_parameters) == sorted(expected_preset_args) def _check_env_vars(self, load_params: LoadParams, expected_env_vars: dict[str, str]): - env_vars = load_params.get_env_vars() + env_vars = load_params.get_k6_vars() assert env_vars == expected_env_vars def _check_all_values_none(self, dataclass, skip_fields=None):