blob: dd514d9cba3b2442a9c0fe50c14f8ccdf3b5b4b8 [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
27 def __new__(cls, arg):
28 if isinstance(arg, cls):
29 return arg
30 else:
31 return object.__new__(cls, arg)
32
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000033 def __init__(self, kind):
34 self.mount_name = 'devlib_'+kind
35 self.kind = kind
36 self.target = None
Patrick Bellasi15f9c032016-04-26 15:23:56 +010037 self._noprefix = False
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000038
39 self.logger = logging.getLogger('cgroups.'+self.kind)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010040 self.mount_point = None
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000041 self._cgroups = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010042
43 def probe(self, target):
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000044 try:
45 exists = target.execute('{} grep {} /proc/cgroups'\
46 .format(target.busybox, self.kind))
47 except TargetError:
48 return False
49 return True
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010050
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000051 def mount(self, target, mount_root):
52
53 mounted = target.list_file_systems()
54 if self.mount_name in [e.device for e in mounted]:
55 # Identify mount point if controller is already in use
56 self.mount_point = [
57 fs.mount_point
58 for fs in mounted
59 if fs.device == self.mount_name
60 ][0]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010061 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000062 # Mount the controller if not already in use
63 self.mount_point = target.path.join(mount_root, self.mount_name)
64 target.execute('mkdir -p {} 2>/dev/null'\
65 .format(self.mount_point), as_root=True)
66 target.execute('mount -t cgroup -o {} {} {}'\
67 .format(self.kind,
68 self.mount_name,
69 self.mount_point),
70 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010071
Patrick Bellasi15f9c032016-04-26 15:23:56 +010072 # Check if this controller uses "noprefix" option
73 output = target.execute('mount | grep "{} "'.format(self.mount_name))
74 if 'noprefix' in output:
75 self._noprefix = True
76 # self.logger.debug('Controller %s using "noprefix" option',
77 # self.kind)
78
79 self.logger.debug('Controller %s mounted under: %s (noprefix=%s)',
80 self.kind, self.mount_point, self._noprefix)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010081
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000082 # Mark this contoller as available
83 self.target = target
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010084
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000085 # Create root control group
86 self.cgroup('/')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010087
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000088 def cgroup(self, name):
89 if not self.target:
90 raise RuntimeError('CGroup creation failed: {} controller not mounted'\
91 .format(self.kind))
92 if name not in self._cgroups:
93 self._cgroups[name] = CGroup(self, name)
94 return self._cgroups[name]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010095
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000096 def exists(self, name):
97 if not self.target:
98 raise RuntimeError('CGroup creation failed: {} controller not mounted'\
99 .format(self.kind))
100 if name not in self._cgroups:
101 self._cgroups[name] = CGroup(self, name, create=False)
102 return self._cgroups[name].existe()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100103
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000104 def list_all(self):
105 self.logger.debug('Listing groups for %s controller', self.kind)
106 output = self.target.execute('{} find {} -type d'\
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100107 .format(self.target.busybox, self.mount_point),
108 as_root=True)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000109 cgroups = []
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000110 for cg in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000111 cg = cg.replace(self.mount_point + '/', '/')
112 cg = cg.replace(self.mount_point, '/')
113 cg = cg.strip()
114 if cg == '':
115 continue
116 self.logger.debug('Populate %s cgroup: %s', self.kind, cg)
117 cgroups.append(cg)
118 return cgroups
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100119
Patrick Bellasi3cab7862016-08-26 16:27:23 +0100120 def move_tasks(self, source, dest, exclude=[]):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100121 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000122 srcg = self._cgroups[source]
123 dstg = self._cgroups[dest]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100124 except KeyError as e:
125 raise ValueError('Unkown group: {}'.format(e))
Patrick Bellasi3cab7862016-08-26 16:27:23 +0100126 output = self.target._execute_util(
127 'cgroups_tasks_move {} {} \'{}\''.format(
128 srcg.directory, dstg.directory, exclude),
129 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100130
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100131 def move_all_tasks_to(self, dest, exclude=[]):
132 """
133 Move all the tasks to the specified CGroup
134
135 Tasks are moved from all their original CGroup the the specified on.
136 The tasks which name matches one of the string in exclude are moved
137 instead in the root CGroup for the controller.
138 The name of a tasks to exclude must be a substring of the task named as
139 reported by the "ps" command. Indeed, this list will be translated into
140 a: "ps | grep -e name1 -e name2..." in order to obtain the PID of these
141 tasks.
142
143 :param exclude: list of commands to keep in the root CGroup
144 :type exlude: list(str)
145 """
146
147 if isinstance(exclude, str):
148 exclude = [exclude]
149 if not isinstance(exclude, list):
150 raise ValueError('wrong type for "exclude" parameter, '
151 'it must be a str or a list')
152
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100153 logging.debug('Moving all tasks into %s', dest)
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100154
155 # Build list of tasks to exclude
156 grep_filters = ''
157 for comm in exclude:
158 grep_filters += '-e "{}" '.format(comm)
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100159 logging.debug(' using grep filter: %s', grep_filters)
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100160 if grep_filters != '':
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100161 logging.debug(' excluding tasks which name matches:')
162 logging.debug(' %s', ', '.join(exclude))
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100163
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000164 for cgroup in self._cgroups:
165 if cgroup != dest:
Patrick Bellasi21d18f82016-08-26 18:13:47 +0100166 self.move_tasks(cgroup, dest, grep_filters)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100167
Patrick Bellasi42efd0a2016-08-26 16:25:43 +0100168 def tasks(self, cgroup):
169 try:
170 cg = self._cgroups[cgroup]
171 except KeyError as e:
172 raise ValueError('Unkown group: {}'.format(e))
173 output = self.target._execute_util(
174 'cgroups_tasks_in {}'.format(cg.directory),
175 as_root=True)
176 entries = output.splitlines()
177 tasks = {}
178 for task in entries:
179 tid = task.split(',')[0]
180 try:
181 tname = task.split(',')[1]
182 except: continue
183 try:
184 tcmdline = task.split(',')[2]
185 except:
186 tcmdline = ''
187 tasks[int(tid)] = (tname, tcmdline)
188 return tasks
189
Patrick Bellasid8ae3ab2016-08-26 16:28:45 +0100190 def tasks_count(self, cgroup):
191 try:
192 cg = self._cgroups[cgroup]
193 except KeyError as e:
194 raise ValueError('Unkown group: {}'.format(e))
195 output = self.target.execute(
196 '{} wc -l {}/tasks'.format(
197 self.target.busybox, cg.directory),
198 as_root=True)
199 return int(output.split()[0])
200
201 def tasks_per_group(self):
202 tasks = {}
203 for cg in self.list_all():
204 tasks[cg] = self.tasks_count(cg)
205 return tasks
206
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000207class CGroup(object):
208
209 def __init__(self, controller, name, create=True):
210 self.logger = logging.getLogger('cgroups.' + controller.kind)
211 self.target = controller.target
212 self.controller = controller
213 self.name = name
214
215 # Control cgroup path
216 self.directory = controller.mount_point
217 if name != '/':
218 self.directory = self.target.path.join(controller.mount_point, name[1:])
219
220 # Setup path for tasks file
221 self.tasks_file = self.target.path.join(self.directory, 'tasks')
222 self.procs_file = self.target.path.join(self.directory, 'cgroup.procs')
223
224 if not create:
225 return
226
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000227 self.logger.debug('Creating cgroup %s', self.directory)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000228 self.target.execute('[ -d {0} ] || mkdir -p {0}'\
229 .format(self.directory), as_root=True)
230
231 def exists(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100232 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000233 self.target.execute('[ -d {0} ]'\
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100234 .format(self.directory), as_root=True)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000235 return True
236 except TargetError:
237 return False
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100238
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000239 def get(self):
240 conf = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100241
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000242 logging.debug('Reading %s attributes from:',
243 self.controller.kind)
244 logging.debug(' %s',
245 self.directory)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000246 output = self.target._execute_util(
247 'cgroups_get_attributes {} {}'.format(
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100248 self.directory, self.controller.kind),
249 as_root=True)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000250 for res in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000251 attr = res.split(':')[0]
252 value = res.split(':')[1]
253 conf[attr] = value
254
255 return conf
256
257 def set(self, **attrs):
258 for idx in attrs:
259 if isiterable(attrs[idx]):
260 attrs[idx] = list_to_ranges(attrs[idx])
261 # Build attribute path
Patrick Bellasi658005a2016-04-26 15:26:44 +0100262 if self.controller._noprefix:
263 attr_name = '{}'.format(idx)
264 else:
265 attr_name = '{}.{}'.format(self.controller.kind, idx)
266 path = self.target.path.join(self.directory, attr_name)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000267
268 self.logger.debug('Set attribute [%s] to: %s"',
269 path, attrs[idx])
270
271 # Set the attribute value
Patrick Bellasi658005a2016-04-26 15:26:44 +0100272 try:
273 self.target.write_value(path, attrs[idx])
274 except TargetError:
275 # Check if the error is due to a non-existing attribute
276 attrs = self.get()
277 if idx not in attrs:
278 raise ValueError('Controller [{}] does not provide attribute [{}]'\
279 .format(self.controller.kind, attr_name))
280 raise
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000281
282 def get_tasks(self):
283 task_ids = self.target.read_value(self.tasks_file).split()
284 logging.debug('Tasks: %s', task_ids)
285 return map(int, task_ids)
286
287 def add_task(self, tid):
288 self.target.write_value(self.tasks_file, tid, verify=False)
289
290 def add_tasks(self, tasks):
291 for tid in tasks:
292 self.add_task(tid)
293
294 def add_proc(self, pid):
295 self.target.write_value(self.procs_file, pid, verify=False)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100296
297CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled')
298
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100299class CgroupsModule(Module):
300
301 name = 'cgroups'
Patrick Bellasi616f2292016-05-13 18:17:22 +0100302 stage = 'setup'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100303 cgroup_root = '/sys/fs/cgroup'
304
305 @staticmethod
306 def probe(target):
Patrick Bellasi4b58c572016-04-26 15:33:23 +0100307 if not target.is_rooted:
308 return False
309 if target.file_exists('/proc/cgroups'):
310 return True
311 return target.config.has('cgroups')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100312
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100313 def __init__(self, target):
314 super(CgroupsModule, self).__init__(target)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000315
316 self.logger = logging.getLogger('CGroups')
317
318 # Initialize controllers mount point
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100319 mounted = self.target.list_file_systems()
320 if self.cgroup_root not in [e.mount_point for e in mounted]:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000321 self.target.execute('mount -t tmpfs {} {}'\
322 .format('cgroup_root',
323 self.cgroup_root),
324 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100325 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000326 self.logger.debug('cgroup_root already mounted at %s',
327 self.cgroup_root)
328
Patrick Bellasida128f92016-10-28 18:54:10 +0100329 # Ensure CGroups is mounted RW
330 self.target.execute('mount -o remount,rw {}'\
331 .format(self.cgroup_root),
332 as_root=True)
333
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000334 # Load list of available controllers
335 controllers = []
336 subsys = self.list_subsystems()
337 for (n, h, c, e) in subsys:
338 controllers.append(n)
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000339 self.logger.debug('Available controllers: %s', controllers)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000340
341 # Initialize controllers
342 self.controllers = {}
343 for idx in controllers:
344 controller = Controller(idx)
345 self.logger.debug('Init %s controller...', controller.kind)
346 if not controller.probe(self.target):
347 continue
348 try:
349 controller.mount(self.target, self.cgroup_root)
350 except TargetError:
351 message = 'cgroups {} controller is not supported by the target'
352 raise TargetError(message.format(controller.kind))
353 self.logger.debug('Controller %s enabled', controller.kind)
354 self.controllers[idx] = controller
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100355
356 def list_subsystems(self):
357 subsystems = []
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000358 for line in self.target.execute('{} cat /proc/cgroups'\
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000359 .format(self.target.busybox)).splitlines()[1:]:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100360 line = line.strip()
361 if not line or line.startswith('#'):
362 continue
363 name, hierarchy, num_cgroups, enabled = line.split()
364 subsystems.append(CgroupSubsystemEntry(name,
365 int(hierarchy),
366 int(num_cgroups),
367 boolean(enabled)))
368 return subsystems
369
370
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000371 def controller(self, kind):
372 if kind not in self.controllers:
373 self.logger.warning('Controller %s not available', kind)
374 return None
375 return self.controllers[kind]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100376
Patrick Bellasi28739392016-04-22 11:43:49 +0100377 def run_into(self, cgroup, cmdline):
378 """
379 Run the specified command into the specified CGroup
380 """
381 return self.target._execute_util(
382 'cgroups_run_into {} {}'.format(cgroup, cmdline),
383 as_root=True)
384
385
386 def cgroups_tasks_move(self, srcg, dstg, exclude=''):
387 """
388 Move all the tasks from the srcg CGroup to the dstg one.
389 A regexps of tasks names can be used to defined tasks which should not
390 be moved.
391 """
392 return self.target._execute_util(
393 'cgroups_tasks_move {} {} {}'.format(srcg, dstg, exclude),
394 as_root=True)
395
Patrick Bellasi75a086d2016-08-26 16:29:43 +0100396 def isolate(self, cpus, exclude=[]):
397 """
398 Remove all userspace tasks from specified CPUs.
399
400 A list of CPUs can be specified where we do not want userspace tasks
401 running. This functions creates a sandbox cpuset CGroup where all
402 user-space tasks and not-pinned kernel-space tasks are moved into.
403 This should allows to isolate the specified CPUs which will not get
404 tasks running unless explicitely moved into the isolated group.
405
406 :param cpus: the list of CPUs to isolate
407 :type cpus: list(int)
408
409 :return: the (sandbox, isolated) tuple, where:
410 sandbox is the CGroup of sandboxed CPUs
411 isolated is the CGroup of isolated CPUs
412 """
413 all_cpus = set(range(self.target.number_of_cpus))
414 sbox_cpus = list(all_cpus - set(cpus))
415 isol_cpus = list(all_cpus - set(sbox_cpus))
416
417 # Create Sandbox and Isolated cpuset CGroups
418 cpuset = self.controller('cpuset')
419 sbox_cg = cpuset.cgroup('/DEVLIB_SBOX')
420 isol_cg = cpuset.cgroup('/DEVLIB_ISOL')
421
422 # Set CPUs for Sandbox and Isolated CGroups
423 sbox_cg.set(cpus=sbox_cpus, mems=0)
424 isol_cg.set(cpus=isol_cpus, mems=0)
425
426 # Move all currently running tasks to the Sandbox CGroup
427 cpuset.move_all_tasks_to('/DEVLIB_SBOX', exclude)
428
429 return sbox_cg, isol_cg
430
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100431 def freeze(self, exclude=[], thaw=False):
432 """
Patrick Bellasi730bb602016-08-31 11:40:10 +0100433 Freeze all user-space tasks but the specified ones
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100434
435 A freezer cgroup is used to stop all the tasks in the target system but
436 the ones which name match one of the path specified by the exclude
437 paramater. The name of a tasks to exclude must be a substring of the
438 task named as reported by the "ps" command. Indeed, this list will be
439 translated into a: "ps | grep -e name1 -e name2..." in order to obtain
440 the PID of these tasks.
441
442 :param exclude: list of commands paths to exclude from freezer
Patrick Bellasi730bb602016-08-31 11:40:10 +0100443 :type exclude: list(str)
444
445 :param thaw: if true thaw tasks instead
446 :type thaw: bool
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100447 """
448
449 # Create Freezer CGroup
450 freezer = self.controller('freezer')
Brendan Jackmanc89f7122016-11-25 11:56:42 +0000451 if freezer is None:
452 raise RuntimeError('freezer cgroup controller not present')
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100453 freezer_cg = freezer.cgroup('/DEVLIB_FREEZER')
454 thawed_cg = freezer.cgroup('/')
455
456 if thaw:
457 # Restart froozen tasks
458 freezer_cg.set(state='THAWED')
459 # Remove all tasks from freezer
460 freezer.move_all_tasks_to('/')
461 return
462
463 # Move all tasks into the freezer group
464 freezer.move_all_tasks_to('/DEVLIB_FREEZER', exclude)
465
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100466 # Get list of not frozen tasks, which is reported as output
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100467 tasks = freezer.tasks('/')
Patrick Bellasi23ad61f2016-08-26 18:14:37 +0100468
469 # Freeze all tasks
470 freezer_cg.set(state='FROZEN')
471
Patrick Bellasic8f118d2016-08-31 11:39:33 +0100472 return tasks
473