Path: blob/master/AndroidRunner/Plugins/trepn/Trepn.py
621 views
import csv1import errno2import json3import os4import os.path as op5import time6from collections import OrderedDict78import lxml.etree as et9from lxml.etree import ElementTree1011from AndroidRunner.Plugins.Profiler import Profiler12from functools import reduce13from AndroidRunner import util141516class Trepn(Profiler):17DEVICE_PATH = '/sdcard/trepn/'1819def dependencies(self):20return ['com.quicinc.trepn']2122def __init__(self, config, paths):23super(Trepn, self).__init__(config, paths)24self.output_dir = ''25self.paths = paths26self.pref_dir = None27self.remote_pref_dir = op.join(Trepn.DEVICE_PATH, 'saved_preferences/')28self.data_points = []29self.build_preferences(config)3031def override_preferences(self, params: OrderedDict, preferences_file: ElementTree) -> ElementTree:32"""Read the preferences XML file and override configurations provided by the user"""33# Parse XML preferences only if the user is overriding a preference34if 'preferences' not in params:35return preferences_file3637# Parse all preferences in the XML file38for xml_preference in preferences_file.getroot().iter():39# Verifies if this XML contains a preference40xml_preference_name = xml_preference.get('name')41if xml_preference_name is not None:42# Verifies if the user configuration file is overriding this preference43xml_preference_override_name = xml_preference.get('name').rsplit('.', 1)[1]44if xml_preference_override_name in params['preferences']:45# Replace the default preference with the configuration provided by the user46preference_value = str(params['preferences'][xml_preference_override_name])47if xml_preference.tag == 'string':48xml_preference.text = preference_value49else:50xml_preference.set('value', preference_value)51return preferences_file5253def build_preferences(self, params):54"""Build the XML files to setup Trepn and the data points"""55current_dir = op.dirname(op.realpath(__file__))56# lxml is not the most secure parser, it is up to the user for valid configurations57# https://docs.python.org/2/library/xml.html#xml-vulnerabilities58self.pref_dir = op.join(self.paths['OUTPUT_DIR'], 'trepn.pref/')59util.makedirs(self.pref_dir)6061preferences_file = et.parse(op.join(current_dir, 'preferences.xml'))62preferences_file = self.override_preferences(params, preferences_file)63preferences_file.write(op.join(self.pref_dir, 'com.quicinc.trepn_preferences.xml'), encoding='utf-8',64xml_declaration=True, standalone=True)65datapoints_file = et.parse(op.join(current_dir, 'data_points.xml'))66dp_root = datapoints_file.getroot()67data_points_dict = util.load_json(op.join(current_dir, 'data_points.json'))68for dp in params['data_points']:69dp = str(data_points_dict[dp])70self.data_points.append(dp)71dp_root.append(et.Element('int', {'name': dp, 'value': dp}))72datapoints_file.write(op.join(self.pref_dir, 'com.quicinc.preferences.saved_data_points.xml'), encoding='utf-8',73xml_declaration=True, standalone=True)7475def load(self, device):76device.push(self.pref_dir, self.remote_pref_dir)77# There is no way to know if the following succeeded78device.launch_package('com.quicinc.trepn')79time.sleep(5) # launch_package returns instantly80# Trepn needs to be started for this to work81device.shell('am broadcast -a com.quicinc.trepn.load_preferences '82'-e com.quicinc.trepn.load_preferences_file "%s"'83% op.join(self.remote_pref_dir, 'trepn.pref'))84time.sleep(1) # am broadcast returns instantly85device.force_stop('com.quicinc.trepn')86time.sleep(2) # am force-stop returns instantly87device.shell('am startservice com.quicinc.trepn/.TrepnService')8889def start_profiling(self, device, **kwargs):90device.shell('am broadcast -a com.quicinc.trepn.start_profiling')9192def stop_profiling(self, device, **kwargs):93device.shell('am broadcast -a com.quicinc.trepn.stop_profiling')9495def file_exists_and_not_empty(self, device, path, csv_filename):96""" Checks whether the file <csv_filename> exists on the device <device> in the folder <path>97and that the file is not empty.9899100Parameters101----------102device : Device103Device on which we want to check whether the file exists.104path : string, bytes105A string or bytes object representing a folder on the device.106csv_filename : string107The file108109Returns110-------111bool112Whether the file exists and is not empty on the device.113"""114ls = device.shell(f"ls {path}")115cat = device.shell(f"cat {os.path.join(path, csv_filename)}")116117return (csv_filename in ls) and bool(cat)118119def collect_results(self, device):120# Gives the latest result121db = device.shell(r'ls %s | grep "\.db$"' % Trepn.DEVICE_PATH).strip().splitlines()122newest_db = db[len(db) - 1]123csv_filename = '%s_%s.csv' % (util.slugify(device.id), op.splitext(newest_db)[0])124if newest_db:125device.shell('am broadcast -a com.quicinc.trepn.export_to_csv '126'-e com.quicinc.trepn.export_db_input_file "%s" '127'-e com.quicinc.trepn.export_csv_output_file "%s"' % (newest_db, csv_filename))128129# adb returns instantly, while the command takes time so we wait till Trepn converted the databsae to a130# csv file on the mobile device.131util.wait_until(self.file_exists_and_not_empty, 5, 1, device, Trepn.DEVICE_PATH, csv_filename)132133device.pull(op.join(Trepn.DEVICE_PATH, csv_filename), self.output_dir)134135# adb returns instantly, while the command takes time so we wait till the files are transferred from the136# device to the host.137util.wait_until(os.path.exists, 5, 1, op.join(self.output_dir, csv_filename))138139# Delete the originals140device.shell('rm %s' % op.join(Trepn.DEVICE_PATH, newest_db))141device.shell('rm %s' % op.join(Trepn.DEVICE_PATH, csv_filename))142self.filter_results(op.join(self.output_dir, csv_filename))143144@staticmethod145def read_csv(filename):146result = []147with open(filename, mode='r') as csv_file:148csv_reader = csv.reader(csv_file)149for row in csv_reader:150result.append(row)151return result152153def filter_results(self, filename):154file_content = self.read_csv(filename)[3:]155split_line = file_content.index(['System Statistics:'])156data = file_content[:split_line - 2]157system_statistics = file_content[split_line + 2:]158system_statistics_dict = {str(statistic[0]): statistic[1] for statistic in system_statistics if159not statistic == []}160wanted_statistics = [system_statistics_dict[data_point] for data_point in self.data_points]161filtered_data = self.filter_data(wanted_statistics, data)162self.write_list_to_file(filename, filtered_data)163164@staticmethod165def write_list_to_file(filename, rows):166with open(filename, 'w') as f:167writer = csv.writer(f)168writer.writerows(rows)169170def filter_data(self, wanted_statistics, data):171wanted_columns = self.get_wanted_columns(wanted_statistics, data[0])172filtered_data = self.filter_columns(wanted_columns, data)173return filtered_data174175@staticmethod176def filter_columns(wanted_columns, data):177remaining_data = []178for row in data:179new_row = [row[column] for column in wanted_columns]180remaining_data.append(new_row)181return remaining_data182183@staticmethod184def get_wanted_columns(statistics, header_row):185wanted_columns = []186last_time = None187for statistic in statistics:188last_time_added = False189for i in range(len(header_row)):190header_item = header_row[i].split('[')[0].strip()191if header_item == 'Time':192last_time = i193if header_item == statistic:194if not last_time_added:195wanted_columns.append(last_time)196last_time_added = True197wanted_columns.append(i)198wanted_columns.sort()199return wanted_columns200201def unload(self, device):202device.shell('am stopservice com.quicinc.trepn/.TrepnService')203device.shell('rm -r %s' % op.join(self.remote_pref_dir, 'trepn.pref'))204205def set_output(self, output_dir):206self.output_dir = output_dir207208def aggregate_subject(self):209filename = os.path.join(self.output_dir, 'Aggregated.csv')210subject_rows = list()211subject_rows.append(self.aggregate_trepn_subject(self.output_dir))212util.write_to_file(filename, subject_rows)213214def aggregate_end(self, data_dir, output_file):215rows = self.aggregate_final(data_dir)216util.write_to_file(output_file, rows)217218def aggregate_trepn_subject(self, logs_dir):219def add_row(accum, new):220row = {key: value + float(new[key]) for key, value in list(accum.items()) if221key not in ['Component', 'count']}222count = accum['count'] + 1223return dict(row, **{'count': count})224225runs = []226for run_file in [f for f in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, f))]:227with open(os.path.join(logs_dir, run_file), 'r') as run:228run_dict = {}229reader = csv.DictReader(run)230column_readers = self.split_reader(reader)231for k, v in list(column_readers.items()):232init = dict({k: 0}, **{'count': 0})233run_total = reduce(add_row, v, init)234if not run_total['count'] == 0:235run_dict[k] = run_total[k] / run_total['count']236runs.append(run_dict)237init = dict({fn: 0 for fn in list(runs[0].keys())}, **{'count': 0})238runs_total = reduce(add_row, runs, init)239return OrderedDict(240sorted(list({k: v / len(runs) for k, v in list(runs_total.items()) if not k == 'count'}.items()),241key=lambda x: x[0]))242243@staticmethod244def split_reader(reader):245column_dicts = {fn: [] for fn in reader.fieldnames if not fn.split('[')[0].strip() == 'Time'}246for row in reader:247for k, v in list(row.items()):248if not k.split('[')[0].strip() == 'Time' and not v == '':249column_dicts[k].append({k: v})250return column_dicts251252def aggregate_final(self, data_dir):253rows = []254for device in util.list_subdir(data_dir):255row = OrderedDict({'device': device})256device_dir = os.path.join(data_dir, device)257for subject in util.list_subdir(device_dir):258row.update({'subject': subject})259subject_dir = os.path.join(device_dir, subject)260if os.path.isdir(os.path.join(subject_dir, 'trepn')):261row.update(self.aggregate_trepn_final(os.path.join(subject_dir, 'trepn')))262rows.append(row.copy())263else:264for browser in util.list_subdir(subject_dir):265row.update({'browser': browser})266browser_dir = os.path.join(subject_dir, browser)267if os.path.isdir(os.path.join(browser_dir, 'trepn')):268row.update(self.aggregate_trepn_final(os.path.join(browser_dir, 'trepn')))269rows.append(row.copy())270return rows271272@staticmethod273def aggregate_trepn_final(logs_dir):274for aggregated_file in [f for f in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, f))]:275if aggregated_file == "Aggregated.csv":276with open(os.path.join(logs_dir, aggregated_file), 'r') as aggregated:277reader = csv.DictReader(aggregated)278row_dict = OrderedDict()279for row in reader:280for f in reader.fieldnames:281row_dict.update({f: row[f]})282return OrderedDict(row_dict)283284285