1313# limitations under the License.
1414#
1515from contextlib import contextmanager
16+ from operator import itemgetter
1617
1718from devlib .module import Module
1819from devlib .exception import TargetStableError
1920from devlib .utils .misc import memoized
21+ import devlib .utils .asyn as asyn
2022
2123
2224# a dict of governor name and a list of it tunables that can't be read
@@ -52,22 +54,25 @@ def __init__(self, target):
5254 self ._governor_tunables = {}
5355
5456 @memoized
55- def list_governors (self , cpu ):
57+ @asyn .asyncf
58+ async def list_governors (self , cpu ):
5659 """Returns a list of governors supported by the cpu."""
5760 if isinstance (cpu , int ):
5861 cpu = 'cpu{}' .format (cpu )
5962 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors' .format (cpu )
60- output = self .target .read_value (sysfile )
63+ output = await self .target .read_value . asyn (sysfile )
6164 return output .strip ().split ()
6265
63- def get_governor (self , cpu ):
66+ @asyn .asyncf
67+ async def get_governor (self , cpu ):
6468 """Returns the governor currently set for the specified CPU."""
6569 if isinstance (cpu , int ):
6670 cpu = 'cpu{}' .format (cpu )
6771 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor' .format (cpu )
68- return self .target .read_value (sysfile )
72+ return await self .target .read_value . asyn (sysfile )
6973
70- def set_governor (self , cpu , governor , ** kwargs ):
74+ @asyn .asyncf
75+ async def set_governor (self , cpu , governor , ** kwargs ):
7176 """
7277 Set the governor for the specified CPU.
7378 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
@@ -90,15 +95,15 @@ def set_governor(self, cpu, governor, **kwargs):
9095 """
9196 if isinstance (cpu , int ):
9297 cpu = 'cpu{}' .format (cpu )
93- supported = self .list_governors (cpu )
98+ supported = await self .list_governors . asyn (cpu )
9499 if governor not in supported :
95100 raise TargetStableError ('Governor {} not supported for cpu {}' .format (governor , cpu ))
96101 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor' .format (cpu )
97- self .target .write_value (sysfile , governor )
98- self .set_governor_tunables (cpu , governor , ** kwargs )
102+ await self .target .write_value . asyn (sysfile , governor )
103+ return await self .set_governor_tunables . asyn (cpu , governor , ** kwargs )
99104
100- @contextmanager
101- def use_governor (self , governor , cpus = None , ** kwargs ):
105+ @asyn . asynccontextmanager
106+ async def use_governor (self , governor , cpus = None , ** kwargs ):
102107 """
103108 Use a given governor, then restore previous governor(s)
104109
@@ -111,66 +116,97 @@ def use_governor(self, governor, cpus=None, **kwargs):
111116 :Keyword Arguments: Governor tunables, See :meth:`set_governor_tunables`
112117 """
113118 if not cpus :
114- cpus = self .target .list_online_cpus ()
115-
116- # Setting a governor & tunables for a cpu will set them for all cpus
117- # in the same clock domain, so only manipulating one cpu per domain
118- # is enough
119- domains = set (self .get_affected_cpus (cpu )[0 ] for cpu in cpus )
120- prev_governors = {cpu : (self .get_governor (cpu ), self .get_governor_tunables (cpu ))
121- for cpu in domains }
122-
123- # Special case for userspace, frequency is not seen as a tunable
124- userspace_freqs = {}
125- for cpu , (prev_gov , _ ) in prev_governors .items ():
126- if prev_gov == "userspace" :
127- userspace_freqs [cpu ] = self .get_frequency (cpu )
128-
129- for cpu in domains :
130- self .set_governor (cpu , governor , ** kwargs )
119+ cpus = await self .target .list_online_cpus .asyn ()
120+
121+ async def get_cpu_info (cpu ):
122+ return await asyn .parallel ((
123+ self .get_affected_cpus .asyn (cpu ),
124+ self .get_governor .asyn (cpu ),
125+ self .get_governor_tunables .asyn (cpu ),
126+ # We won't always use the frequency, but it's much quicker to
127+ # do in parallel at the same time anyway so do it now
128+ self .get_frequency .asyn (cpu ),
129+ ))
130+
131+ cpus_infos = await asyn .parallel_dict (cpus , get_cpu_info )
132+
133+ # Setting a governor & tunables for a cpu will set them for all cpus in
134+ # the same cpufreq policy, so only manipulating one cpu per domain is
135+ # enough
136+ domains = set (
137+ info [0 ][0 ]
138+ for info in cpus_infos .values ()
139+ )
140+
141+ await asyn .parallel (
142+ self .set_governor .asyn (cpu , governor , ** kwargs )
143+ for cpu in domains
144+ )
131145
132146 try :
133147 yield
134-
135148 finally :
136- for cpu , (prev_gov , tunables ) in prev_governors .items ():
137- self .set_governor (cpu , prev_gov , ** tunables )
149+ async def set_gov (cpu ):
150+ domain , prev_gov , tunables , freq = cpus_infos [cpu ]
151+ await self .set_governor .asyn (cpu , prev_gov , ** tunables )
152+ # Special case for userspace, frequency is not seen as a tunable
138153 if prev_gov == "userspace" :
139- self .set_frequency (cpu , userspace_freqs [cpu ])
154+ await self .set_frequency .asyn (cpu , freq )
155+
156+ await asyn .parallel (
157+ set_gov (cpu )
158+ for cpu in domains
159+ )
140160
141- def list_governor_tunables (self , cpu ):
161+ @asyn .asyncf
162+ async def list_governor_tunables (self , cpu ):
142163 """Returns a list of tunables available for the governor on the specified CPU."""
143164 if isinstance (cpu , int ):
144165 cpu = 'cpu{}' .format (cpu )
145- governor = self .get_governor (cpu )
166+ governor = await self .get_governor . asyn (cpu )
146167 if governor not in self ._governor_tunables :
147168 try :
148169 tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}' .format (cpu , governor )
149- self ._governor_tunables [governor ] = self .target .list_directory (tunables_path )
170+ self ._governor_tunables [governor ] = await self .target .list_directory . asyn (tunables_path )
150171 except TargetStableError : # probably an older kernel
151172 try :
152173 tunables_path = '/sys/devices/system/cpu/cpufreq/{}' .format (governor )
153- self ._governor_tunables [governor ] = self .target .list_directory (tunables_path )
174+ self ._governor_tunables [governor ] = await self .target .list_directory . asyn (tunables_path )
154175 except TargetStableError : # governor does not support tunables
155176 self ._governor_tunables [governor ] = []
156177 return self ._governor_tunables [governor ]
157178
158- def get_governor_tunables (self , cpu ):
179+ @asyn .asyncf
180+ async def get_governor_tunables (self , cpu ):
159181 if isinstance (cpu , int ):
160182 cpu = 'cpu{}' .format (cpu )
161- governor = self .get_governor (cpu )
183+ governor , tunable_list = await asyn .parallel ((
184+ self .get_governor .asyn (cpu ),
185+ self .list_governor_tunables .asyn (cpu )
186+ ))
187+
188+ write_only = set (WRITE_ONLY_TUNABLES .get (governor , []))
189+ tunable_list = [
190+ tunable
191+ for tunable in tunable_list
192+ if tunable not in write_only
193+ ]
194+
162195 tunables = {}
163- for tunable in self .list_governor_tunables (cpu ):
164- if tunable not in WRITE_ONLY_TUNABLES .get (governor , []):
165- try :
166- path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}' .format (cpu , governor , tunable )
167- tunables [tunable ] = self .target .read_value (path )
168- except TargetStableError : # May be an older kernel
169- path = '/sys/devices/system/cpu/cpufreq/{}/{}' .format (governor , tunable )
170- tunables [tunable ] = self .target .read_value (path )
196+ async def get_tunable (tunable ):
197+ try :
198+ path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}' .format (cpu , governor , tunable )
199+ x = await self .target .read_value .asyn (path )
200+ except TargetStableError : # May be an older kernel
201+ path = '/sys/devices/system/cpu/cpufreq/{}/{}' .format (governor , tunable )
202+ x = await self .target .read_value .asyn (path )
203+ return x
204+
205+ tunables = await asyn .parallel_dict (tunable_list , get_tunable )
171206 return tunables
172207
173- def set_governor_tunables (self , cpu , governor = None , ** kwargs ):
208+ @asyn .asyncf
209+ async def set_governor_tunables (self , cpu , governor = None , ** kwargs ):
174210 """
175211 Set tunables for the specified governor. Tunables should be specified as
176212 keyword arguments. Which tunables and values are valid depends on the
@@ -191,20 +227,20 @@ def set_governor_tunables(self, cpu, governor=None, **kwargs):
191227 if isinstance (cpu , int ):
192228 cpu = 'cpu{}' .format (cpu )
193229 if governor is None :
194- governor = self .get_governor (cpu )
195- valid_tunables = self .list_governor_tunables (cpu )
230+ governor = await self .get_governor . asyn (cpu )
231+ valid_tunables = await self .list_governor_tunables . asyn (cpu )
196232 for tunable , value in kwargs .items ():
197233 if tunable in valid_tunables :
198234 path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}' .format (cpu , governor , tunable )
199235 try :
200- self .target .write_value (path , value )
236+ await self .target .write_value . asyn (path , value )
201237 except TargetStableError :
202- if self .target .file_exists (path ):
238+ if await self .target .file_exists . asyn (path ):
203239 # File exists but we did something wrong
204240 raise
205241 # Expected file doesn't exist, try older sysfs layout.
206242 path = '/sys/devices/system/cpu/cpufreq/{}/{}' .format (governor , tunable )
207- self .target .write_value (path , value )
243+ await self .target .write_value . asyn (path , value )
208244 else :
209245 message = 'Unexpected tunable {} for governor {} on {}.\n ' .format (tunable , governor , cpu )
210246 message += 'Available tunables are: {}' .format (valid_tunables )
@@ -301,7 +337,8 @@ def set_min_frequency(self, cpu, frequency, exact=True):
301337 except ValueError :
302338 raise ValueError ('Frequency must be an integer; got: "{}"' .format (frequency ))
303339
304- def get_frequency (self , cpu , cpuinfo = False ):
340+ @asyn .asyncf
341+ async def get_frequency (self , cpu , cpuinfo = False ):
305342 """
306343 Returns the current frequency currently set for the specified CPU.
307344
@@ -321,9 +358,10 @@ def get_frequency(self, cpu, cpuinfo=False):
321358 sysfile = '/sys/devices/system/cpu/{}/cpufreq/{}' .format (
322359 cpu ,
323360 'cpuinfo_cur_freq' if cpuinfo else 'scaling_cur_freq' )
324- return self .target .read_int (sysfile )
361+ return await self .target .read_int . asyn (sysfile )
325362
326- def set_frequency (self , cpu , frequency , exact = True ):
363+ @asyn .asyncf
364+ async def set_frequency (self , cpu , frequency , exact = True ):
327365 """
328366 Set's the minimum value for CPU frequency. Actual frequency will
329367 depend on the Governor used and may vary during execution. The value should be
@@ -347,16 +385,16 @@ def set_frequency(self, cpu, frequency, exact=True):
347385 try :
348386 value = int (frequency )
349387 if exact :
350- available_frequencies = self .list_frequencies (cpu )
388+ available_frequencies = await self .list_frequencies . asyn (cpu )
351389 if available_frequencies and value not in available_frequencies :
352390 raise TargetStableError ('Can\' t set {} frequency to {}\n must be in {}' .format (cpu ,
353391 value ,
354392 available_frequencies ))
355- if self .get_governor (cpu ) != 'userspace' :
393+ if await self .get_governor . asyn (cpu ) != 'userspace' :
356394 raise TargetStableError ('Can\' t set {} frequency; governor must be "userspace"' .format (cpu ))
357395 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed' .format (cpu )
358- self .target .write_value (sysfile , value , verify = False )
359- cpuinfo = self .get_frequency (cpu , cpuinfo = True )
396+ await self .target .write_value . asyn (sysfile , value , verify = False )
397+ cpuinfo = await self .get_frequency . asyn (cpu , cpuinfo = True )
360398 if cpuinfo != value :
361399 self .logger .warning (
362400 'The cpufreq value has not been applied properly cpuinfo={} request={}' .format (cpuinfo , value ))
@@ -495,7 +533,8 @@ def trace_frequencies(self):
495533 # pylint: disable=protected-access
496534 return self .target ._execute_util ('cpufreq_trace_all_frequencies' , as_root = True )
497535
498- def get_affected_cpus (self , cpu ):
536+ @asyn .asyncf
537+ async def get_affected_cpus (self , cpu ):
499538 """
500539 Get the online CPUs that share a frequency domain with the given CPU
501540 """
@@ -504,7 +543,8 @@ def get_affected_cpus(self, cpu):
504543
505544 sysfile = '/sys/devices/system/cpu/{}/cpufreq/affected_cpus' .format (cpu )
506545
507- return [int (c ) for c in self .target .read_value (sysfile ).split ()]
546+ content = await self .target .read_value .asyn (sysfile )
547+ return [int (c ) for c in content .split ()]
508548
509549 @memoized
510550 def get_related_cpus (self , cpu ):
0 commit comments