Commit f2213b9a authored by Kazutomo Yoshii's avatar Kazutomo Yoshii
Browse files

Added mapping function. Adding docstrings

parent be059812
#!/usr/bin/env python
#
# coolr rapl related codes
#
# This code requires the intel_powerclamp module.
#
# Contact: Kazutomo Yoshii <ky@anl.gov>
#
"""
COOLR RAPL package
The intel_rapl kernel module is required.
Contact: Kazutomo Yoshii <ky@anl.gov>
"""
import os, sys, re, time, getopt
import clr_nodeinfo
class rapl_reader:
dryrun = False
"""The 'rapl_reader' class provides monitoring and controlling
capabilities for Intel RAPL.
rapldir='/sys/devices/virtual/powercap/intel-rapl'
This implentation parses sysfs entries created the intel_rapl
kernel module.
re_domain = re.compile('package-([0-9]+)(/\S+)?')
"""
re_domain = re.compile('package-(\d+)$')
re_domain_long = re.compile('package-(\d+)(/\S+)?')
re_domain_short = re.compile('p(\d+)(/\D+)?')
def readint(self, fn):
"""A convenient function that reads the firs line in fn and returns
an integer value
"""
v = -1
for retry in range(0,10):
try:
......@@ -30,6 +39,9 @@ class rapl_reader:
return v
def writeint(self, fn, v):
"""A convenient function that writes an integer value to fn
"""
ret = False
try:
f = open(fn, 'w')
......@@ -39,17 +51,50 @@ class rapl_reader:
except:
ret = False
return ret
#
# e.g.,
# intel-rapl:0/name
# intel-rapl:0/intel-rapl:0:0/name
# intel-rapl:0/intel-rapl:0:1/name
def is_enabled(self):
"""Check see if RAPL is enabled
The overhead of this call is around 80 usec, which is
acceptable for reading energy since RAPL updates the internal
energy counter in milliseconds (e.g., 10 msec).
"""
return self.readint(self.rapldir + "/enabled")
def write_enabled(self, val):
"""Write val to enabled
For cases, even though sysfs reports enabled, we need to re-enable for power capping.
"""
def __init__ (self):
"""Initialize the rapl_reader module
It detects the power domains/subdomains by scanning the
intel_rapl sysfs recurcively and creates a dict 'dirs' that
contains sub-directory for available domains. The existance
of each domain is decided by the existance of a file name
'name' (see below). It also creates a dict named
'max_energy_range_uj_d' for the maximum energy range for the
RAPL energy counter, which is used to calculate the counter
wrapping.
e.g.,
intel-rapl:0/name
intel-rapl:0/intel-rapl:0:0/name
intel-rapl:0/intel-rapl:0:1/name
"""
self.dryrun = False
self.rapldir='/sys/devices/virtual/powercap/intel-rapl'
self.dirs = {}
self.max_energy_range_uj_d = {}
self.ct = clr_nodeinfo.cputopology()
if self.dryrun :
return
return
self.init = False
if not os.path.exists(self.rapldir):
......@@ -90,17 +135,20 @@ class rapl_reader:
self.start_energy_counter()
def initialized(self):
"""Return whether or not it is initialized"""
return self.init
def shortenkey(self,str):
"""Convert a long name to a short name"""
return str.replace('package-','p')
# for k in sorted(self.dirs.keys()):
# print k, self.max_energy_range_uj_d[k]
def readenergy(self):
if not self.init:
"""Read the energy consumption on all available domains
Returns:
A dict for energy consumptions on all available domains
"""
if not self.initialized():
return
ret = {}
......@@ -113,14 +161,19 @@ class rapl_reader:
ret[k] = self.readint(fn)
return ret
# Read all possible power caps, except package 'short_term', which
# will be supported later. This function is designed to be called
# from a slow path. return a dict with long domain names as keys
# and a value contains a dict with 'curW', 'maxW', 'enabled'
def readpowerlimitall(self):
if not self.init:
"""Read the current power limit values on all available domains
Read all possible power caps, except package 'short_term', which
will be supported later. This function is designed to be called
from a slow path. return a dict with long domain names as keys
and a value contains a dict with 'curW', 'maxW', 'enabled'
Returns:
A dict for power limit (power capping) values on all available domains
"""
if not self.initialized():
return
ret = {}
......@@ -142,7 +195,17 @@ class rapl_reader:
ret[k] = dvals
return ret
def diffenergy(self,e1,e2): # e1 is prev and e2 is not
def diffenergy(self, e1, e2): # e1 is prev and e2 is now
"""Calculate the delta value between e1 and e2, considering the counter wrapping
Args:
e1: the previous energy value
e2: the current energy value
Returns:
A dict for the energy delta values on all available domains and the time delta values
"""
ret = {}
ret['time'] = e2['time'] - e1['time']
for k in self.max_energy_range_uj_d:
......@@ -152,10 +215,18 @@ class rapl_reader:
ret[k] = (self.max_energy_range_uj_d[k]-e1[k]) + e2[k]
return ret
# calculate the average power from two energy values
# e1 and e2 are the value returned from readenergy()
# e1 should be sampled before e2
def calcpower(self,e1,e2):
def calcpower(self, e1, e2):
"""Calculate the average power from two energy values
e1 and e2 are the value returned from readenergy()
Args:
e1: the previous energy value
e2: the current energy value
Returns:
A dict for the average power consumption values on all available domains and the time delta values
"""
ret = {}
delta = e2['time'] - e1['time'] # assume 'time' never wrap around
ret['delta'] = delta
......@@ -174,8 +245,10 @@ class rapl_reader:
ret[k] /= (1000.0*1000.0) # conv. [uW] to [W]
return ret
# this should be renamed to reset_...
def start_energy_counter(self):
"""Start or reset the energy counter
"""
if not self.initialized():
return
......@@ -190,8 +263,10 @@ class rapl_reader:
self.lastpower[k] = 0.0
self.prev_e = e
# XXX: fix the total energy tracking later
def read_energy_acc(self):
"""Read the accumulated energy consumption
"""
if not self.initialized():
return
......@@ -208,6 +283,9 @@ class rapl_reader:
return e
def stop_energy_counter(self):
"""Stop the energy counter
"""
if not self.initialized():
return
......@@ -215,6 +293,9 @@ class rapl_reader:
self.stop_time = time.time()
def sample(self, accflag=False):
"""Sample the current counter values and return a dict
"""
if not self.initialized():
return
......@@ -253,7 +334,74 @@ class rapl_reader:
return ret
def sample_and_json(self, label = "", accflag = False, node = ""):
"""Sample the current counter values and creates a json string
"""
if not self.initialized():
return
e = self.readenergy()
de = self.diffenergy(self.prev_e, e)
for k in sorted(e.keys()):
if k != 'time':
if accflag:
self.totalenergy[k] += de[k]
self.lastpower[k] = de[k]/de['time']/1000.0/1000.0;
self.prev_e = e
# constructing a json output
s = '{"sample":"energy","time":%.3f' % (e['time'])
if len(node) > 0:
s += ',"node":"%s"' % node
if len(label) > 0:
s += ',"label":"%s"' % label
s += ',"energy":{'
firstitem = True
for k in sorted(e.keys()):
if k != 'time':
if firstitem:
firstitem = False
else:
s+=','
s += '"%s":%d' % (self.shortenkey(k), e[k])
s += '},'
s += '"power":{'
totalpower = 0.0
firstitem = True
for k in sorted(self.lastpower.keys()):
if k != 'time':
if firstitem:
firstitem = False
else:
s+=','
s += '"%s":%.1f' % (self.shortenkey(k), self.lastpower[k])
# this is a bit ad hoc way to calculate the total. needs to be fixed later
if k.find("core") == -1:
totalpower += self.lastpower[k]
s += ',"total":%.1f' % (totalpower)
s += '},'
s += '"powercap":{'
rlimit = self.readpowerlimitall()
firstitem = True
for k in sorted(rlimit.keys()):
if firstitem:
firstitem = False
else:
s+=','
s += '"%s":%.1f' % (self.shortenkey(k), rlimit[k]['curW'])
s += '}'
s += '}'
return s
def total_energy_json(self):
"""Create a json string for the total energy consumption
"""
if not self.initialized():
return ''
......@@ -268,30 +416,73 @@ class rapl_reader:
return s
def to_shortdn(self, n):
"""Convert to a short domain name form
def conv_long2short(self, n):
m = self.re_domain.match(n)
sn = ''
if m:
pkgid = int(m.group(1))
sn = 'p%d' % (pkgid)
No conversion takes place if n is already a short form
"""
m = self.re_domain_long.match(n)
sn = ''
if not m:
return n
else:
sn = 'p%d' % int(m.group(1))
if m.group(2):
subname = m.group(2)[1:]
sn += subname
sn += '/'
sn += m.group(2)[1:]
return sn
#
# APIs for power capping
#
def to_longdn(self, n):
"""Convert to a long domain name form
No conversion takes place if n is already a long form
"""
m = self.re_domain_short.match(n)
ln = ''
if not m:
return n
else:
ln = 'package-%d' % int(m.group(1))
if m.group(2):
ln += m.group(2)
return ln
def create_powerdomains_cpuids(self):
"""Create a mapping that represents the relationship between power domains and cpuids
Return:
A dict with power domain (interger) as key and a list of cpuids as content
"""
ret = {}
for pd in self.readpowerlimitall().keys():
m = self.re_domain.match(pd)
if m:
pkgid = int(m.group(1))
ret[pkgid] = self.ct.pkgcpus[pkgid]
return ret
def get_powerdomains(self):
"""Return a list of available power domains
Return:
A list that contains all possible power domain names (long-name string)
"""
return self.readpowerlimitall().keys
def get_powerlimits(self):
"""Return a dict of the current power limit values on all available power domains
Return:
A dict with long-name power domain string as key and a dict that includes capping information as content
"""
return self.readpowerlimitall()
def _set_powerlimit(self, rrdir, newval, id = 0):
"""Internal method to set new power limit value
"""
fn = rrdir + '/constraint_%d_power_limit_uw' % id
uw = newval * 1e6
try:
......@@ -303,20 +494,25 @@ class rapl_reader:
f.close()
def set_powerlimit(self, newval, dom):
l = self.dirs[dom]
"""Set new power limit value to specified power domain "dom"
Args:
newval: new power limit value in Watt
dom: target power domain. both long or short name forms are accepted
"""
dom_ln = to_longdn(dom)
l = self.dirs[dom_ln]
self._set_powerlimit(l, newval)
def set_powerlimit_pkg(self, newval):
"""Set new power limit value to all possible top-level CPU packages
"""
rlims = self.readpowerlimitall()
for k in rlims.keys():
if re.findall('package-[0-9]$', k):
self._set_powerlimit(self.dirs[k], newval)
# Implement the following method
# is_enabled_rapl(), enable_rapl(), disable_rapl()
# convshort2long
# pkgid2cpuids, cpuid2pkgid
def usage():
print 'clr_rapl.py [options]'
print ''
......@@ -327,11 +523,6 @@ def usage():
print 'If no option is specified, run the test pattern.'
print ''
def test_conv():
l = ['package-1', 'package-3/dram', 'package-2/core']
for s in l:
rr.conv_long2short(s)
def report_powerlimits():
l = rr.get_powerlimits()
......@@ -353,6 +544,29 @@ def run_powercap_testbench():
time.sleep(w)
rr.set_powerlimit_pkg(145)
def test_conv():
lns = ['package-1', 'package-3/dram', 'package-2/core']
for ln in lns:
sn = rr.to_shortdn(ln)
ln2 = rr.to_longdn(sn)
if ln == ln2:
print 'passed: ',
else:
print 'failed: ',
print ln, sn, ln2
def test_map():
pds = rr.create_powerdomains_cpuids()
for pd in pds:
print pd, pds[pd]
def unittest(m):
if m == 'conv':
test_conv()
elif m == 'map':
test_map()
if __name__ == '__main__':
rr = rapl_reader()
......@@ -362,9 +576,9 @@ if __name__ == '__main__':
sys.exit(1)
shortopt = "h"
longopt = ['getpd', 'getplim', 'setplim=', 'show', 'limitp=', 'testbench' ]
longopt = ['getpd', 'getplim', 'setplim=', 'show', 'limitp=', 'testbench', 'doc', 'test=' ]
try:
opts, args = getopt.getopt(sys.argv[1:],
opts, args = getopt.getopt(sys.argv[1:],
shortopt, longopt)
except getopt.GetoptError, err:
......@@ -376,6 +590,12 @@ if __name__ == '__main__':
if o in ('-h'):
usage()
sys.exit(0)
elif o in ("--test"):
unittest(a)
sys.exit(0)
elif o in ("--doc"):
help(rapl_reader)
sys.exit(0)
elif o in ("--testbench"):
print 'Start: testbench'
run_powercap_testbench()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment