| #!/usr/bin/env python3 |
| # |
| # Copyright 2024 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the', help="License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an', help="AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| import argparse |
| import csv |
| import datetime |
| import os |
| import platform |
| import subprocess |
| import time |
| import psutil |
| import requests |
| |
| PLATFORM_SYSTEM = platform.system().lower() |
| QEMU_ARCH_MAP = {'arm64': 'aarch64', 'AMD64': 'x86_64'} |
| PLATFORM_MACHINE = QEMU_ARCH_MAP.get(platform.machine(), platform.machine()) |
| TEST_DURATION = 300 |
| CURRENT_PATH = os.path.dirname(os.path.abspath(__file__)) |
| EXE_SUFFIX = '.exe' if PLATFORM_SYSTEM == 'windows' else '' |
| NETSIMD_BINARY = f'netsimd{EXE_SUFFIX}' |
| NETSIM_FRONTEND_HTTP_URI = 'http://localhost:7681' |
| EMULATOR_BINARY = f'emulator{EXE_SUFFIX}' |
| QEMU_SYSTEM_BINARY = f'qemu-system-{PLATFORM_MACHINE}{EXE_SUFFIX}' |
| |
| |
| def _get_cpu_usage(): |
| """Retrieves CPU and memory usage for netsimd and qemu.""" |
| netsimd_usage, qemu_usage = [], [] |
| |
| for process in psutil.process_iter( |
| ['name', 'cpu_percent', 'num_threads', 'memory_info'] |
| ): |
| process_name = process.info['name'] |
| if process_name == NETSIMD_BINARY: |
| netsimd_usage.append(process.info) |
| elif process_name == QEMU_SYSTEM_BINARY: |
| qemu_usage.append(process.info) |
| |
| def _validate_and_extract(process_list, process_name): |
| if len(process_list) > 1: |
| raise LookupError(f'Multiple {process_name} processes found') |
| if not process_list: |
| raise LookupError(f'Process {process_name} not found') |
| return process_list[0] |
| |
| netsimd_info = _validate_and_extract(netsimd_usage, NETSIMD_BINARY) |
| qemu_info = _validate_and_extract(qemu_usage, QEMU_SYSTEM_BINARY) |
| |
| return ( |
| netsimd_info['cpu_percent'], |
| qemu_info['cpu_percent'], |
| netsimd_info['num_threads'], |
| netsimd_info['memory_info'].rss / 1024 / 1024, |
| ) |
| |
| |
| def _process_usage_iteration(writer, avd, netsim_wifi, iteration): |
| """Collects and writes usage data for a single iteration.""" |
| try: |
| netsimd_cpu, qemu_cpu, netsimd_threads, netsimd_mem = _get_cpu_usage() |
| if iteration == 0: |
| time.sleep(0.1) |
| return |
| data = [time.time(), netsimd_cpu, qemu_cpu, netsimd_threads, netsimd_mem] |
| if netsim_wifi: |
| data.extend(_get_wifi_packet_count(avd)) |
| print(f'Got {data}') |
| writer.writerow(data) |
| except LookupError as e: |
| print(e) |
| time.sleep(1) |
| time.sleep(1) |
| |
| |
| def _trace_usage(filename: str, avd: str, netsim_wifi: bool): |
| """Traces usage data and writes to a CSV file.""" |
| with open(filename, 'w', newline='') as csvfile: |
| writer = csv.writer(csvfile) |
| headers = [ |
| 'Timestamp', |
| NETSIMD_BINARY, |
| QEMU_SYSTEM_BINARY, |
| 'NetSimThreads', |
| 'NetSimMemUsage(MB)', |
| ] |
| if netsim_wifi: |
| headers.extend(['txCount', 'rxCount']) |
| writer.writerow(headers) |
| for i in range(TEST_DURATION): |
| _process_usage_iteration(writer, avd, netsim_wifi, i) |
| |
| |
| def _launch_emulator(cmd): |
| """Utility function for launching Emulator""" |
| if PLATFORM_SYSTEM == 'windows': |
| return subprocess.Popen( |
| cmd, |
| creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, |
| ) |
| else: |
| return subprocess.Popen(cmd) |
| |
| |
| def _terminate_emulator(process): |
| """Utility function for terminating Emulator""" |
| try: |
| if PLATFORM_SYSTEM == 'windows': |
| import signal |
| |
| process.send_signal(signal.CTRL_BREAK_EVENT) |
| process.wait() |
| else: |
| process.terminate() |
| except OSError: |
| print('Process already termianted') |
| |
| |
| def _get_wifi_packet_count(avd: str): |
| """Utility function for getting WiFi Packet Counts. |
| |
| Returns (txCount, rxCount) |
| """ |
| avd = avd.replace('_', ' ') |
| try: |
| response = requests.get(NETSIM_FRONTEND_HTTP_URI + '/v1/devices') |
| response.raise_for_status() |
| for device in response.json()['devices']: |
| if device['name'] == avd: |
| for chip in device['chips']: |
| if chip['kind'] == 'WIFI': |
| return (chip['wifi']['txCount'], chip['wifi']['rxCount']) |
| except requests.exceptions.RequestException as e: |
| print(f'Request Error: {e}') |
| except KeyError as e: |
| print(f'KeyError: {e}') |
| except IndexError as e: |
| print(f'IndexError: {e}') |
| return (0, 0) |
| |
| |
| def _collect_cpu_usage(avd: str, netsim_wifi: bool): |
| """Utility function for running the CPU usage collection session""" |
| # Setup cmd and filename to trace |
| time_now = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S') |
| cmd = [f'{CURRENT_PATH}/{EMULATOR_BINARY}', '-avd', avd, '-wipe-data'] |
| filename = ( |
| f'netsimd_cpu_usage_{PLATFORM_SYSTEM}_{PLATFORM_MACHINE}_{time_now}.csv' |
| ) |
| if netsim_wifi: |
| cmd.extend(['-feature', 'WiFiPacketStream']) |
| filename = f'netsimd_cpu_usage_{PLATFORM_SYSTEM}_{PLATFORM_MACHINE}_WiFiPacketStream_{time_now}.csv' |
| |
| # Launch emulator |
| process = _launch_emulator(cmd) |
| |
| # Enough time for Emulator to boot |
| time.sleep(10) |
| |
| # Trace CPU usage |
| _trace_usage(filename, avd, netsim_wifi) |
| |
| # Terminate Emulator Process |
| _terminate_emulator(process) |
| |
| |
| def main(): |
| # Check if ANDROID_SDK_ROOT env is defined |
| if 'ANDROID_SDK_ROOT' not in os.environ: |
| print('Please set ANDROID_SDK_ROOT') |
| return |
| |
| # Check if Emulator Binary exists |
| emulator_path = f'{CURRENT_PATH}/{EMULATOR_BINARY}' |
| if not os.path.isfile(emulator_path): |
| print( |
| f"Can't find {emulator_path}. Please place the file with the binaries" |
| ' before executing.' |
| ) |
| return |
| |
| # Set avd provided by the user |
| parser = argparse.ArgumentParser() |
| parser.add_argument('avd', help='The AVD to use', type=str) |
| args = parser.parse_args() |
| |
| # Collect CPU usage without netsim WiFi |
| _collect_cpu_usage(args.avd, False) |
| |
| # Enough time for Emulator to terminate |
| time.sleep(10) |
| |
| # Collect CPU usage with netsim WiFi |
| _collect_cpu_usage(args.avd, True) |
| |
| print('CPU Usage Completed!') |
| |
| |
| if __name__ == '__main__': |
| main() |