Skip to content

Commit fcd8d4b

Browse files
utils/async: Add new utils.async module
Home for async-related utilities.
1 parent 2c1bb42 commit fcd8d4b

File tree

4 files changed

+348
-76
lines changed

4 files changed

+348
-76
lines changed

devlib/module/cpufreq.py

Lines changed: 100 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
# limitations under the License.
1414
#
1515
from contextlib import contextmanager
16+
from operator import itemgetter
1617

1718
from devlib.module import Module
1819
from devlib.exception import TargetStableError
1920
from 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 {}\nmust 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

Comments
 (0)