Path: blob/master/examples/plugintest/Plugins/AndroidPlugin.py
908 views
import csv1import os2import os.path as op3import threading4import time5import timeit6from collections import OrderedDict78from .Profiler import Profiler9from functools import reduce101112class ConfigError(Exception):13pass141516class AndroidPlugin(Profiler):17def __init__(self, config, paths):18super(AndroidPlugin, self).__init__(config, paths)19self.output_dir = ''20self.paths = paths21self.profile = False22available_data_points = ['cpu', 'mem']23self.interval = float(self.is_integer(config.get('sample_interval', 0))) / 100024self.data_points = config['data_points']25invalid_data_points = [dp for dp in config['data_points'] if dp not in set(available_data_points)]26if invalid_data_points:27self.logger.warning('Invalid data points in config: {}'.format(invalid_data_points))28self.data_points = [dp for dp in config['data_points'] if dp in set(available_data_points)]29self.data = [['datetime'] + self.data_points]3031@staticmethod32def get_cpu_usage(device):33"""Get CPU usage in percentage"""34# return device.shell('dumpsys cpuinfo | grep TOTAL | cut -d" " -f1').strip()[:-1]35shell_result = device.shell('dumpsys cpuinfo | grep TOTAL')36shell_splitted = shell_result.split('%')[0]37if '.-' in shell_splitted:38shell_splitted = shell_splitted.replace('.-', '.')39return shell_splitted40# return device.shell('dumpsys cpuinfo | grep TOTAL').split('%')[0]4142@staticmethod43def get_mem_usage(device, app):44"""Get memory usage in KB for app, if app is None system usage is used"""45if not app:46# return device.shell('dumpsys meminfo | grep Used | cut -d" " -f5').strip()[1:-1]47# return device.shell('dumpsys meminfo | grep Used').split()[2].strip()[1:-1].replace(",", ".")48return device.shell('dumpsys meminfo | grep Used').translate(str.maketrans('','', '(kB,K')).split()[2]49else:50result = device.shell('dumpsys meminfo {} | grep TOTAL'.format(app))51if result == '':52result = device.shell('dumpsys meminfo {}'.format(app))53if 'No process found' in result:54raise Exception('Android Profiler: {}'.format(result))55return ' '.join(result.strip().split()).split()[1]5657def start_profiling(self, device, **kwargs):58self.profile = True59app = kwargs.get('app', None)60self.get_data(device, app)6162def get_data(self, device, app):63"""Runs the profiling methods every self.interval seconds in a separate thread"""64start = timeit.default_timer()65device_time = device.shell('date -u')66row = [device_time]67if 'cpu' in self.data_points:68row.append(self.get_cpu_usage(device))69if 'mem' in self.data_points:70row.append(self.get_mem_usage(device, app))71self.data.append(row)72end = timeit.default_timer()73# timer results could be negative74interval = max(float(0), self.interval - max(0, int(end - start)))75if self.profile:76threading.Timer(interval, self.get_data, args=(device, app)).start()7778def stop_profiling(self, device, **kwargs):79self.profile = False8081def collect_results(self, device):82filename = '{}_{}.csv'.format(device.id, time.strftime('%Y.%m.%d_%H%M%S'))83with open(op.join(self.output_dir, filename), 'w+') as f:84writer = csv.writer(f)85for row in self.data:86writer.writerow(row)8788def set_output(self, output_dir):89self.output_dir = output_dir9091def dependencies(self):92return []9394def load(self, device):95return9697def unload(self, device):98return99100def aggregate_subject(self):101filename = os.path.join(self.output_dir, 'Aggregated.csv')102subject_rows = list()103subject_rows.append(self.aggregate_android_subject(self.output_dir))104self.write_to_file(filename, subject_rows)105106def aggregate_end(self, data_dir, output_file):107rows = self.aggregate_final(data_dir)108self.write_to_file(output_file, rows)109110@staticmethod111def aggregate_android_subject(logs_dir):112def add_row(accum, new):113row = {k: v + float(new[k]) for k, v in list(accum.items()) if k not in ['Component', 'count']}114count = accum['count'] + 1115return dict(row, **{'count': count})116117runs = []118for run_file in [f for f in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, f))]:119with open(os.path.join(logs_dir, run_file), 'r') as run:120reader = csv.DictReader(run)121init = dict({fn: 0 for fn in reader.fieldnames if fn != 'datetime'}, **{'count': 0})122run_total = reduce(add_row, reader, init)123runs.append({k: v / run_total['count'] for k, v in list(run_total.items()) if k != 'count'})124runs_total = reduce(lambda x, y: {k: v + y[k] for k, v in list(x.items())}, runs)125return OrderedDict(126sorted(list({'android_' + k: v / len(runs) for k, v in list(runs_total.items())}.items()), key=lambda x: x[0]))127128def aggregate_final(self, data_dir):129rows = []130for device in self.list_subdir(data_dir):131row = OrderedDict({'device': device})132device_dir = os.path.join(data_dir, device)133for subject in self.list_subdir(device_dir):134row.update({'subject': subject})135subject_dir = os.path.join(device_dir, subject)136if os.path.isdir(os.path.join(subject_dir, 'AndroidPlugin')):137row.update(self.aggregate_android_final(os.path.join(subject_dir, 'AndroidPlugin')))138rows.append(row.copy())139else:140for browser in self.list_subdir(subject_dir):141row.update({'browser': browser})142browser_dir = os.path.join(subject_dir, browser)143if os.path.isdir(os.path.join(browser_dir, 'AndroidPlugin')):144row.update(self.aggregate_android_final(os.path.join(browser_dir, 'AndroidPlugin')))145rows.append(row.copy())146return rows147148@staticmethod149def aggregate_android_final(logs_dir):150for aggregated_file in [f for f in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, f))]:151if aggregated_file == "Aggregated.csv":152with open(os.path.join(logs_dir, aggregated_file), 'r') as aggregated:153reader = csv.DictReader(aggregated)154row_dict = OrderedDict()155for row in reader:156for f in reader.fieldnames:157row_dict.update({f: row[f]})158return OrderedDict(row_dict)159160@staticmethod161def list_subdir(a_dir):162"""List immediate subdirectories of a_dir"""163# https://stackoverflow.com/a/800201164return [name for name in os.listdir(a_dir)165if os.path.isdir(os.path.join(a_dir, name))]166167@staticmethod168def write_to_file(filename, rows):169with open(filename, 'w') as f:170writer = csv.DictWriter(f, list(rows[0].keys()))171writer.writeheader()172writer.writerows(rows)173174@staticmethod175def is_integer(number, minimum=0):176if not isinstance(number, int):177raise ConfigError('%s is not an integer' % number)178if number < minimum:179raise ConfigError('%s should be equal or larger than %i' % (number, minimum))180return number181182183