зеркало из https://github.com/mozilla/energia.git
Finalize new version.
This commit is contained in:
Родитель
0023a747a2
Коммит
d6f38c7793
18
README.md
18
README.md
|
@ -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)
|
373
power_logger.py
373
power_logger.py
|
@ -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)
|
10
wrapper.py
10
wrapper.py
|
@ -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:
|
||||
|
|
Загрузка…
Ссылка в новой задаче