-
Notifications
You must be signed in to change notification settings - Fork 1
feature: [ESXi] CPU measurement using esxtop #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,19 @@ | |
|
|
||
| import logging | ||
| import re | ||
| from time import sleep | ||
|
|
||
| from mfd_common_libs import add_logging_level, log_levels, TimeoutCounter | ||
| from mfd_connect.process.rpyc import RPyCProcess | ||
|
|
||
| from mfd_common_libs import add_logging_level, log_levels | ||
| from mfd_host.exceptions import CPUFeatureExecutionError, CPUFeatureException | ||
| from mfd_host.feature.cpu.base import BaseFeatureCPU | ||
| from mfd_network_adapter.data_structures import State | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
| add_logging_level("MODULE_DEBUG", log_levels.MODULE_DEBUG) | ||
|
|
||
| ESXTOP_FILE_LOCATION = "/tmp/esxtop_cpu.log" | ||
|
|
||
| class ESXiCPU(BaseFeatureCPU): | ||
| """ESXi class for CPU feature.""" | ||
|
|
@@ -62,3 +66,75 @@ def set_numa_affinity(self, numa_state: State) -> None: | |
| f'esxcli system settings advanced set {value} -o "/Numa/LocalityWeightActionAffinity"', | ||
| custom_exception=CPUFeatureExecutionError, | ||
| ) | ||
|
|
||
| def start_cpu_measurement(self) -> RPyCProcess: | ||
| """ | ||
| Start CPU measurement on SUT host. | ||
|
|
||
| :return: Handle to process | ||
| """ | ||
| logger.debug(msg="Start CPU measurement on SUT Host") | ||
| return self._connection.start_process( | ||
| command="esxtop -b -n 8 -d3", log_file=True, output_file=ESXTOP_FILE_LOCATION, shell=True | ||
| ) | ||
|
|
||
| def stop_cpu_measurement(self, process: RPyCProcess, vm_name: str) -> int: | ||
| """ | ||
| Stop CPU measurement process. | ||
|
|
||
| :param process: Process handle | ||
| :param vm_name: VM name to filter CPU usage | ||
| :return: Average CPU usage percentage | ||
| """ | ||
| logger.debug(msg="Stop CPU measurement on SUT Host") | ||
| if process.running: | ||
| process.stop() | ||
| timeout = TimeoutCounter(5) | ||
| while not timeout: | ||
| if not process.running: | ||
| break | ||
| sleep(1) | ||
| else: | ||
| process.kill() | ||
| timeout = TimeoutCounter(5) | ||
| while not timeout: | ||
| if not process.running: | ||
| break | ||
| sleep(1) | ||
| if process.running: | ||
| raise RuntimeError("CPU measurement process is still running after stop and kill.") | ||
|
|
||
| return self.parse_cpu_usage(vm_name=vm_name, process=process) | ||
|
|
||
| def parse_cpu_usage(self, vm_name: str, process: RPyCProcess) -> int: | ||
| """ | ||
| Parse CPU usage from esxtop output. | ||
|
|
||
| :param vm_name: VM name to filter CPU usage | ||
| :param process: Process handle | ||
| :return: average CPU usage percentage | ||
| """ | ||
| parsed_file_path = "/tmp/parsed_output.txt" | ||
| command = ( | ||
| f"cut -d, -f`awk -F, '{{for (i=1;i<=NF;i++){{if ($i ~/Group Cpu.*{vm_name}).*Used/) " | ||
| f"{{print i}}}}}}' {process.log_path}` {process.log_path}>{parsed_file_path}" | ||
| ) | ||
|
Comment on lines
+118
to
+121
|
||
| self._connection.execute_command(command=command, shell=True) | ||
|
||
| p = self._connection.path(process.log_path) | ||
| p.unlink() | ||
|
Comment on lines
+122
to
+124
|
||
| try: | ||
| with self._connection.modules().builtins.open(parsed_file_path, "r") as f: | ||
| file_content = f.read() | ||
| cpu_list = [] | ||
| for line in file_content.splitlines()[1:]: | ||
| try: | ||
| cpu_list.append(float(line.strip('"'))) | ||
| except ValueError: | ||
| continue | ||
|
|
||
| except Exception as e: | ||
| raise RuntimeError(f"Failed to read parsed CPU usage output file due to - {e}.") | ||
|
|
||
| p = self._connection.path(parsed_file_path) | ||
| p.unlink() | ||
| return round(sum(cpu_list) / len(cpu_list)) if cpu_list else 0 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded path '/tmp/parsed_output.txt' creates a security risk as it could be exploited through race conditions or predictable file paths. Consider using
tempfile.NamedTemporaryFile()or generating unique filenames with timestamps or UUIDs to prevent potential file overwrites or unauthorized access.