This commit is contained in:
Roberto Agostino Vitillo 2014-03-04 12:46:44 +00:00
Родитель 0023a747a2
Коммит d6f38c7793
14 изменённых файлов: 50 добавлений и 969 удалений

Просмотреть файл

@ -1,18 +1,20 @@
# energia
Power benchmarking utilities used to collect and plot data of the energy usage of different desktop browsers on Windows, OSX & Linux.
Desktop Browser Power benchmarking Utility
## Dependencies
* Intel's Power Gadget, turbostat & powertop on Linux
* Python 3 with numpy, scipy and rpy2
* R with ggplot2 and gridExtra
* On Linux the **msr** and **cpuid** modules have to be loaded
* Intel's Power Gadget, Intel's BLA (Win only)
* Python 3 with numpy & pandas
## Idle benchmark on Windows, OSX or Linux
## Idle benchmark on Windows, OSX and Linux
The idle benchmark collects CPU & GPU statistics of the requested browsers idling on predefined pages.
The set of browsers and pages to benchmark can be specified in a json configuration file.
The collected metrics are aggregated from several tools which can also be specified in the configuration file, currently *energia* supports *PowerGadget* and *BLA*.
PowerGadget is the only tool available on all platforms. Finally, the aggregated results are stored in a csv file.
```bash
python3 idle_benchmark.py -c config.json -e 50 -d 30 -i 5
python3 benchmark.py -c config.json
```
The command will collect data about the idle usage of the browsers and the websites specified in the configuration file and produce a csv file and plot, if R and ggplot2 are available on the system.
The command will collect data about the idle usage of the browsers and the websites specified in the configuration file and produce a csv file.

Просмотреть файл

Просмотреть файл

@ -38,6 +38,10 @@ class Benchmark:
def run(self, benchmark, browser, partial):
df = benchmark.log()
df['Browser'] = browser.get_name()
df['Page'] = browser.get_page()
df['OS'] = browser.get_os()
res = df if partial is None else partial.combine_first(df)
return res
@ -66,7 +70,7 @@ if __name__ == "__main__":
parser.add_argument("-e", "--resolution", help="Sampling resolution in ms, if applicable", default=100, type=int)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=5, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=10, type=int)
parser.add_argument("-o", "--output", help="Path of the final csv output", default="report.csv")
parser.add_argument("-p", "--path", help="Tool path", default="")
parser.add_argument("-b", "--benchmark", help="Benchmark to run", default="idle")

Просмотреть файл

@ -17,6 +17,9 @@ class Browser:
def get_path(self):
return self.path
def get_os(self):
return platform.system()
@staticmethod
def create_browser(name, path, page):
os = platform.system()

Просмотреть файл

@ -16,9 +16,9 @@
]
},
"Benchmarks": ["BLA", "PowerGadget"],
"Pages": ["about:blank", "www.youtube.com", "www.yahoo.com", "www.amazon.com",
"www.ebay.com", "www.google.com", "www.facebook.com", "www.wikipedia.com",
"www.craigslist.com"],
"Benchmarks": ["PowerGadget"]
"www.craigslist.com"]
}

Просмотреть файл

@ -1,150 +0,0 @@
import os
import platform
import argparse
import json
from browser import Browser
from power_logger import PowerLogger
from subprocess import Popen
from time import sleep
_plotting_enabled = False
_measurements = {"Browser":[], "Page":[], "Watts":[], "CI":[], "Runs":[], "sec":[], "hz":[], "signal":[]}
_config = None
_args = None
try:
#Use Rpy2 & ggplot2 only if plotting is required
import rpy2.robjects as ro
import rpy2.robjects.lib.ggplot2 as ggplot2
from rpy2.robjects.packages import importr
gridExtra = importr("gridExtra")
grDevices = importr('grDevices')
_plotting_enabled = True
except:
pass
class IdleLogger(PowerLogger):
def __init__(self, browser, gadget_path):
super().__init__(gadget_path=gadget_path)
self.browser = browser
# This method is run before all iterations
def initialize(self):
self.browser.initialize()
sleep(_args.sleep)
# Everything in here runs with the power logger enabled
def execute_iteration(self):
pass
def process_measurements(self, m, r, signals, closest_signal, duration, frequency):
_measurements["Browser"].append(self.browser.get_name())
_measurements["Page"].append(self.browser.get_page())
_measurements["Watts"].append(m)
_measurements["CI"].append(r)
_measurements["Runs"].append(len(signals))
_measurements["sec"].append(duration)
_measurements["hz"].append(frequency)
_measurements["signal"].append(closest_signal)
# This method is run after all iterations
def finalize(self):
self.browser.finalize()
sleep(5)
def get_pages():
return _config["Pages"]
def get_browsers():
os = platform.system()
return _config["OS"][os]
def run_benchmark():
for page in get_pages():
for b in get_browsers():
browser = Browser.create_browser(name=b["name"], path=b["path"], page=page)
logger = IdleLogger(browser, _args.gadget_path)
logger.log(resolution=_args.resolution, iterations=_args.iterations, duration=_args.duration)
def plot_data(width=1024, height=300):
if not _plotting_enabled:
print("Warning: plotting requested but disabled due to unmet dependencies (rpy2 & ggplot2)")
return
frame = ro.DataFrame({"Browser": ro.StrVector(_measurements["Browser"]),
"Page": ro.StrVector(_measurements["Page"]),
"Watts": ro.FloatVector(_measurements["Watts"]),
"CI": ro.FloatVector(_measurements["CI"])})
title = "Idle power measurements for {} runs of {}s each at {}hz on {}".format(
_args.iterations, _args.duration, 1000.0/_args.resolution, platform.system())
p = ggplot2.ggplot(frame) + \
ggplot2.aes_string(x="Page", y="Watts", fill="Browser") + \
ggplot2.geom_bar(position="dodge", stat="identity") + \
ggplot2.geom_errorbar(ggplot2.aes_string(ymax="Watts+CI", ymin="Watts-CI"),
position=ggplot2.position_dodge(0.9), width=0.4) + \
ggplot2.theme_bw() + \
ggplot2.theme(**{'plot.title': ggplot2.element_text(size = 13),
'axis.text.x': ggplot2.element_text(angle=90, hjust=1),
'axis.title.x': ggplot2.element_blank()}) + \
ggplot2.ggtitle(title)
plots = [p]
n_browsers = len(get_browsers())
n_pages = len(get_pages())
for i in range(0, n_pages):
tmp_plots = []
# get highest usage
scale = 0
for j in range(0, n_browsers):
index = i*n_browsers + j
scale = max(_measurements["signal"][index].get_max_watts(), scale)
for j in range(0, n_browsers):
index = i*n_browsers + j
title = _measurements["Browser"][index] + " " + _measurements["Page"][index]
wplot = _measurements["signal"][index].get_time_freq_plots()[0] + \
ggplot2.ggtitle(title) + \
ggplot2.scale_y_continuous(limits=ro.IntVector([0, scale+1]))
tmp_plots.append(wplot)
plots.append(gridExtra.arrangeGrob(*tmp_plots, ncol=n_browsers))
grDevices.png(file=_args.png_output, width=width, height=height * len(plots))
gridExtra.grid_arrange(gridExtra.arrangeGrob(*plots, nrow=n_pages + 1))
grDevices.dev_off()
def dump_csv():
m = _measurements
with open(_args.csv_output, 'w') as f:
f.write("OS, Browser, Page, Watts, CI, Runs, Sec, Hz\n")
for i in range(len(m["Browser"])):
f.write("{}, {}, {}, {:.2f}, {:.2f}, {}, {}, {:.2f}\n".format(platform.system(),
m["Browser"][i], m["Page"][i], m["Watts"][i], m["CI"][i], m["Runs"][i],
m["sec"][i], m["hz"][i]))
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Idle power benchmark",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-o", "--png_output", help="Path of the final .png plot", default="report.png")
parser.add_argument("-t", "--csv_output", help="Path of the emitted csv file", default="report.csv")
parser.add_argument("-c", "--config", help="Configuration file", default="config.json")
parser.add_argument("-e", "--resolution", help="Sampling resolution in ms", default=50, type=int)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=10, type=int)
parser.add_argument("-s", "--sleep", help="Seconds to sleep before the benchmark starts recording the power usage", default=100, type=int)
parser.add_argument("-p", "--gadget_path", help="Intel's Power Gadget path", default="")
_args = parser.parse_args()
with open(_args.config) as f:
_config = json.load(f)
run_benchmark()
dump_csv()
plot_data()

Просмотреть файл

@ -1,84 +0,0 @@
import argparse
import json
import platform
import os
import power_summary as ps
sys.path.append("..")
from browser import Browser
from time import sleep
from subprocess import Popen, PIPE
from pandas import DataFrame
_fields = []
_config = None
_args = None
_output = None
_result_df = None
class IdleSummary(ps.PowerSummary):
def __init__(self, browser, duration, iterations):
super().__init__(duration, iterations)
self._browser = browser
def initialize(self):
self._browser.initialize()
sleep(_args.sleep)
def process_measurements(self, df):
global _result_df
summary = df.mean().to_dict()
cis = df.apply(lambda x: stats.sem(x) * stats.t.ppf((1.95)/2., len(x) - 1)).to_dict()
for key, value in cis.items():
summary[key+" CI"] = value
summary["Browser"] = self._browser.get_name()
summary["Page"] = self._browser.get_page()
_result_df = _result_df.append(summary, ignore_index=True)
def finalize(self):
self._browser.finalize()
sleep(5)
def get_pages():
return _config["Pages"]
def get_browsers():
os = platform.system()
return _config["OS"][os]
def run_benchmark():
for page in get_pages():
for b in get_browsers():
browser = Browser.create_browser(name=b["name"], path=b["path"], page=page)
idleSummary = IdleSummary(browser, _args.duration, _args.iterations)
idleSummary.log()
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Idle power benchmark",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=10, type=int)
parser.add_argument("-o", "--output", help="Path of the csv output", default="report.csv")
parser.add_argument("-c", "--config", help="Configuration file", default="idle_config.json")
parser.add_argument("-s", "--sleep", help="Seconds to sleep before the benchmark starts recording the power usage", default=100, type=int)
_args = parser.parse_args()
# Prepare result dataset
for field in ps.fields:
_fields.append(field)
_fields.append(field + " SD")
_fields.append("Browser")
_fields.append("Page")
_result_df = DataFrame(columns=_fields)
# Load configuration
with open(_args.config) as f:
_config = json.load(f)
# Run benchmark
run_benchmark()
_result_df.to_csv(_args.output, index=False)

Просмотреть файл

@ -1,124 +0,0 @@
import argparse
import tempfile
import os
import uuid
import shutil
import re
from subprocess import Popen, PIPE
from pandas import DataFrame
fields = ["%c0", "GHz", "TSC", "SMI", "%c1", "%c3", "%c6", "%c7", "CTMP",
"PTMP", "%pc2", "%pc3", "%pc6", "%pc7", "Pkg_W", "Cor_W", "GFX_W", "wakeups"]
class PowerTop:
def __init__(self, duration=5):
self._process = None
self._duration = duration
self._file = tempfile.NamedTemporaryFile(suffix=".csv", delete='False')
def _remove_tmp_dir(self, directory):
shutil.rmtree(directory)
def start(self):
self._process = Popen(["powertop --csv={} --time={}".format(self._file.name, self._duration)],
shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
def join(self):
self._process.wait()
with open(self._file.name, "r") as f:
content = f.read()
match = re.search(r'\n(.*),(.*) wakeups/second', content)
if match:
return int(match.group(1))
else:
assert(0)
class TurboStat:
def __init__(self, duration=5):
self._process = None
self._duration = duration
def start(self):
self._process = Popen(['turbostat -S sleep ' + str(self._duration)],
shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
def join(self):
out = self._process.communicate()
f = out[1].decode().split('\n')[1].split()
summary = {}
for i, val in enumerate(fields[:-1]):
summary[val] = f[i]
return summary
class PowerSummary:
def __init__(self, duration, iterations, debug=False):
self._debug = debug
self._duration = duration
self._iterations = iterations
self._turbostat = TurboStat(duration)
self._powertop = PowerTop(duration)
def _collect_power_usage(self):
df = DataFrame(columns=fields)
for i in range(0, self._iterations):
if self._debug:
print("Starting run", i)
df = self._run_iteration(df)
return df.convert_objects(convert_numeric=True)
def _run_iteration(self, df):
self.initialize_iteration()
self._turbostat.start()
self.execute_iteration()
summary = self._turbostat.join()
# run powertop
self._powertop.start()
wakeups = self._powertop.join()
summary["wakeups"] = wakeups
self.finalize_iteration()
return df.append(summary, ignore_index=True)
def log(self, filename=None):
self.initialize()
df = self._collect_power_usage()
df.describe().to_csv(filename) if filename else None
self.process_measurements(df)
self.finalize()
def initialize(self):
pass
def initialize_iteration(self):
pass
def execute_iteration(self):
pass
def finalize_iteration(self):
pass
def process_measurements(self, df):
pass
def finalize(self):
pass
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Collect power usage statistics.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=5, type=int)
parser.add_argument("-o", "--output", help="Path of the csv output", default="report.csv")
parser.add_argument("--debug", help="Show debug messages", action="store_true")
args = parser.parse_args()
summary = PowerSummary(args.duration, args.iterations, args.debug)
summary.log(args.output)

Просмотреть файл

@ -1,373 +0,0 @@
import scipy
import scipy.fftpack
import scipy.stats
import sys
import argparse
import datetime
import time
import numpy
import os
import platform
import re
import shutil
import uuid
import tempfile
import functools
import multiprocessing
from bisect import bisect_left
from datetime import datetime, timedelta
_plotting_enabled = False
try:
#Use Rpy2 & ggplot2 only if plotting is required
import rpy2.robjects as ro
import rpy2.robjects.lib.ggplot2 as ggplot2
from rpy2.robjects.packages import importr
_plotting_enabled = True
except:
pass
def binary_search(a, x, lo=0, hi=None):
hi = hi if hi is not None else len(a)
pos = bisect_left(a,x,lo,hi)
return (pos if pos != hi and a[pos] == x else -1)
class PowerGadget:
_osx_exec = "PowerLog"
_win_exec = "PowerLog.exe"
_lin_exec = "power_gadget"
def __init__(self, path):
self._system = platform.system()
if path:
if os.path.exists(path) and os.access(path, os.X_OK):
self._path = path
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Darwin":
if shutil.which(PowerGadget._osx_exec):
self._path = PowerGadget._osx_exec
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Linux":
if shutil.which(PowerGadget._lin_exec):
self._path = PowerGadget._lin_exec
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Windows":
if shutil.which(PowerGadget._win_exec):
self._path = PowerGadget._win_exec
else:
raise Exception("Intel Power Gadget executable not found")
else:
raise Exception("Platform is not supported.")
def _start(self, resolution, duration, filename):
if self._system == "Darwin":
os.system(self._path + " -resolution " + str(resolution) + " -duration " +
str(duration) + " -file " + filename + " > /dev/null")
elif self._system == "Linux":
os.system(self._path + " -e " + str(resolution) + " -d " +
str(duration) + " > " + filename)
else:
os.system(self._path + " -resolution " + str(resolution) + " -duration " +
str(duration) + " -file " + filename + " > NUL 2>&1")
def start(self, resolution, duration, filename):
self._log_process = multiprocessing.Process(target=functools.partial(self._start, resolution, duration, filename))
self._log_process.start()
def join(self):
assert(self._log_process)
self._log_process.join()
class Signal:
def __init__(self, sequence, timestamps, cumulative_joules, frequency, duration):
assert(len(sequence) == len(timestamps) and len(sequence) == len(cumulative_joules))
self._sequence = numpy.array(sequence)
self._timestamps = timestamps
self._cumulative_joules = cumulative_joules
self._frequency = frequency
self._duration = duration
self._start_time = timestamps[0]
self._end_time = timestamps[0] + timedelta(0, duration)
def get_avg_watts(self, start_ts=None, end_ts=None):
start = 0
end = len(self._cumulative_joules) - 1
if start_ts:
start = bisect_left(self._timestamps, start_ts)
assert(start <= self._end_time)
assert(start >= self._start_time)
if end_ts:
end = bisect_left(self._timestamps, end_ts)
assert(end <= self._end_time)
assert(end >= self._start_time)
return numpy.mean(self._sequence[start:end+1])
def get_joules(self, start_ts=None, end_ts=None):
start = 0
end = len(self._cumulative_joules) - 1
if start_ts:
start = bisect_left(self._timestamps, start_ts)
assert(start <= self._end_time)
assert(start >= self._start_time)
if end_ts:
end = bisect_left(self._timestamps, end_ts)
assert(end <= self._end_time)
assert(end >= self._start_time)
start = self._cumulative_joules[start]
end = self._cumulative_joules[end]
return end - start
def get_max_watts(self):
return max(self._sequence)
def get_start_time(self):
return self._start_time
def get_end_time(self):
return self._end_time
def get_length(self):
return len(self._sequence)
def get_time_freq_plots(self, title=""):
length = self.get_length()
t = scipy.linspace(0, self._duration, len(self._sequence))
frame = ro.DataFrame({'Watt': ro.FloatVector(self._sequence), 'sec': ro.FloatVector(t)})
watts = ggplot2.ggplot(frame) + \
ggplot2.aes_string(x="sec", y="Watt") + \
ggplot2.geom_line() + \
ggplot2.ggtitle(title) + \
ggplot2.theme(**{'plot.title': ggplot2.element_text(size = 13)}) + \
ggplot2.theme_bw() + \
ggplot2.scale_x_continuous(expand=ro.IntVector([0, 0]))
fft = abs(scipy.fft(self._sequence))
f = scipy.linspace(0, self._frequency/2.0, length/2.0)
# don't plot the mean
fft = 2.0/length*abs(fft[1:length//2])
f = f[1:]
frame = ro.DataFrame({'Amplitude': ro.FloatVector(fft), 'hz': ro.FloatVector(f)})
freq = ggplot2.ggplot(frame) + \
ggplot2.aes_string(x="hz", y="Amplitude") + \
ggplot2.geom_line() + \
ggplot2.theme_bw() + \
ggplot2.scale_x_continuous(expand=ro.IntVector([0, 0]))
return (watts, freq)
def plot(self, filename, title="", width=1024, height=512):
if not _plotting_enabled:
print("Warning: plotting requested but disabled due to unmet dependencies (rpy2 & ggplot2)")
return
gridExtra = importr("gridExtra")
grDevices = importr('grDevices')
time, freq = self.get_time_freq_plots(title)
grDevices.png(file=filename, width=width, height=height)
gridExtra.grid_arrange(time, freq)
grDevices.dev_off()
@staticmethod
def parse(path, frequency, duration, start_time, debug=False):
signal = []
cumulative_joules = []
timestamps = []
try:
with open(path) as f:
lines = f.readlines()
data = []
metadata = []
# split in data and metadata
for iteration, line in enumerate(lines):
if line == "\n":
data = lines[1:iteration]
metadata = lines[iteration + 1:]
break
# print metadata if required
for line in metadata:
line = line.strip()
if line and debug:
print(line)
# assume duration < 24h
one_day = timedelta(1)
assert(duration < (24*60*60 - 60))
for line in data:
fields = line.split(",")
ts = datetime.strptime("{}:{}:{} {}".format(start_time.month, start_time.day, start_time.year, fields[0]), "%m:%d:%Y %H:%M:%S:%f")
if ts < start_time:
ts = ts + one_day
watts = float(fields[4])
joules = float(fields[5])
# filter invalid data
if watts == 0:
continue
timestamps.append(ts)
signal.append(watts)
cumulative_joules.append(joules)
except FileNotFoundError:
raise Exception("PowerLog failed to generate a valid logfile")
return sys.exit(-1)
assert(len(signal) > 0)
return Signal(signal, timestamps, cumulative_joules, frequency, duration)
class PowerLogger:
def __init__(self, gadget_path="", debug=False):
self._powergadget = PowerGadget(gadget_path)
self._debug = debug
def _create_tmp_dir(self):
directory = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
if os.path.exists(directory):
shutil.rmtree(directory)
os.makedirs(directory)
return directory
def _remove_tmp_dir(self, directory):
if self._debug:
print("Logs saved to:", directory)
else:
shutil.rmtree(directory)
def _collect_power_usage(self, directory, resolution, frequency, duration, iterations):
signals = []
self.initialize()
for i in range(0, iterations):
if self._debug:
print("Starting run", i)
report = os.path.join(directory, "log_" + str(i))
signals.append(self._run_iteration(resolution, frequency, duration, report))
self.finalize()
return signals
def _predict_duration(self):
self.initialize_iteration()
start = datetime.now()
self.execute_iteration()
end = datetime.now()
self.finalize_iteration()
return int((end - start).total_seconds()) + 5 #magic number
def _run_iteration(self, resolution, frequency, duration, report):
# Decorate power usage logging with template methods
self.initialize_iteration()
start_time = datetime.now()
self._powergadget.start(resolution, duration, report + ".log")
self.execute_iteration()
end_time = datetime.now()
self._powergadget.join()
self.finalize_iteration()
signal = Signal.parse(report + ".log", frequency, duration, start_time, self._debug)
assert(end_time < signal.get_end_time())
if self._debug:
signal.plot(report + ".png")
return signal
def _mean_confidence_interval(self, signals, confidence=0.95):
data = [signal.get_avg_watts() for signal in signals]
mean = numpy.mean(data)
if len(data) == 1:
return mean, float('nan')
se = scipy.stats.sem(data)
n = len(data)
h = se * scipy.stats.t.ppf((1 + confidence)/2., n - 1)
return mean, h
def _plot_closest_signal(self, signals, freq, duration, mean, range, png_output):
signal = self.get_closest_signal(signals, mean)
title = "Mean of {:.2f} += {:.2f} Watts for {} runs of {} s at {:.2f} hz".\
format(mean, range, len(signals), duration, freq)
signal.plot(png_output, title)
def get_closest_signal(self, signals, mean):
min = lambda x, y: x if abs(x.get_avg_watts() - mean) < abs(y.get_avg_watts() - mean) else y
return functools.reduce(min, signals)
def log(self, resolution, iterations, duration=None, png_output="report.png", plot=False):
directory = self._create_tmp_dir()
frequency = 1000.0/resolution
#run prediction in any case as a warm up run
predicted_duration = self._predict_duration()
duration = duration if duration else predicted_duration
signals = self._collect_power_usage(directory, resolution, frequency, duration, iterations)
m, r = self._mean_confidence_interval(signals)
self._plot_closest_signal(signals, frequency, duration, m, r, png_output) if plot else None
self._remove_tmp_dir(directory)
self.process_measurements(m, r, signals, self.get_closest_signal(signals, m), duration, frequency)
def initialize_iteration(self):
pass
def execute_iteration(self):
pass
def finalize_iteration(self):
pass
def initialize(self):
pass
def finalize(self):
pass
def process_measurements(self, m, r, signals, closest_signal, druation, frequency):
pass
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Plot Power Gadget's logs in time and frequency domain",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-e", "--resolution", help="Sampling resolution in ms", default=50, type=int)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=60, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=2, type=int)
parser.add_argument("-p", "--gadget_path", help="Intel's Power Gadget path", default="")
parser.add_argument("-o", "--output", help="Path of the final .png plot", default="report.png")
parser.add_argument("--debug", help="Show debug messages", action="store_true")
args = parser.parse_args()
logger = PowerLogger(args.gadget_path, args.debug)
logger.log(args.resolution, args.iterations, args.duration, args.output, True)

Просмотреть файл

@ -1,87 +0,0 @@
import argparse
import json
import platform
import os
import sys
import power_summary as ps
sys.path.append("..")
from browser import Browser
from time import sleep
from subprocess import Popen, PIPE
from pandas import DataFrame
from scipy import stats
_fields = []
_config = None
_args = None
_output = None
_result_df = None
class IdleSummary(ps.PowerSummary):
def __init__(self, browser, duration, iterations, process=None):
super().__init__(duration, iterations, image=process)
self._browser = browser
def initialize(self):
self._browser.initialize()
sleep(_args.sleep)
def process_measurements(self, df):
global _result_df
summary = df.mean().to_dict()
cis = df.apply(lambda x: stats.sem(x) * stats.t.ppf((1.95)/2., len(x) - 1)).to_dict()
for key, value in cis.items():
summary[key+" CI"] = value
summary["Browser"] = self._browser.get_name()
summary["Page"] = self._browser.get_page()
_result_df = _result_df.append(summary, ignore_index=True)
def finalize(self):
self._browser.finalize()
sleep(5)
def get_pages():
return _config["Pages"]
def get_browsers():
os = platform.system()
return _config["OS"][os]
def run_benchmark():
for page in get_pages():
for b in get_browsers():
browser = Browser.create_browser(name=b["name"], path=b["path"], page=page)
process = os.path.basename(browser.get_path())
idleSummary = IdleSummary(browser, _args.duration, _args.iterations, process)
idleSummary.log()
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Idle power benchmark",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=10, type=int)
parser.add_argument("-o", "--output", help="Path of the csv output", default="report.csv")
parser.add_argument("-c", "--config", help="Configuration file", default="idle_config.json")
parser.add_argument("-s", "--sleep", help="Seconds to sleep before the benchmark starts recording the power usage", default=500, type=int)
_args = parser.parse_args()
# Prepare result dataset
for field in ps.fields:
_fields.append(field)
_fields.append(field + " CI")
_fields.append("Browser")
_fields.append("Page")
_result_df = DataFrame(columns=_fields)
# Load configuration
with open(_args.config) as f:
_config = json.load(f)
# Run benchmark
run_benchmark()
_result_df.to_csv(_args.output, index=False)

Просмотреть файл

@ -1,124 +0,0 @@
import argparse
import tempfile
import os
import uuid
import shutil
import re
import pandas
from subprocess import Popen, PIPE
from pandas import DataFrame
fields = ["CPU % (Platform)", "CPU % (Logical)", "CPU Proc % (Platform)", "CPU Proc % (Logical)", "Idle Wakeups", "Idle Proc Wakeups", "Power Impact", "Power Proc Impact"]
class BLA:
def __init__(self, duration, image=None):
self._process = None
self._duration = duration
self._directory = "tmp"
self._image = None
if image:
self._image = image if image.endswith(".exe") else image + ".exe"
def start(self):
# Can't use tempfile bc BLA doesn't support abbreviated filenames
self._process = Popen('BLA.exe c sw:{} -o {}'.format(self._duration, self._directory))
pass
def join(self):
self._process.wait()
path = os.path.join(self._directory, "Active Analysis.csv")
aa_df = pandas.io.parsers.read_csv(path, sep="\t", encoding="utf-16")
entry = {}
entry["CPU % (Platform)"] = aa_df['CPU % (Platform)'][0]
entry["CPU % (Logical)"] = aa_df['CPU % (Logical)'][0]
entry["Idle Wakeups"] = aa_df['CSwitches from Idle'][0]
entry["Power Impact"] = aa_df['Power Impact (W) - HuronRiver - Sandybridge - Dual Core'][0]
print(aa_df['CPU % (Platform)'][0])
if self._image != None:
selection = aa_df[aa_df['Image Name'] == self._image]
if len(selection > 0):
entry["CPU Proc % (Platform)"] = selection["CPU % (Platform)"].sum()
entry["CPU Proc % (Logical)"] = selection["CPU % (Logical)"].sum()
entry["Idle Proc Wakeups"] = selection["CSwitches from Idle"].sum()
entry["Power Proc Impact"] = selection['Power Impact (W) - HuronRiver - Sandybridge - Dual Core'].sum()
shutil.rmtree(self._directory)
return entry
class PowerSummary:
def __init__(self, duration, iterations, image=None, debug=False):
self._debug = debug
self._duration = duration
self._iterations = iterations
self._bla = BLA(duration, image)
def _collect_power_usage(self):
df = DataFrame(columns=fields)
for i in range(0, self._iterations):
if self._debug:
print("Starting run", i)
df = self._run_iteration(df)
return df.convert_objects(convert_numeric=True)
def _run_iteration(self, df):
self.initialize_iteration()
self._bla.start()
self.execute_iteration()
summary = self._bla.join()
self.finalize_iteration()
return df.append(summary, ignore_index=True)
def _filter_outliers(self, df):
if len(df) <= 1:
return df
cpu = df["CPU % (Platform)"]
# SD is not robust
return df[(cpu >= cpu.median() - cpu.mad()*5) & (cpu <= cpu.median() + cpu.mad()*5)]
def log(self, filename=None):
self.initialize()
# There are some bad outliers here
df = self._filter_outliers(self._collect_power_usage())
df.describe().to_csv(filename) if filename else None
self.process_measurements(df)
self.finalize()
def initialize(self):
pass
def initialize_iteration(self):
pass
def execute_iteration(self):
pass
def finalize_iteration(self):
pass
def process_measurements(self, df):
pass
def finalize(self):
pass
if __name__ == "__main__":
parser= argparse.ArgumentParser(description="Collect power usage statistics.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--duration", help="Collection duration in s", default=30, type=int)
parser.add_argument("-i", "--iterations", help="Number of iterations", default=5, type=int)
parser.add_argument("-o", "--output", help="Path of the csv output", default="report.csv")
parser.add_argument("-p", "--process", help="Name of the process to monitor", default=None)
parser.add_argument("--debug", help="Show debug messages", action="store_true")
args = parser.parse_args()
summary = PowerSummary(args.duration, args.iterations, args.process, args.debug)
summary.log(args.output)

Просмотреть файл

@ -23,12 +23,13 @@ class Wrapper:
for key, value in cis.items():
summary[key + " CI"] = value
summary["iterations"] = self._args.iterations
summary["duration"] = self._args.duration
summary["Iterations"] = self._args.iterations
summary["Duration"] = self._args.duration
return DataFrame(summary, index=[0])
def _filter_outliers(self, df):
if len(df) <= 1:
length = len(df)
if length <= 1:
return df
for c in df.columns:
@ -36,6 +37,9 @@ class Wrapper:
# SD is not robust
df = df[(series >= series.median() - series.mad()*5) & (series <= series.median() + series.mad()*5)]
if length != len(df):
print("Warning: {} outliers removed.".format(length - len(df)))
return df
def _run_iteration(self, df):

Просмотреть файл

@ -22,9 +22,19 @@ class BLA(Wrapper):
if self._image:
self._image = self._image if self._image.endswith(".exe") else self._image + ".exe"
if self._args.path:
if os.path.exists(self._args.path) and os.access(self._args.path, os.X_OK):
self._tool = self._args.path
else:
raise Exception("Intel Battery Life Analyzer not found")
elif shutil.which("BLA.exe"):
self._tool = "BLA.exe"
else:
raise Exception("Intel Battery Life Analyzer not found")
def start(self):
# Can't use tempfile bc BLA doesn't support abbreviated filenames
self._process = Popen('BLA.exe c sw:{} -o {}'.format(self._args.duration, self._directory))
self._process = Popen('{} c sw:{} -o {}'.format(self._tool, self._args.duration, self._directory))
def join(self):
self._process.wait()

Просмотреть файл

@ -24,22 +24,22 @@ class PowerGadget(Wrapper):
if self._args.path:
if os.path.exists(self._args.path) and os.access(self._args.path, os.X_OK):
self._log = self._args.path
self._tool = self._args.path
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Darwin":
if shutil.which(PowerGadget._osx_exec):
self._log = PowerGadget._osx_exec
self._tool = PowerGadget._osx_exec
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Linux":
if shutil.which(PowerGadget._lin_exec):
self._log = PowerGadget._lin_exec
self._tool = PowerGadget._lin_exec
else:
raise Exception("Intel Power Gadget executable not found")
elif self._system == "Windows":
if shutil.which(PowerGadget._win_exec):
self._log = PowerGadget._win_exec
self._tool = PowerGadget._win_exec
else:
raise Exception("Intel Power Gadget executable not found")
else:
@ -61,13 +61,13 @@ class PowerGadget(Wrapper):
if self._system == "Darwin":
os.system("{} -resolution {} -duration {} -file {} > /dev/null".format(
self._log, str(resolution), str(duration), self._logfile))
self._tool, str(resolution), str(duration), self._logfile))
elif self._system == "Linux":
os.system("{} -e {} -d {} > {}".format(self._log, str(resolution),
os.system("{} -e {} -d {} > {}".format(self._tool, str(resolution),
str(duration), self._logfile))
else:
os.system("{} -resolution {} -duration {} -file {} > NUL 2>&1".format(
self._log, str(resolution), str(duration), self._logfile))
self._tool, str(resolution), str(duration), self._logfile))
def join(self):
self._log_process.join()
@ -83,10 +83,10 @@ class PowerGadget(Wrapper):
regexps = {"Processor Watt" : re.compile(".* Processor Power_0 \(Watt\) = (.*)"),
"Processor Joules": re.compile(".* Processor Energy_0 \(Joules\) = (.*)"),
"IA Watt": re.compile(".* IA Energy_0 \(Joules\) = (.*)"),
"IA Joules": re.compile(".* IA Power_0 \(Watt\) = (.*)"),
"GT Watt": re.compile(".* GT Energy_0 \(Joules\) = (.*)"),
"GT Joules": re.compile(".* GT Power_0 \(Watt\) = (.*)")}
"IA Joules": re.compile(".* IA Energy_0 \(Joules\) = (.*)"),
"IA Watt": re.compile(".* IA Power_0 \(Watt\) = (.*)"),
"GT Joules": re.compile(".* GT Energy_0 \(Joules\) = (.*)"),
"GT Watt": re.compile(".* GT Power_0 \(Watt\) = (.*)")}
try:
with open(self._logfile) as f: