blob: 1bdd7959a0293e1ac35babb39574e6761963b38d [file] [log] [blame]
#!/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()