diff --git a/benchmark_runner/common/template_operations/template_operations.py b/benchmark_runner/common/template_operations/template_operations.py index 10f3c1bb..a30ce175 100644 --- a/benchmark_runner/common/template_operations/template_operations.py +++ b/benchmark_runner/common/template_operations/template_operations.py @@ -162,6 +162,15 @@ def __generate_yamls_internal(self, scale: str = None, scale_num: str = None, sc logger.info(f'HAMMERDB_CONFIG override: {hammerdb_config}') render_data.update(hammerdb_config) + workload_config = self.__environment_variables_dict.get('workload_config', {}) + if workload_config: + workload_config = {k: v for k, v in workload_config.items() if v != ''} + unknown_keys = set(workload_config.keys()) - set(render_data.keys()) + if unknown_keys: + logger.warning(f'WORKLOAD_CONFIG unknown keys (will be ignored): {unknown_keys}') + logger.info(f'WORKLOAD_CONFIG override: {workload_config}') + render_data.update(workload_config) + out_files = [] standard_template_path = os.path.join(workload_dir_path, 'internal_data', self.__standard_template_file) if os.path.isfile(standard_template_path): diff --git a/benchmark_runner/common/template_operations/templates/linstress/__init__.py b/benchmark_runner/common/template_operations/templates/linstress/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmark_runner/common/template_operations/templates/linstress/internal_data/__init__.py b/benchmark_runner/common/template_operations/templates/linstress/internal_data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmark_runner/common/template_operations/templates/linstress/internal_data/linstress_vm_template.yaml b/benchmark_runner/common/template_operations/templates/linstress/internal_data/linstress_vm_template.yaml new file mode 100644 index 00000000..b7a80e07 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/linstress/internal_data/linstress_vm_template.yaml @@ -0,0 +1,196 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-{{ trunc_uuid }} + namespace: {{ namespace }} +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + {%- if ssh_public_key is defined and ssh_public_key %} + ssh_authorized_keys: + - "{{ ssh_public_key }}" + {%- endif %} + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * {{ stress_cpu }} / 100)) + duration = {{ stress_duration }} + mem_target = {{ stress_memory }} + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs ({{ stress_cpu }}%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': {{ stress_cpu }}, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + {% if scale -%} + name: linstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + labels: + kubevirt.io/vm: linstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: linstress-{{ kind }}-{{ trunc_uuid }} + labels: + kubevirt.io/vm: linstress-{{ kind }}-{{ trunc_uuid }} + {%- endif %} + namespace: {{ namespace }} +spec: + running: true + template: + metadata: + labels: + {% if scale -%} + kubevirt.io/vm: linstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + kubevirt.io/vm: linstress-{{ kind }}-{{ trunc_uuid }} + {%- endif %} + spec: + {%- if pin == 'true' or pin == true %} + nodeSelector: + kubernetes.io/hostname: '{{ pin_node }}' + {%- endif %} + domain: + cpu: + sockets: {{ sockets }} + cores: {{ cores }} + threads: {{ threads }} + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: {{ requests_memory }} + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: {{ fedora_container_disk }} + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-{{ trunc_uuid }} diff --git a/benchmark_runner/common/template_operations/templates/linstress/linstress_data_template.yaml b/benchmark_runner/common/template_operations/templates/linstress/linstress_data_template.yaml new file mode 100644 index 00000000..8f4f49b3 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/linstress/linstress_data_template.yaml @@ -0,0 +1,24 @@ +metadata: + name: linstress +template_data: + shared: + pin_node: {{ pin_node0 }} + odf_pvc: {{ odf_pvc }} + uuid: {{ uuid }} + fedora_container_disk: {{ fedora_container_disk }} + stress_cpu: 100 + stress_memory: 50 + stress_duration: 600 + run_type: + perf_ci: + requests_memory: 8Gi + requests_cpu: 8 + cores: 1 + sockets: 8 + threads: 1 + default: + requests_memory: 1Gi + requests_cpu: 1 + cores: 1 + sockets: 2 + threads: 1 diff --git a/benchmark_runner/common/template_operations/templates/winstress/__init__.py b/benchmark_runner/common/template_operations/templates/winstress/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmark_runner/common/template_operations/templates/winstress/internal_data/01_run_stress_template.ps1 b/benchmark_runner/common/template_operations/templates/winstress/internal_data/01_run_stress_template.ps1 new file mode 100644 index 00000000..353c1c79 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/winstress/internal_data/01_run_stress_template.ps1 @@ -0,0 +1,123 @@ +$ErrorActionPreference = 'Stop' + +$stressDir = 'C:\tools\stress' +$pythonExe = 'C:\Program Files\Python312\python.exe' +$stressScript = "$stressDir\stress.py" +$reportFile = "$stressDir\stress_report.json" + +New-Item -ItemType Directory -Force -Path $stressDir | Out-Null + +$scriptContent = @" +import multiprocessing, time, psutil, json + +def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + +def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + +if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * {{ stress_cpu }} / 100)) + duration = {{ stress_duration }} + mem_target = {{ stress_memory }} + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs ({{"{{ stress_cpu }}"}}%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': {{ stress_cpu }}, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open(r'C:\tools\stress\stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to C:\\tools\\stress\\stress_report.json') + print('Done') +"@ + +Set-Content -Path $stressScript -Value $scriptContent -Force +Write-Host "Running stress test..." +& $pythonExe $stressScript +Write-Host "Stress test complete" diff --git a/benchmark_runner/common/template_operations/templates/winstress/internal_data/__init__.py b/benchmark_runner/common/template_operations/templates/winstress/internal_data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/benchmark_runner/common/template_operations/templates/winstress/internal_data/windows_dv_template.yaml b/benchmark_runner/common/template_operations/templates/winstress/internal_data/windows_dv_template.yaml new file mode 100644 index 00000000..7e631a43 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/winstress/internal_data/windows_dv_template.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: {{ namespace }} +spec: + source: + http: + url: {{ url }} + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ storage }} + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/benchmark_runner/common/template_operations/templates/winstress/internal_data/winstress_vm_template.yaml b/benchmark_runner/common/template_operations/templates/winstress/internal_data/winstress_vm_template.yaml new file mode 100644 index 00000000..56d97a12 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/winstress/internal_data/winstress_vm_template.yaml @@ -0,0 +1,152 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + {% if scale -%} + name: winstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + labels: + kubevirt.io/vm: winstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: winstress-{{ kind }}-{{ trunc_uuid }} + labels: + kubevirt.io/vm: winstress-{{ kind }}-{{ trunc_uuid }} + {%- endif %} + namespace: {{ namespace }} +spec: + {% if run_strategy -%} + runStrategy: Always + {%- else -%} + runStrategy: Halted + {%- endif %} + template: + metadata: + labels: + {% if scale -%} + kubevirt.io/vm: winstress-{{ kind }}-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + kubevirt.io/vm: winstress-{{ kind }}-{{ trunc_uuid }} + {%- endif %} + spec: + {%- if pin == 'true' or pin == true %} + nodeSelector: + kubernetes.io/hostname: '{{ pin_node }}' + {%- endif %} + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: {{ cores }} + sockets: {{ sockets }} + threads: {{ threads }} + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - {% if scale -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }} + {%- endif %} + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: {{ requests_cpu }} + memory: {{ requests_memory }} + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + {% if scale -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }} + {%- endif %} + {% if scale -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }} + {%- endif %} + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '{{ ssh_public_key }}' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + {% if scale -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }}-{{ scale }} + {%- else -%} + name: winstress-{{ kind }}-root-disk-{{ trunc_uuid }} + {%- endif %} + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: {{ storage }} + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: {{ namespace }} + name: windows-clone-dv diff --git a/benchmark_runner/common/template_operations/templates/winstress/winstress_data_template.yaml b/benchmark_runner/common/template_operations/templates/winstress/winstress_data_template.yaml new file mode 100644 index 00000000..b6f295a0 --- /dev/null +++ b/benchmark_runner/common/template_operations/templates/winstress/winstress_data_template.yaml @@ -0,0 +1,29 @@ +metadata: + name: windows +files: + - name: 01_run_stress.ps1 + template: 01_run_stress_template.ps1 +template_data: + shared: + pin_node: {{ pin_node0 }} + odf_pvc: {{ odf_pvc }} + uuid: {{ uuid }} + url: {{ windows_url }} + stress_cpu: 100 + stress_memory: 50 + stress_duration: 600 + run_type: + perf_ci: + requests_memory: 32G + requests_cpu: 8 + cores: 1 + sockets: 64 + threads: 1 + storage: 76Gi + default: + requests_memory: 4G + requests_cpu: 1 + cores: 1 + sockets: 2 + threads: 1 + storage: 76Gi diff --git a/benchmark_runner/main/environment_variables.py b/benchmark_runner/main/environment_variables.py index 9316c43a..c9a4ee24 100644 --- a/benchmark_runner/main/environment_variables.py +++ b/benchmark_runner/main/environment_variables.py @@ -124,7 +124,7 @@ def __init__(self): 'vdbench_pod_ephemeral', 'vdbench_vm_ephemeral', 'fio_pod', 'fio_vm', 'fio_pod_ephemeral', 'fio_vm_ephemeral', - 'clusterbuster', 'bootstorm_vm', 'windows_vm', 'winmssql_vm', 'winfio_vm', + 'clusterbuster', 'bootstorm_vm', 'windows_vm', 'winmssql_vm', 'winfio_vm', 'winstress_vm', 'linstress_vm', 'krknhub'] # Workloads namespaces self._environment_variables_dict['workload_namespaces'] = { @@ -139,6 +139,8 @@ def __init__(self): 'windows': 'benchmark-runner', 'winmssql': 'benchmark-runner', 'winfio': 'benchmark-runner', + 'winstress': 'benchmark-runner', + 'linstress': 'benchmark-runner', 'krknhub': 'krknhub', } @@ -160,6 +162,12 @@ def __init__(self): # test_ci defaults: HAMMERDB_CONFIG="{'db_min_workers': 1, 'db_num_workers': 2, 'db_warehouses': 2, 'runtime': 1, 'rampup': 1, 'iterations': 2, 'transactions': 100000, 'database_requests_cpu': '', 'sockets': '', 'database_requests_memory': '', 'data_storage': ''}" self._environment_variables_dict['hammerdb_config'] = literal_eval(EnvironmentVariables.get_env('HAMMERDB_CONFIG', '{}')) + # Workload config override (optional), overrides template defaults for any workload. + # e.g. WORKLOAD_CONFIG="{'requests_cpu': 8, 'requests_memory': '32G', 'sockets': 64, 'cores': 1, 'threads': 1}" + # hammerdb: WORKLOAD_CONFIG="{'database_requests_cpu': 16, 'database_requests_memory': '16G', 'sockets': 16}" + # winstress/linstress: WORKLOAD_CONFIG="{'stress_cpu': 100, 'stress_memory': 50, 'stress_duration': 800, 'requests_cpu': 8, 'requests_memory': '32G', 'sockets': 64, 'cores': 1, 'threads': 1}" + self._environment_variables_dict['workload_config'] = literal_eval(EnvironmentVariables.get_env('WORKLOAD_CONFIG', '{}')) + # Set namespace based on workload. # The workload_namespaces dict is the source of truth for where each workload runs. # NAMESPACE env var is only used as a fallback for unknown workloads. @@ -373,7 +381,7 @@ def __init__(self): self._environment_variables_dict['benchmark_runner_id'] = EnvironmentVariables.get_env('BENCHMARK_RUNNER_ID', '') # Node Selector functionality - self._environment_variables_dict['pin'] = bool(self._environment_variables_dict['pin_node1']) + self._environment_variables_dict['pin'] = bool(self._environment_variables_dict['pin_node0'] or self._environment_variables_dict['pin_node1']) # if pin_node2 not exist, get pin_node1 value if self._environment_variables_dict['pin_node1'] and not self._environment_variables_dict['pin_node2']: self._environment_variables_dict['pin_node2'] = self._environment_variables_dict['pin_node1'] diff --git a/benchmark_runner/workloads/linstress_vm.py b/benchmark_runner/workloads/linstress_vm.py new file mode 100644 index 00000000..7603d8e7 --- /dev/null +++ b/benchmark_runner/workloads/linstress_vm.py @@ -0,0 +1,172 @@ + +import json +import os +import time + +from benchmark_runner.common.logger.logger_time_stamp import logger_time_stamp, logger +from benchmark_runner.common.elasticsearch.elasticsearch_exceptions import ElasticSearchDataNotUploaded +from benchmark_runner.workloads.bootstorm_vm import BootstormVM +from benchmark_runner.common.oc.oc import OC + + +class LinstressVm(BootstormVM): + """ + This class runs Linux CPU + memory stress test on Fedora VMs. + Cloud-init installs psutil and runs the stress script inside the VM. + Results are extracted via virtctl scp. + Same code path for 1 VM and N VMs. + """ + def __init__(self): + super().__init__() + self.__namespace = self._environment_variables_dict.get('namespace', 'benchmark-runner') + self.__username = self._environment_variables_dict.get('vm_user', '') or 'fedora' + self.__ssh_key_path = self._ssh_key_path + + def _get_vm_name(self, vm_num: str) -> str: + if self._scale: + return f'{self._workload_name}-{self._trunc_uuid}-{vm_num}' + return f'{self._workload_name}-{self._trunc_uuid}' + + def _get_vm_yaml(self, vm_num: str) -> str: + if self._scale: + return os.path.join(self._run_artifacts_path, f'{self._name}_{vm_num}.yaml') + return os.path.join(self._run_artifacts_path, f'{self._name}.yaml') + + def _create_vm(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + self._oc.create_async(yaml=self._get_vm_yaml(vm_num)) + self._oc.wait_for_vm_create(vm_name=vm_name) + logger.info(f'VM {vm_name} created, stress script running via cloud-init') + except Exception as err: + self.save_error_logs() + raise err + + def _collect_results(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + local_result_json = os.path.join(self._run_artifacts_path, f'{vm_name}.json') + + stress_duration = self._environment_variables_dict.get('stress_duration', 600) + logger.info(f'Waiting for stress test to complete on VM {vm_name} (stress_duration={stress_duration}s, timeout={self._timeout}s)') + + check_cmd = f'test -f /tmp/stress_report.json && echo found' + for elapsed in range(0, self._timeout, OC.DELAY): + check = self._virtctl.virtctl_ssh(vm_name=vm_name, command=check_cmd, + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username, timeout=60) + if check and 'found' in check: + break + if elapsed > 0 and elapsed % 300 == 0: + logger.info(f'Waiting for stress_report.json on VM {vm_name}... ({elapsed}s)') + time.sleep(OC.DELAY) + + if not self._virtctl._scp_file(vm_name=vm_name, + remote_path='/tmp/stress_report.json', + local_path=local_result_json, + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username): + logger.warning(f'Failed to SCP stress_report.json from VM {vm_name}') + return + + try: + with open(local_result_json, 'r') as f: + report = json.load(f) + except (json.JSONDecodeError, FileNotFoundError) as e: + logger.warning(f'Failed to parse stress report from {local_result_json}: {e}') + return + + vm_node = self._oc.get_vm_node(vm_name=vm_name) or '' + workload_name = self._environment_variables_dict.get('workload', '').replace('_', '-') + workload = self._get_workload_file_name(workload=self._get_run_artifacts_hierarchy(workload_name=workload_name, is_file=True)) + run_artifacts_url = os.path.join(self._run_artifacts_url, f'{workload}.tar.gz') + + result = { + 'cpu_total': report.get('config', {}).get('cpu_total', 0), + 'cpu_stressed': report.get('config', {}).get('cpu_stressed', 0), + 'stress_cpu_percent': report.get('config', {}).get('stress_cpu_percent', 0), + 'stress_memory_percent': report.get('config', {}).get('stress_memory_percent', 0), + 'duration_sec': report.get('config', {}).get('duration_sec', 0), + 'total_memory_gb': report.get('config', {}).get('total_memory_gb', 0), + 'total_ops': report.get('throughput', {}).get('total_ops', 0), + 'total_ops_per_sec': report.get('throughput', {}).get('total_ops_per_sec', 0), + 'avg_ops_per_cpu': report.get('throughput', {}).get('avg_ops_per_cpu', 0), + 'vm_name': vm_name, + 'node': vm_node, + 'run_artifacts_url': run_artifacts_url, + } + + with open(local_result_json, 'w') as f: + json.dump(result, f, indent=2) + logger.info(f'Stress results for {vm_name}: throughput={result["total_ops_per_sec"]:.0f} ops/sec, avg_per_cpu={result["avg_ops_per_cpu"]:.0f} ops/sec') + + except Exception as err: + self.save_error_logs() + raise err + + def _delete_vm(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + self._oc.delete_vm_sync(yaml=self._get_vm_yaml(vm_num), vm_name=vm_name) + except Exception as err: + self.save_error_logs() + raise err + + def _upload_results(self, vm_count: int): + for vm_num in range(vm_count): + vm_name = self._get_vm_name(str(vm_num)) + local_result_json = os.path.join(self._run_artifacts_path, f'{vm_name}.json') + if not os.path.exists(local_result_json): + logger.warning(f'No results file for {vm_name}') + continue + try: + with open(local_result_json, 'r') as f: + result = json.load(f) + except (json.JSONDecodeError, FileNotFoundError): + logger.warning(f'Failed to read results for {vm_name}') + continue + self._upload_to_elasticsearch(index=self._es_index, kind=self._kind, + status='complete', result=result) + self._verify_elasticsearch_data_uploaded(index=self._es_index, uuid=self._uuid) + + def run(self): + try: + logger.info(f'Running {self._workload} workload uuid={self._uuid} run_type={self._run_type} scale={self._scale}') + if self._run_type in ('test_ci', 'chaos_ci', 'func_ci'): + self._es_index = f"linstress-{self._run_type.replace('_', '-')}-results" + else: + self._es_index = 'linstress-results' + self._name = self._workload + self._workload_name = self._workload.replace('_', '-') + self._vm_name = f'{self._workload_name}-{self._trunc_uuid}' + self._kind = 'vm' + self._environment_variables_dict['kind'] = 'vm' + + self._oc.create_async(yaml=os.path.join(self._run_artifacts_path, 'namespace.yaml')) + + if self._scale: + vm_count = self._scale * len(self._scale_node_list) + threads_limit = self._threads_limit + bulk_sleep = self._bulk_sleep_time + else: + vm_count = 1 + threads_limit = 1 + bulk_sleep = 0 + + bulks = tuple(self.split_run_bulks(iterable=range(vm_count), limit=threads_limit)) + + steps = [self._create_vm, self._collect_results] + if self._delete_all: + steps.append(self._delete_vm) + self._run_parallel_phases(steps, bulks, bulk_sleep) + + if self._es_host: + self._upload_results(vm_count) + + if self._delete_all: + self._oc.delete_async(yaml=os.path.join(self._run_artifacts_path, 'namespace.yaml')) + except ElasticSearchDataNotUploaded as err: + raise err + except Exception as err: + self.save_error_logs() + raise err diff --git a/benchmark_runner/workloads/winstress_vm.py b/benchmark_runner/workloads/winstress_vm.py new file mode 100644 index 00000000..bf10a08b --- /dev/null +++ b/benchmark_runner/workloads/winstress_vm.py @@ -0,0 +1,221 @@ + +import json +import os +import time + +from benchmark_runner.common.logger.logger_time_stamp import logger_time_stamp, logger +from benchmark_runner.common.elasticsearch.elasticsearch_exceptions import ElasticSearchDataNotUploaded +from benchmark_runner.workloads.bootstorm_vm import BootstormVM +from benchmark_runner.common.oc.oc import VMStatus, OC + + +class WinstressVm(BootstormVM): + """ + This class runs Windows CPU + memory stress test on Windows VMs. + Same code path for 1 VM and N VMs. + """ + def __init__(self): + super().__init__() + if not self._windows_url: + raise ValueError('Missing Windows DV URL') + self.__namespace = self._environment_variables_dict.get('namespace', 'benchmark-runner') + self.__username = self._environment_variables_dict.get('vm_user') or 'Administrator' + self.__ssh_key_path = self._ssh_key_path + self.__remote_dir = 'C:/tools/stress' + + def _get_vm_name(self, vm_num: str) -> str: + if self._scale: + return f'{self._workload_name}-{self._trunc_uuid}-{vm_num}' + return f'{self._workload_name}-{self._trunc_uuid}' + + def _get_vm_yaml(self, vm_num: str) -> str: + if self._scale: + return os.path.join(self._run_artifacts_path, f'{self._name}_{vm_num}.yaml') + return os.path.join(self._run_artifacts_path, f'{self._name}.yaml') + + SCP_RETRIES = 3 + + def _scp_to_vm_with_verify(self, vm_name: str, local_path: str, remote_path: str): + """SCP a file to the VM with verification and retry.""" + script = os.path.basename(local_path) + for attempt in range(1, self.SCP_RETRIES + 1): + self._virtctl._scp_to_vm(vm_name=vm_name, local_path=local_path, + remote_path=remote_path, + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username) + verify = self._virtctl.virtctl_ssh(vm_name=vm_name, + command=f'powershell -Command "if (Test-Path \'{remote_path}\') {{ echo exists }}"', + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username, timeout=60) + if verify and 'exists' in verify: + logger.info(f'Copied and verified {script} on VM {vm_name}') + return + logger.warning(f'SCP verify failed for {script} on VM {vm_name} (attempt {attempt}/{self.SCP_RETRIES})') + time.sleep(5) + raise RuntimeError(f'Failed to SCP {script} to VM {vm_name} after {self.SCP_RETRIES} attempts') + + def _create_vm(self, vm_num: str): + try: + self._oc.create_async(yaml=self._get_vm_yaml(vm_num)) + self._oc.wait_for_vm_status(vm_name=self._get_vm_name(vm_num), status=VMStatus.Stopped) + except Exception as err: + self.save_error_logs() + raise err + + def _prepare_vm(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + self._virtctl.start_vm_sync(vm_name=vm_name) + if not self._virtctl._wait_for_virtctl_ssh(vm_name=vm_name, namespace=self.__namespace, + key_path=self.__ssh_key_path, username=self.__username, + timeout=OC.SHORT_TIMEOUT): + logger.warning(f'SSH never became ready on VM {vm_name}, skipping') + return + + except Exception as err: + logger.warning(f'Failed to prepare VM {self._get_vm_name(vm_num)}: {err}') + + def _run_stress(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + self._scp_to_vm_with_verify(vm_name=vm_name, + local_path=os.path.join(self._run_artifacts_path, '01_run_stress.ps1'), + remote_path=f'{self.__remote_dir}/01_run_stress.ps1') + stress_duration = self._environment_variables_dict.get('stress_duration', 600) + logger.info(f'Running 01_run_stress.ps1 on VM {vm_name} (stress_duration={stress_duration}s, timeout={self._timeout}s) ...') + self._virtctl.virtctl_ssh(vm_name=vm_name, + command=f'powershell -NoProfile -ExecutionPolicy Bypass -File {self.__remote_dir}/01_run_stress.ps1', + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username, timeout=self._timeout) + except Exception as err: + logger.warning(f'Stress test failed on VM {self._get_vm_name(vm_num)}: {err}') + + def _collect_results(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + local_result_json = os.path.join(self._run_artifacts_path, f'{vm_name}.json') + + check_cmd = f'powershell -Command "if (Test-Path \'{self.__remote_dir}/stress_report.json\') {{ echo found }}"' + for elapsed in range(0, self._timeout, OC.DELAY): + check = self._virtctl.virtctl_ssh(vm_name=vm_name, command=check_cmd, + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username, timeout=60) + if check and 'found' in check: + break + logger.info(f'Waiting for stress_report.json on VM {vm_name}... ({elapsed}s)') + time.sleep(OC.DELAY) + + if not self._virtctl._scp_file(vm_name=vm_name, + remote_path=f'{self.__remote_dir}/stress_report.json', + local_path=local_result_json, + namespace=self.__namespace, key_path=self.__ssh_key_path, + username=self.__username): + logger.warning(f'Failed to SCP stress_report.json from VM {vm_name}') + return + + try: + with open(local_result_json, 'r') as f: + report = json.load(f) + except (json.JSONDecodeError, FileNotFoundError) as e: + logger.warning(f'Failed to parse stress report from {local_result_json}: {e}') + return + + vm_node = self._oc.get_vm_node(vm_name=vm_name) or '' + workload_name = self._environment_variables_dict.get('workload', '').replace('_', '-') + workload = self._get_workload_file_name(workload=self._get_run_artifacts_hierarchy(workload_name=workload_name, is_file=True)) + run_artifacts_url = os.path.join(self._run_artifacts_url, f'{workload}.tar.gz') + + result = { + 'cpu_total': report.get('config', {}).get('cpu_total', 0), + 'cpu_stressed': report.get('config', {}).get('cpu_stressed', 0), + 'stress_cpu_percent': report.get('config', {}).get('stress_cpu_percent', 0), + 'stress_memory_percent': report.get('config', {}).get('stress_memory_percent', 0), + 'duration_sec': report.get('config', {}).get('duration_sec', 0), + 'total_memory_gb': report.get('config', {}).get('total_memory_gb', 0), + 'total_ops': report.get('throughput', {}).get('total_ops', 0), + 'total_ops_per_sec': report.get('throughput', {}).get('total_ops_per_sec', 0), + 'avg_ops_per_cpu': report.get('throughput', {}).get('avg_ops_per_cpu', 0), + 'vm_name': vm_name, + 'node': vm_node, + 'run_artifacts_url': run_artifacts_url, + } + + with open(local_result_json, 'w') as f: + json.dump(result, f, indent=2) + logger.info(f'Stress results saved to {local_result_json}') + logger.info(f' throughput: {result["total_ops_per_sec"]:.0f} ops/sec, avg_per_cpu: {result["avg_ops_per_cpu"]:.0f} ops/sec') + + except Exception as err: + self.save_error_logs() + raise err + + def _delete_vm(self, vm_num: str): + try: + vm_name = self._get_vm_name(vm_num) + self._oc.delete_vm_sync(yaml=self._get_vm_yaml(vm_num), vm_name=vm_name) + except Exception as err: + self.save_error_logs() + raise err + + def _upload_results(self, vm_count: int): + for vm_num in range(vm_count): + vm_name = self._get_vm_name(str(vm_num)) + local_result_json = os.path.join(self._run_artifacts_path, f'{vm_name}.json') + if not os.path.exists(local_result_json): + logger.warning(f'No results file for {vm_name}') + continue + try: + with open(local_result_json, 'r') as f: + result = json.load(f) + except (json.JSONDecodeError, FileNotFoundError): + logger.warning(f'Failed to read results for {vm_name}') + continue + self._upload_to_elasticsearch(index=self._es_index, kind=self._kind, + status='complete', result=result) + self._verify_elasticsearch_data_uploaded(index=self._es_index, uuid=self._uuid) + + def run(self): + try: + logger.info(f'Running {self._workload} workload uuid={self._uuid} run_type={self._run_type} scale={self._scale}') + if self._run_type in ('test_ci', 'chaos_ci', 'func_ci'): + self._es_index = f"winstress-{self._run_type.replace('_', '-')}-results" + else: + self._es_index = 'winstress-results' + self._name = self._workload + self._workload_name = self._workload.replace('_', '-') + self._vm_name = f'{self._workload_name}-{self._trunc_uuid}' + self._kind = 'vm' + self._environment_variables_dict['kind'] = 'vm' + + self._oc.create_async(yaml=os.path.join(self._run_artifacts_path, 'namespace.yaml')) + + self._oc.create_async(yaml=os.path.join(self._run_artifacts_path, 'windows_dv.yaml')) + self._oc.wait_for_dv_status(status='Succeeded') + + if self._scale: + vm_count = self._scale * len(self._scale_node_list) + threads_limit = self._threads_limit + bulk_sleep = self._bulk_sleep_time + else: + vm_count = 1 + threads_limit = 1 + bulk_sleep = 0 + + bulks = tuple(self.split_run_bulks(iterable=range(vm_count), limit=threads_limit)) + + steps = [self._create_vm, self._prepare_vm, self._run_stress, self._collect_results] + if self._delete_all: + steps.append(self._delete_vm) + self._run_parallel_phases(steps, bulks, bulk_sleep) + + if self._es_host: + self._upload_results(vm_count) + + if self._delete_all: + self._oc.delete_async(yaml=os.path.join(self._run_artifacts_path, 'windows_dv.yaml')) + self._oc.delete_async(yaml=os.path.join(self._run_artifacts_path, 'namespace.yaml')) + except ElasticSearchDataNotUploaded as err: + raise err + except Exception as err: + self.save_error_logs() + raise err diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_linstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/chaos_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_linstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/func_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml new file mode 100644 index 00000000..32fe824c --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 8 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 8Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml new file mode 100644 index 00000000..32fe824c --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 8 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 8Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_linstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml new file mode 100644 index 00000000..c53ad146 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 64 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 8 + memory: 32G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml new file mode 100644 index 00000000..c53ad146 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/perf_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 64 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 8 + memory: 32G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_linstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_False/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/release_winstress_vm_ODF_PVC_True/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml new file mode 100644 index 00000000..bc3755a1 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/linstress_vm.yaml @@ -0,0 +1,180 @@ +apiVersion: v1 +kind: Secret +metadata: + name: linstress-cloudinit-deadbeef + namespace: benchmark-runner +stringData: + userdata: | + #cloud-config + user: fedora + password: fedora + chpasswd: { expire: False } + ssh_pwauth: true + packages: + - python3-psutil + write_files: + - path: /tmp/stress.py + permissions: '0755' + content: | + import multiprocessing, time, psutil, json + + def burn_cpu(d, result_dict, idx): + ops = 0 + start = time.time() + end = start + d + while time.time() < end: + _ = 2**32 + ops += 1 + elapsed = time.time() - start + result_dict[idx] = {'ops': ops, 'elapsed': elapsed, 'ops_per_sec': ops / elapsed} + + def burn_memory(target_percent, duration): + total = psutil.virtual_memory().total + target_bytes = int(total * target_percent / 100) + current_used = psutil.virtual_memory().used + alloc_bytes = target_bytes - current_used + if alloc_bytes <= 0: + print(f'Memory already at {psutil.virtual_memory().percent}%, skipping') + return + chunk_size = 100 * 1024 * 1024 + blocks = [] + allocated = 0 + while allocated < alloc_bytes: + size = min(chunk_size, alloc_bytes - allocated) + blocks.append(bytearray(size)) + allocated += size + print(f'Allocated {allocated // (1024*1024)}MB / {alloc_bytes // (1024*1024)}MB ({psutil.virtual_memory().percent}%)') + print(f'Memory at {psutil.virtual_memory().percent}%, holding for {duration}s...') + time.sleep(duration) + + if __name__ == '__main__': + cpu_total = multiprocessing.cpu_count() + cpu_count = max(1, int(cpu_total * 100 / 100)) + duration = 600 + mem_target = 50 + + print(f'CPU count: {cpu_total}') + print(f'Stressing {cpu_count} CPUs (100%) and {mem_target}% memory for {duration}s') + print(f'Total memory: {psutil.virtual_memory().total // (1024**3)}GB') + print(f'Memory before: {psutil.virtual_memory().percent}%') + print(f'CPU before: {psutil.cpu_percent(interval=1)}%') + + mem_proc = None + if mem_target > 0: + mem_proc = multiprocessing.Process(target=burn_memory, args=(mem_target, duration)) + mem_proc.start() + time.sleep(5) + + manager = multiprocessing.Manager() + result_dict = manager.dict() + + cpu_procs = [multiprocessing.Process(target=burn_cpu, args=(duration, result_dict, i)) for i in range(cpu_count)] + [p.start() for p in cpu_procs] + + intervals = max(1, duration // 30) + samples = [] + for i in range(intervals): + time.sleep(30) + mem = psutil.virtual_memory() + cpu_pct = psutil.cpu_percent(interval=1) + sample = { + 'time_sec': (i+1)*30, + 'cpu_percent': cpu_pct, + 'mem_percent': mem.percent, + 'mem_used_gb': round(mem.used / (1024**3), 1), + 'mem_total_gb': round(mem.total / (1024**3), 1) + } + samples.append(sample) + print(f"At {sample['time_sec']}s: CPU={cpu_pct}% MEM={mem.percent}% ({sample['mem_used_gb']}GB/{sample['mem_total_gb']}GB)") + + [p.join() for p in cpu_procs] + if mem_proc: + mem_proc.join() + + total_ops = sum(r['ops'] for r in result_dict.values()) + total_ops_per_sec = sum(r['ops_per_sec'] for r in result_dict.values()) + avg_ops_per_sec = total_ops_per_sec / cpu_count if cpu_count > 0 else 0 + + print(f'CPU after: {psutil.cpu_percent(interval=1)}%') + print(f'Memory after: {psutil.virtual_memory().percent}%') + print(f'Total operations: {total_ops:,}') + print(f'Total throughput: {total_ops_per_sec:,.0f} ops/sec') + print(f'Avg per CPU: {avg_ops_per_sec:,.0f} ops/sec') + + report = { + 'config': { + 'cpu_total': cpu_total, + 'cpu_stressed': cpu_count, + 'stress_cpu_percent': 100, + 'stress_memory_percent': mem_target, + 'duration_sec': duration, + 'total_memory_gb': round(psutil.virtual_memory().total / (1024**3), 1) + }, + 'throughput': { + 'total_ops': total_ops, + 'total_ops_per_sec': round(total_ops_per_sec, 2), + 'avg_ops_per_cpu': round(avg_ops_per_sec, 2), + 'per_cpu': [{'cpu': i, 'ops': r['ops'], 'ops_per_sec': round(r['ops_per_sec'], 2)} for i, r in sorted(result_dict.items())] + }, + 'samples': samples + } + + with open('/tmp/stress_report.json', 'w') as f: + json.dump(report, f, indent=2) + print('Report saved to /tmp/stress_report.json') + print('Done') + runcmd: + - export HOME=/root + - dnf install -y python3-psutil || true + - python3 /tmp/stress.py +--- +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: linstress-vm-deadbeef + labels: + kubevirt.io/vm: linstress-vm-deadbeef + namespace: benchmark-runner +spec: + running: true + template: + metadata: + labels: + kubevirt.io/vm: linstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + cpu: + sockets: 2 + cores: 1 + threads: 1 + devices: + disks: + - disk: + bus: virtio + name: containerdisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + masquerade: {} + networkInterfaceMultiqueue: true + machine: + type: "" + resources: + requests: + memory: 1Gi + terminationGracePeriodSeconds: 180 + networks: + - name: default + pod: {} + volumes: + - name: containerdisk + containerDisk: + image: quay.io/benchmark-runner/fedora-container-disk:43 + - name: cloudinitdisk + cloudInitNoCloud: + secretRef: + name: linstress-cloudinit-deadbeef diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_linstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_False/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/namespace.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/namespace.yaml new file mode 100644 index 00000000..d3f55eb0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: benchmark-runner + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml new file mode 100644 index 00000000..78697a9d --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/windows_dv.yaml @@ -0,0 +1,19 @@ +apiVersion: cdi.kubevirt.io/v1beta1 +kind: DataVolume +metadata: + annotations: + cdi.kubevirt.io/storage.deleteAfterCompletion: "false" + name: windows-clone-dv + namespace: benchmark-runner +spec: + source: + http: + url: http://localhost:8083/winmssql2022.qcow2 + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization diff --git a/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml new file mode 100644 index 00000000..aafe45d0 --- /dev/null +++ b/tests/unittest/benchmark_runner/common/template_operations/golden_files/test_ci_winstress_vm_ODF_PVC_True/winstress_vm.yaml @@ -0,0 +1,120 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: winstress-vm-deadbeef + labels: + kubevirt.io/vm: winstress-vm-deadbeef + namespace: benchmark-runner +spec: + runStrategy: Halted + template: + metadata: + labels: + kubevirt.io/vm: winstress-vm-deadbeef + spec: + nodeSelector: + kubernetes.io/hostname: 'None' + domain: + clock: + timer: + hpet: + present: false + hyperv: {} + pit: + tickPolicy: delay + rtc: + tickPolicy: catchup + utc: {} + cpu: + model: host-passthrough + cores: 1 + sockets: 2 + threads: 1 + devices: + autoattachMemBalloon: false + blockMultiQueue: false + disks: + - name: winstress-vm-root-disk-deadbeef + disk: + bus: virtio + bootOrder: 1 + - disk: + bus: virtio + name: cloudinitdisk + inputs: + - bus: virtio + name: tablet + type: tablet + tpm: {} + interfaces: + - name: nic-0 + masquerade: {} + model: virtio + networkInterfaceMultiqueue: true + features: + acpi: {} + apic: {} + hyperv: + ipi: {} + synic: {} + synictimer: + direct: {} + spinlocks: + spinlocks: 8191 + evmcs: + enabled: true + relaxed: {} + vpindex: {} + runtime: {} + tlbflush: {} + frequencies: {} + vapic: {} + smm: {} + firmware: + bootloader: + efi: + secureBoot: false + ioThreads: + supplementalPoolThreadCount: 8 + ioThreadsPolicy: supplementalPool + machine: + type: q35 + resources: + requests: + cpu: 1 + memory: 4G + evictionStrategy: LiveMigrate + networks: + - name: nic-0 + pod: {} + terminationGracePeriodSeconds: 180 + volumes: + - dataVolume: + name: winstress-vm-root-disk-deadbeef + name: winstress-vm-root-disk-deadbeef + - cloudInitNoCloud: + userData: | + #cloud-config + runcmd: + - powershell -Command "New-Item -ItemType Directory -Force -Path 'C:\ProgramData\ssh' | Out-Null; Set-Content -Path 'C:\ProgramData\ssh\administrators_authorized_keys' -Value '' -Encoding ascii -Force; icacls 'C:\ProgramData\ssh\administrators_authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + - cmd /c "mkdir C:\Users\Administrator\.ssh >nul 2>&1 & icacls C:\Users\Administrator\.ssh /grant Everyone:F >nul 2>&1 & takeown /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1 & icacls C:\Users\Administrator\.ssh\authorized_keys /grant Everyone:F >nul 2>&1 & del /f C:\Users\Administrator\.ssh\authorized_keys >nul 2>&1" + - powershell -Command "Copy-Item 'C:\ProgramData\ssh\administrators_authorized_keys' 'C:\Users\Administrator\.ssh\authorized_keys' -Force; icacls 'C:\Users\Administrator\.ssh\authorized_keys' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'; icacls 'C:\Users\Administrator\.ssh' /inheritance:r /grant 'Administrators:F' /grant 'SYSTEM:F'" + name: cloudinitdisk + dataVolumeTemplates: + - metadata: + annotations: + descheduler.alpha.kubernetes.io/evict: "true" + name: winstress-vm-root-disk-deadbeef + spec: + pvc: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 76Gi + volumeMode: Block + storageClassName: ocs-storagecluster-ceph-rbd-virtualization + source: + pvc: + namespace: benchmark-runner + name: windows-clone-dv