blob: 466423effb147cd8bf6fe7338d62294012f41949 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001# Copyright 2014-2015 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15# pylint: disable=attribute-defined-outside-init
16import logging
17from collections import namedtuple
18
19from devlib.module import Module
20from devlib.exception import TargetError
21from devlib.utils.misc import list_to_ranges, isiterable
22from devlib.utils.types import boolean
23
24
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000025class Controller(object):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010026
Patrick Bellasib569a562016-11-29 10:39:28 +000027 def __init__(self, kind, hid, clist):
28 """
29 Initialize a controller given the hierarchy it belongs to.
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010030
Patrick Bellasib569a562016-11-29 10:39:28 +000031 :param kind: the name of the controller
32 :type kind: str
33
34 :param hid: the Hierarchy ID this controller is mounted on
35 :type hid: int
36
37 :param clist: the list of controller mounted in the same hierarchy
38 :type clist: list(str)
39 """
40 self.mount_name = 'devlib_cgh{}'.format(hid)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000041 self.kind = kind
Patrick Bellasib569a562016-11-29 10:39:28 +000042 self.hid = hid
43 self.clist = clist
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000044 self.target = None
Patrick Bellasi15f9c032016-04-26 15:23:56 +010045 self._noprefix = False
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000046
Patrick Bellasib569a562016-11-29 10:39:28 +000047 self.logger = logging.getLogger('CGroup.'+self.kind)
48 self.logger.debug('Initialized [%s, %d, %s]',
49 self.kind, self.hid, self.clist)
50
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010051 self.mount_point = None
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000052 self._cgroups = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010053
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000054 def mount(self, target, mount_root):
55
56 mounted = target.list_file_systems()
57 if self.mount_name in [e.device for e in mounted]:
58 # Identify mount point if controller is already in use
59 self.mount_point = [
60 fs.mount_point
61 for fs in mounted
62 if fs.device == self.mount_name
63 ][0]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010064 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000065 # Mount the controller if not already in use
66 self.mount_point = target.path.join(mount_root, self.mount_name)
67 target.execute('mkdir -p {} 2>/dev/null'\
68 .format(self.mount_point), as_root=True)
69 target.execute('mount -t cgroup -o {} {} {}'\
Patrick Bellasib569a562016-11-29 10:39:28 +000070 .format(','.join(self.clist),
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000071 self.mount_name,
72 self.mount_point),
73 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010074
Patrick Bellasi15f9c032016-04-26 15:23:56 +010075 # Check if this controller uses "noprefix" option
76 output = target.execute('mount | grep "{} "'.format(self.mount_name))
77 if 'noprefix' in output:
78 self._noprefix = True
79 # self.logger.debug('Controller %s using "noprefix" option',
80 # self.kind)
81
82 self.logger.debug('Controller %s mounted under: %s (noprefix=%s)',
83 self.kind, self.mount_point, self._noprefix)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010084
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000085 # Mark this contoller as available
86 self.target = target
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010087
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000088 # Create root control group
89 self.cgroup('/')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010090
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000091 def cgroup(self, name):
92 if not self.target:
93 raise RuntimeError('CGroup creation failed: {} controller not mounted'\
94 .format(self.kind))
95 if name not in self._cgroups:
96 self._cgroups[name] = CGroup(self, name)
97 return self._cgroups[name]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010098
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000099 def exists(self, name):
100 if not self.target:
101 raise RuntimeError('CGroup creation failed: {} controller not mounted'\
102 .format(self.kind))
103 if name not in self._cgroups:
104 self._cgroups[name] = CGroup(self, name, create=False)
105 return self._cgroups[name].existe()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100106
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000107 def list_all(self):
108 self.logger.debug('Listing groups for %s controller', self.kind)
109 output = self.target.execute('{} find {} -type d'\
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100110 .format(self.target.busybox, self.mount_point),
111 as_root=True)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000112 cgroups = []
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000113 for cg in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000114 cg = cg.replace(self.mount_point + '/', '/')
115 cg = cg.replace(self.mount_point, '/')
116 cg = cg.strip()
117 if cg == '':
118 continue
119 self.logger.debug('Populate %s cgroup: %s', self.kind, cg)
120 cgroups.append(cg)
121 return cgroups
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100122
Patrick Bellasi3cab7862016-08-26 16:27:23 +0100123 def move_tasks(self, source, dest, exclude=[]):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100124 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000125 srcg = self._cgroups[source]
126 dstg = self._cgroups[dest]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100127 except KeyError as e:
128 raise ValueError('Unkown group: {}'.format(e))
Patrick Bellasi3cab7862016-08-26 16:27:23 +0100129 output = self.target._execute_util(
130 'cgroups_tasks_move {} {} \'{}\''.format(
131 srcg.directory, dstg.directory, exclude),
132 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100133
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100134 def move_all_tasks_to(self, dest, exclude=[]):
135 """
136 Move all the tasks to the specified CGroup
137
138 Tasks are moved from all their original CGroup the the specified on.
139 The tasks which name matches one of the string in exclude are moved
140 instead in the root CGroup for the controller.
141 The name of a tasks to exclude must be a substring of the task named as
142 reported by the "ps" command. Indeed, this list will be translated into
143 a: "ps | grep -e name1 -e name2..." in order to obtain the PID of these
144 tasks.
145
146 :param exclude: list of commands to keep in the root CGroup
147 :type exlude: list(str)
148 """
149
150 if isinstance(exclude, str):
151 exclude = [exclude]
152 if not isinstance(exclude, list):
153 raise ValueError('wrong type for "exclude" parameter, '
154 'it must be a str or a list')
155
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100156 logging.debug('Moving all tasks into %s', dest)
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100157
158 # Build list of tasks to exclude
159 grep_filters = ''
160 for comm in exclude:
Brendan Jackman3dd4ea62016-11-25 16:04:34 +0000161 grep_filters += '-e {} '.format(comm)
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100162 logging.debug(' using grep filter: %s', grep_filters)
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100163 if grep_filters != '':
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100164 logging.debug(' excluding tasks which name matches:')
165 logging.debug(' %s', ', '.join(exclude))
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100166
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000167 for cgroup in self._cgroups:
168 if cgroup != dest:
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100169 self.move_tasks(cgroup, dest, grep_filters)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100170
Patrick Bellasi42efd0a2016-08-26 16:25:43 +0100171 def tasks(self, cgroup):
172 try:
173 cg = self._cgroups[cgroup]
174 except KeyError as e:
175 raise ValueError('Unkown group: {}'.format(e))
176 output = self.target._execute_util(
177 'cgroups_tasks_in {}'.format(cg.directory),
178 as_root=True)
179 entries = output.splitlines()
180 tasks = {}
181 for task in entries:
182 tid = task.split(',')[0]
183 try:
184 tname = task.split(',')[1]
185 except: continue
186 try:
187 tcmdline = task.split(',')[2]
188 except:
189 tcmdline = ''
190 tasks[int(tid)] = (tname, tcmdline)
191 return tasks
192
Patrick Bellasid8ae3ab2016-08-26 16:28:45 +0100193 def tasks_count(self, cgroup):
194 try:
195 cg = self._cgroups[cgroup]
196 except KeyError as e:
197 raise ValueError('Unkown group: {}'.format(e))
198 output = self.target.execute(
199 '{} wc -l {}/tasks'.format(
200 self.target.busybox, cg.directory),
201 as_root=True)
202 return int(output.split()[0])
203
204 def tasks_per_group(self):
205 tasks = {}
206 for cg in self.list_all():
207 tasks[cg] = self.tasks_count(cg)
208 return tasks
209
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000210class CGroup(object):
211
212 def __init__(self, controller, name, create=True):
213 self.logger = logging.getLogger('cgroups.' + controller.kind)
214 self.target = controller.target
215 self.controller = controller
216 self.name = name
217
218 # Control cgroup path
219 self.directory = controller.mount_point
220 if name != '/':
221 self.directory = self.target.path.join(controller.mount_point, name[1:])
222
223 # Setup path for tasks file
224 self.tasks_file = self.target.path.join(self.directory, 'tasks')
225 self.procs_file = self.target.path.join(self.directory, 'cgroup.procs')
226
227 if not create:
228 return
229
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000230 self.logger.debug('Creating cgroup %s', self.directory)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000231 self.target.execute('[ -d {0} ] || mkdir -p {0}'\
232 .format(self.directory), as_root=True)
233
234 def exists(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100235 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000236 self.target.execute('[ -d {0} ]'\
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100237 .format(self.directory), as_root=True)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000238 return True
239 except TargetError:
240 return False
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100241
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000242 def get(self):
243 conf = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100244
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000245 logging.debug('Reading %s attributes from:',
246 self.controller.kind)
247 logging.debug(' %s',
248 self.directory)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000249 output = self.target._execute_util(
250 'cgroups_get_attributes {} {}'.format(
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100251 self.directory, self.controller.kind),
252 as_root=True)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000253 for res in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000254 attr = res.split(':')[0]
255 value = res.split(':')[1]
256 conf[attr] = value
257
258 return conf
259
260 def set(self, **attrs):
261 for idx in attrs:
262 if isiterable(attrs[idx]):
263 attrs[idx] = list_to_ranges(attrs[idx])
264 # Build attribute path
Patrick Bellasi658005a2016-04-26 15:26:44 +0100265 if self.controller._noprefix:
266 attr_name = '{}'.format(idx)
267 else:
268 attr_name = '{}.{}'.format(self.controller.kind, idx)
269 path = self.target.path.join(self.directory, attr_name)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000270
271 self.logger.debug('Set attribute [%s] to: %s"',
272 path, attrs[idx])
273
274 # Set the attribute value
Patrick Bellasi658005a2016-04-26 15:26:44 +0100275 try:
276 self.target.write_value(path, attrs[idx])
277 except TargetError:
278 # Check if the error is due to a non-existing attribute
279 attrs = self.get()
280 if idx not in attrs:
281 raise ValueError('Controller [{}] does not provide attribute [{}]'\
282 .format(self.controller.kind, attr_name))
283 raise
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000284
285 def get_tasks(self):
286 task_ids = self.target.read_value(self.tasks_file).split()
287 logging.debug('Tasks: %s', task_ids)
288 return map(int, task_ids)
289
290 def add_task(self, tid):
291 self.target.write_value(self.tasks_file, tid, verify=False)
292
293 def add_tasks(self, tasks):
294 for tid in tasks:
295 self.add_task(tid)
296
297 def add_proc(self, pid):
298 self.target.write_value(self.procs_file, pid, verify=False)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100299
300CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled')
301
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100302class CgroupsModule(Module):
303
304 name = 'cgroups'
Patrick Bellasi616f2292016-05-13 18:17:22 +0100305 stage = 'setup'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100306
307 @staticmethod
308 def probe(target):
Patrick Bellasi4b58c572016-04-26 15:33:23 +0100309 if not target.is_rooted:
310 return False
311 if target.file_exists('/proc/cgroups'):
312 return True
313 return target.config.has('cgroups')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100314
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100315 def __init__(self, target):
316 super(CgroupsModule, self).__init__(target)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000317
318 self.logger = logging.getLogger('CGroups')
319
Patrick Bellasib2ec9572016-11-29 10:02:57 +0000320 # Set Devlib's CGroups mount point
321 self.cgroup_root = target.path.join(
322 target.working_directory, 'cgroups')
323
Patrick Bellasi103f7922016-11-29 10:13:01 +0000324 # Get the list of the available controllers
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000325 subsys = self.list_subsystems()
Patrick Bellasi103f7922016-11-29 10:13:01 +0000326 if len(subsys) == 0:
327 self.logger.warning('No CGroups controller available')
328 return
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000329
Patrick Bellasib569a562016-11-29 10:39:28 +0000330 # Map hierarchy IDs into a list of controllers
331 hierarchy = {}
332 for ss in subsys:
333 try:
334 hierarchy[ss.hierarchy].append(ss.name)
335 except KeyError:
336 hierarchy[ss.hierarchy] = [ss.name]
337 self.logger.debug('Available hierarchies: %s', hierarchy)
338
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000339 # Initialize controllers
Patrick Bellasi103f7922016-11-29 10:13:01 +0000340 self.logger.info('Available controllers:')
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000341 self.controllers = {}
Patrick Bellasi103f7922016-11-29 10:13:01 +0000342 for ss in subsys:
Patrick Bellasib569a562016-11-29 10:39:28 +0000343 hid = ss.hierarchy
344 controller = Controller(ss.name, hid, hierarchy[hid])
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000345 try:
Patrick Bellasib2ec9572016-11-29 10:02:57 +0000346 controller.mount(self.target, self.cgroup_root)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000347 except TargetError:
Patrick Bellasib569a562016-11-29 10:39:28 +0000348 message = 'Failed to mount "{}" controller'
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000349 raise TargetError(message.format(controller.kind))
Patrick Bellasib569a562016-11-29 10:39:28 +0000350 self.logger.info(' %-12s : %s', controller.kind,
351 controller.mount_point)
Patrick Bellasi103f7922016-11-29 10:13:01 +0000352 self.controllers[ss.name] = controller
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100353
354 def list_subsystems(self):
355 subsystems = []
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000356 for line in self.target.execute('{} cat /proc/cgroups'\
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000357 .format(self.target.busybox)).splitlines()[1:]:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100358 line = line.strip()
359 if not line or line.startswith('#'):
360 continue
361 name, hierarchy, num_cgroups, enabled = line.split()
362 subsystems.append(CgroupSubsystemEntry(name,
363 int(hierarchy),
364 int(num_cgroups),
365 boolean(enabled)))
366 return subsystems
367
368
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000369 def controller(self, kind):
370 if kind not in self.controllers:
371 self.logger.warning('Controller %s not available', kind)
372 return None
373 return self.controllers[kind]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100374
Patrick Bellasib2ec9572016-11-29 10:02:57 +0000375 def run_into_cmd(self, cgroup, cmdline):
376 return 'CGMOUNT={} {} cgroups_run_into {} {}'\
377 .format(self.cgroup_root, self.target.shutils,
378 cgroup, cmdline)
379
Patrick Bellasi28739392016-04-22 11:43:49 +0100380 def run_into(self, cgroup, cmdline):
381 """
382 Run the specified command into the specified CGroup
383 """
384 return self.target._execute_util(
Patrick Bellasib2ec9572016-11-29 10:02:57 +0000385 'CGMOUNT={} cgroups_run_into {} {}'\
386 .format(self.cgroup_root, cgroup, cmdline),
Patrick Bellasi28739392016-04-22 11:43:49 +0100387 as_root=True)
388
389
390 def cgroups_tasks_move(self, srcg, dstg, exclude=''):
391 """
392 Move all the tasks from the srcg CGroup to the dstg one.
393 A regexps of tasks names can be used to defined tasks which should not
394 be moved.
395 """
396 return self.target._execute_util(
397 'cgroups_tasks_move {} {} {}'.format(srcg, dstg, exclude),
398 as_root=True)
399
Patrick Bellasi75a086d2016-08-26 16:29:43 +0100400 def isolate(self, cpus, exclude=[]):
401 """
402 Remove all userspace tasks from specified CPUs.
403
404 A list of CPUs can be specified where we do not want userspace tasks
405 running. This functions creates a sandbox cpuset CGroup where all
406 user-space tasks and not-pinned kernel-space tasks are moved into.
407 This should allows to isolate the specified CPUs which will not get
408 tasks running unless explicitely moved into the isolated group.
409
410 :param cpus: the list of CPUs to isolate
411 :type cpus: list(int)
412
413 :return: the (sandbox, isolated) tuple, where:
414 sandbox is the CGroup of sandboxed CPUs
415 isolated is the CGroup of isolated CPUs
416 """
417 all_cpus = set(range(self.target.number_of_cpus))
418 sbox_cpus = list(all_cpus - set(cpus))
419 isol_cpus = list(all_cpus - set(sbox_cpus))
420
421 # Create Sandbox and Isolated cpuset CGroups
422 cpuset = self.controller('cpuset')
423 sbox_cg = cpuset.cgroup('/DEVLIB_SBOX')
424 isol_cg = cpuset.cgroup('/DEVLIB_ISOL')
425
426 # Set CPUs for Sandbox and Isolated CGroups
427 sbox_cg.set(cpus=sbox_cpus, mems=0)
428 isol_cg.set(cpus=isol_cpus, mems=0)
429
430 # Move all currently running tasks to the Sandbox CGroup
431 cpuset.move_all_tasks_to('/DEVLIB_SBOX', exclude)
432
433 return sbox_cg, isol_cg
434
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100435 def freeze(self, exclude=[], thaw=False):
436 """
Patrick Bellasi730bb602016-08-31 11:40:10 +0100437 Freeze all user-space tasks but the specified ones
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100438
439 A freezer cgroup is used to stop all the tasks in the target system but
440 the ones which name match one of the path specified by the exclude
441 paramater. The name of a tasks to exclude must be a substring of the
442 task named as reported by the "ps" command. Indeed, this list will be
443 translated into a: "ps | grep -e name1 -e name2..." in order to obtain
444 the PID of these tasks.
445
446 :param exclude: list of commands paths to exclude from freezer
Patrick Bellasi730bb602016-08-31 11:40:10 +0100447 :type exclude: list(str)
448
449 :param thaw: if true thaw tasks instead
450 :type thaw: bool
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100451 """
452
453 # Create Freezer CGroup
454 freezer = self.controller('freezer')
Brendan Jackmanc89f7122016-11-25 11:56:42 +0000455 if freezer is None:
456 raise RuntimeError('freezer cgroup controller not present')
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100457 freezer_cg = freezer.cgroup('/DEVLIB_FREEZER')
458 thawed_cg = freezer.cgroup('/')
459
460 if thaw:
461 # Restart froozen tasks
462 freezer_cg.set(state='THAWED')
463 # Remove all tasks from freezer
464 freezer.move_all_tasks_to('/')
465 return
466
467 # Move all tasks into the freezer group
468 freezer.move_all_tasks_to('/DEVLIB_FREEZER', exclude)
469
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100470 # Get list of not frozen tasks, which is reported as output
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100471 tasks = freezer.tasks('/')
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100472
473 # Freeze all tasks
474 freezer_cg.set(state='FROZEN')
475
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100476 return tasks
477