diff --git a/.gitignore b/.gitignore index 4b2db0f..5f5006e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ sftp-config.json +*.pyc +models/ diff --git a/OffloadingDemo/mobile/src/main/java/edu/ucsb/cs/sandlab/offloadingdemo/MainActivity.java b/OffloadingDemo/mobile/src/main/java/edu/ucsb/cs/sandlab/offloadingdemo/MainActivity.java index bcaad9c..5ed640e 100755 --- a/OffloadingDemo/mobile/src/main/java/edu/ucsb/cs/sandlab/offloadingdemo/MainActivity.java +++ b/OffloadingDemo/mobile/src/main/java/edu/ucsb/cs/sandlab/offloadingdemo/MainActivity.java @@ -64,7 +64,7 @@ class MainActivity extends Activity { protected static int UDPfinishTime = 0; protected static double reportedFinishTime = 0.0; protected static int repeatCounts = 3; - protected static int bytes2send = 10 * Utilities.oneMB; // default 10MB + protected static int bytes2send = 100 * Utilities.oneMB; // default 100MB protected static int currentBandwidth = -1; // bps, default is -1, indicating unlimited protected static TextView txt_results; protected static Handler myHandler; diff --git a/energy_model/modules/TerminalColors.py b/energy_model/modules/TerminalColors.py new file mode 100644 index 0000000..d425a78 --- /dev/null +++ b/energy_model/modules/TerminalColors.py @@ -0,0 +1,44 @@ +''' +Created by Yanzi @ 06/16/2016 +Last updated by Yanzi @ 08/18/2016 +color module: terminal text add colors +''' + + +class TerminalColors: + ''' + Currently support purple, cyan, darkcyan, blue, green, yellow, red + ''' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m' + PURPLE = '\033[95m' + CYAN = '\033[96m' + DARKCYAN = '\033[36m' + BLUE = '\033[94m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + + +def colorString(myString, bold=True, underline=False, color='red'): + ''' + Set color of the string, including bold and underline + @ param myString: the string + @ param bold: flag to set if we want to bold the text + @ param underline: flag to set if we want to draw underline + @ param color: specify a color name, e.g. 'red' or 'green' + (may not show depending on terminal setups) + @ return new string wrapped with terminal color indicators + ''' + tmp = myString + if bold: + tmp = '{1}{0}{2}'.format( + tmp, TerminalColors.BOLD, TerminalColors.END) + if underline: + tmp = '{1}{0}{2}'.format( + tmp, TerminalColors.UNDERLINE, TerminalColors.END) + if hasattr(TerminalColors, color.upper()): + tmp = '{1}{0}{2}'.format( + tmp, getattr(TerminalColors, color.upper()), TerminalColors.END) + return tmp diff --git a/energy_model/modules/misc.py b/energy_model/modules/misc.py new file mode 100644 index 0000000..7a3c60d --- /dev/null +++ b/energy_model/modules/misc.py @@ -0,0 +1,205 @@ +''' +Created by Yanzi @ 06/16/2016 +Last updated by Yanzi @ 02/02/2017 +misc module: all sorts of uncategorized functions +''' + +import logging +import os +import random +import time +import threading +import numpy as np +from math import sqrt +from TerminalColors import * + + +class MyTimer(threading.Thread): + ''' + Threaded timer so it is non-blocking and we can do additional stuff + ''' + def __init__(self, timeInSec): + threading.Thread.__init__(self) + self.time = timeInSec + + def run(self): + print "Timer thread kicked in.. START.." + time.sleep(self.time) + print "Timer thread kicked in.. STOP.." + + +class EmptyLogger: + ''' + logger base + ''' + def __init__( + self, loggerTag, level=logging.DEBUG, logPath=None, printout=False + ): + self.myLogger = logging.getLogger(loggerTag) + self.myLogger.setLevel(level) + self.ch_file = None + self.ch_stream = None + formatter = logging.Formatter( + '%(asctime)s %(name)s %(levelname)s, %(message)s') + if logPath is not None: + self.ch_file = logging.FileHandler(logPath, 'w') + self.ch_file.setLevel(level) + self.ch_file.setFormatter(formatter) + self.myLogger.addHandler(self.ch_file) + if printout: + self.ch_stream = logging.StreamHandler() + self.ch_stream.setLevel(level) + # ch_stream.setLevel(level) + self.ch_stream.setFormatter(formatter) + self.myLogger.addHandler(self.ch_stream) + self.myLogger.info('logging started') + + def info(self, string): + self.myLogger.info(string) + + def debug(self, string): + self.myLogger.debug(string) + + def error(self, string): + self.myLogger.error(colorString(string)) + + def note(self, string): + self.myLogger.info(colorString(string, color='blue')) + + def enable(self): + if self.ch_file is not None: + self.myLogger.addHandler(self.ch_file) + if self.ch_stream is not None: + self.myLogger.addHandler(self.ch_stream) + + def disable(self): + if self.ch_file is not None: + self.myLogger.removeHandler(self.ch_file) + if self.ch_stream is not None: + self.myLogger.removeHandler(self.ch_stream) + + +def convert2Bool(stuff): + ''' + convert string or integer 0/1 to bool + supported ['True', 'true', 'yes', 'y', '1', 1] and vice versa + ''' + return stuff in ['True', 'true', 'yes', 'y', '1', 1] + + +def abs(num): + ''' + compute absolute number + ''' + return np.abs(num) + + +def nanratio(lst): + ''' + compute the ratio of nan over all elements in list + ''' + if len(lst) is 0: + return 0 + try: + nan_count = np.count_nonzero(np.isnan(lst)) + except: + nan_count = np.sum(np.isnan(lst)) + if np.isnan(nan_count): + return float('nan') + return 1.0 * nan_count / len(lst) + + +def max(lst): + ''' + compute max (excluding nan) of a list of numbers + ''' + if len(lst) is 0: + return float('nan') + return np.nanmax(lst) + + +def mean(lst): + ''' + compute mean (excluding nan) of a list of numbers + ''' + if len(lst) is 0: + return float('nan') + try: + tmp = np.count_nonzero(~np.isnan(lst)) + except: + tmp = np.sum(~np.isnan(lst)) + tmp2 = np.nansum(lst) + if tmp is 0 or np.isnan(tmp2): + return float('nan') + return tmp2 / tmp + + +def median(lst): + ''' + compute median (excluding nan) of a list of numbers + ''' + if len(lst) is 0: + return float('nan') + lst = np.array(lst) + newlst = lst[~np.isnan(lst)] + if len(newlst) is 0: + return float('nan') + return np.median(newlst) + + +def std(lst): + ''' + compute std (excluding nan) of a list of numbers + ''' + if len(lst) is 0: + return float('nan') + try: + return np.nanstd(lst) + except: + pass + lst = np.array(lst) + tmp = mean(lst) + if np.isnan(tmp): + return float('nan') + return np.sqrt(mean(abs(lst - tmp)**2)) + + +def getRandomIPAddr(): + ''' + derive a random ip address + ''' + return '192.168.1.'+str(random.randint(1, 255)) + + +def getRandomMacAddr(): + ''' + derive a random MAC address + ''' + mac = [ + 0x02, 0x08, 0x02, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + + +def which(program): + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + return None + + +if __name__ == '__main__': + print 'Usage: from misc import *' diff --git a/energy_model/modules/model.py b/energy_model/modules/model.py new file mode 100644 index 0000000..32f53d6 --- /dev/null +++ b/energy_model/modules/model.py @@ -0,0 +1,238 @@ +import sys +import os +import xml.etree.cElementTree as ET +from misc import * + + +def getVoltage(productname): + if productname == "shamu": + return 4.2 + return 1 + + +class Model(): + ''' + The energy model module + ''' + + def __init__(self, isDebuging=False, use_uAh=False): + self.freqs = [] + self.cpu_single_core = {} + self.cpu_multi_core = {} + ''' + cpu format: { cpu num: + { freq: [active current, idle current], ...} + } + ''' + self.net_wifi = {} + self.net_LTE = {} + self.net_3G = {} + ''' + net format: { 'prom': + { 'index': [rssi, current, length], ...}, + 'active': + { rssi: [rx current, tx current, rx xput, tx xput]} + 'tail': + { 'index': [rssi, current, length], ...}, + } + ''' + self.DEBUG = isDebuging + self.logger = None + if isDebuging: + self.logger = EmptyLogger("Model", printout=True) + if use_uAh: + self._ratio_uAh_over_mAs = 5 / 18.0 + else: + self._ratio_uAh_over_mAs = 1.0 + + def load(self, productname, dir="../models/"): + self.voltage = getVoltage(productname) + filepath = "{0}/{1}.xml".format(dir, productname) + if not os.path.isfile(filepath): + self.logger.error("File {0} does not exist.".format(filepath)) + sys.exit(-1) + tree = ET.parse(filepath) + root = tree.getroot() + cpumodel = root.find("cpumodel") + self.parseFreqs(cpumodel.find("freqs")) + cores = cpumodel.find("cores") + self.parseCPUSingleCore(cores) + self.parseCPUMultiCore(cores) + netmodel = root.find("netmodel") + for net in netmodel.findall("net"): + if net.attrib['id'] == 'WIFI': + self.parseNet(net, self.net_wifi) + elif net.attrib['id'] == 'LTE': + self.parseNet(net, self.net_LTE) + elif net.attrib['id'] == '3G': + self.parseNet(net, self.net_3G) + + def parseNet(self, node, net_node): + prom = node.find("prom") + if prom is None or prom.attrib['numstates'] == '0': + net_node['prom'] = None + else: + net_node['prom'] = {} + for pst in prom.findall("promstate"): + net_node['prom'][pst.attrib['index']] = \ + [int(pst.attrib['index']), + int(pst.attrib['prompwr']), + int(pst.attrib['promlen'])] + + active = node.find("active") + if active is None: + net_node['active'] = None + else: + net_node['active'] = {} + for ast in active.findall("activestate"): + net_node['active'][int(ast.attrib['rssi'])] = \ + [int(ast.attrib['rxpwr']), + int(ast.attrib['txpwr']), + float(ast.attrib['rxxput']), + float(ast.attrib['txxput'])] + + tail = node.find("tail") + if tail is None or tail.attrib['numstates'] == '0': + net_node['tail'] = None + else: + net_node['tail'] = {} + for tst in tail.findall("tailstate"): + net_node['tail'][tst.attrib['index']] = \ + [int(tst.attrib['index']), + int(tst.attrib['tailpwr']), + int(tst.attrib['taillen'])] + + # print net_node + + def parseCPUMultiCore(self, node): + for core in node.findall("core"): + if core.attrib['mode'] == 'multicore': + myid = int(core.attrib['id']) + if myid in self.cpu_multi_core: + myfreq = int(core.attrib['freq']) + self.cpu_multi_core[myid][myfreq] = \ + [int(core.attrib['active']), + int(core.attrib['idle'])] + else: + self.cpu_multi_core[myid] = \ + {int(core.attrib['freq']): + [int(core.attrib['active']), + int(core.attrib['idle'])]} + # print self.cpu_multi_core + + def parseCPUSingleCore(self, node): + for core in node.findall("core"): + if core.attrib['mode'] == 'singlecore': + myid = int(core.attrib['id']) + if myid in self.cpu_single_core: + myfreq = int(core.attrib['freq']) + self.cpu_single_core[myid][myfreq] = \ + [int(core.attrib['active']), + int(core.attrib['idle'])] + else: + self.cpu_single_core[myid] = \ + {int(core.attrib['freq']): + [int(core.attrib['active']), + int(core.attrib['idle'])]} + # print self.cpu_single_core + + def parseFreqs(self, node): + for freq in node.findall("freq"): + self.freqs.append(int(freq.attrib['val'])) + # if self.DEBUG: + # self.logger.debug(self.freqs) + + def get_cpu_energy(self, time_diff, freq, util): + ''' + @param freq: list of cpu frequencies + @param util: list of cpu utilization + ''' + if len(freq) != len(util) or len(freq) < 1: + self.logger.error("freq & util have different length!") + sys.exit(-1) + current = 0 + if len(freq) > 1: + db = self.cpu_multi_core + for i in xrange(len(freq)): + if freq[i] <= 0 or freq[i] not in db[i]: + self.logger.error("freq outlier: {0}".format(freq[i])) + self.logger.debug(db[i]) + continue + active_current = db[i][freq[i]][0] + idle_current = db[i][freq[i]][1] + current += util[i] * (active_current - idle_current) + \ + idle_current + else: + db = self.cpu_single_core + if freq[0] <= 0 or freq[0] not in db[0]: + self.logger.error("freq outlier: {0}".format(f)) + self.logger.debug(db[i]) + else: + active_current = db[0][freq[0]][0] + idle_current = db[0][freq[0]][1] + current = util[0] * (active_current - idle_current) + \ + idle_current + # derive energy + energy = current * time_diff * self._ratio_uAh_over_mAs + if self.DEBUG: + self.logger.debug("cpu_energy: {0:.4f}".format(energy)) + return energy + + def get_lte_prom_energy(self, time_diff, rssi, isTX=True): + self.logger.error('TODO: not implemented yet') + return None + + def get_lte_tail_energy(self, time_diff, rssi, isTX=True): + self.logger.error('TODO: not implemented yet') + return None + + def get_lte_active_energy(self, time_diff, rssi, isTX=True): + self.logger.error('TODO: not implemented yet') + return None + + def get_wifi_active_energy(self, time_diff, rssi, isTX=True): + table_rssi = sorted(self.net_wifi['active'].keys(), reverse=True) + if isTX: + currentIdx = 1 + else: + currentIdx = 0 + # fetch current + current = None + if rssi >= table_rssi[0]: + current = self.net_wifi['active'][table_rssi[0]][currentIdx] + elif rssi < table_rssi[-1]: + current = self.net_wifi['active'][table_rssi[-1]][currentIdx] + else: + for i in xrange(1, len(table_rssi)): + if rssi >= table_rssi[i]: + endp = self.net_wifi['active'][table_rssi[i-1]][currentIdx] + startp = self.net_wifi['active'][table_rssi[i]][currentIdx] + rssi_diff = table_rssi[i-1] - table_rssi[i] + current = (endp + 1.0 * (endp - startp) * + (rssi - table_rssi[i-1]) / rssi_diff) + break + if current is None: + self.logger.error("Current {0} is nothing!".format(current)) + sys.exit(-1) + # derive energy + energy = current * time_diff * self._ratio_uAh_over_mAs + if self.DEBUG: + self.logger.debug("wifi_active_energy: {0:.4f}".format(energy)) + return energy + + def get_wifi_tail_energy(self, time_diff): + energy = (time_diff * self.net_wifi['tail']['0'][1] * + self._ratio_uAh_over_mAs) + if self.DEBUG: + self.logger.debug("wifi_tail_energy: {0:.4f}".format(energy)) + return energy + +if __name__ == "__main__": + print "Usage: from model import *" + # debugging.. + myObj = Model(isDebuging=True) + # myObj.load(sys.argv[1]) + myObj.load("shamu") + myObj.get_wifi_tail_energy(1) + myObj.get_wifi_active_energy(1, -60, isTX=False) + myObj.get_cpu_energy(1, [1036800, 422400], [0, 1])