blob: b146abb10b1dc440116af8c8da00d6f9ce7ea483 [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
153 logging.info('Moving all tasks into %s', dest)
154
155 # Build list of tasks to exclude
156 grep_filters = ''
157 for comm in exclude:
158 grep_filters += '-e "{}" '.format(comm)
159 logging.debug('Using grep filter: %s', grep_filters)
160 if grep_filters != '':
161 logging.info('Excluding tasks which name matches:')
162 logging.info('%s', ','.join(exclude))
163
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
329 # Load list of available controllers
330 controllers = []
331 subsys = self.list_subsystems()
332 for (n, h, c, e) in subsys:
333 controllers.append(n)
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000334 self.logger.debug('Available controllers: %s', controllers)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000335
336 # Initialize controllers
337 self.controllers = {}
338 for idx in controllers:
339 controller = Controller(idx)
340 self.logger.debug('Init %s controller...', controller.kind)
341 if not controller.probe(self.target):
342 continue
343 try:
344 controller.mount(self.target, self.cgroup_root)
345 except TargetError:
346 message = 'cgroups {} controller is not supported by the target'
347 raise TargetError(message.format(controller.kind))
348 self.logger.debug('Controller %s enabled', controller.kind)
349 self.controllers[idx] = controller
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100350
351 def list_subsystems(self):
352 subsystems = []
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000353 for line in self.target.execute('{} cat /proc/cgroups'\
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000354 .format(self.target.busybox)).splitlines()[1:]:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100355 line = line.strip()
356 if not line or line.startswith('#'):
357 continue
358 name, hierarchy, num_cgroups, enabled = line.split()
359 subsystems.append(CgroupSubsystemEntry(name,
360 int(hierarchy),
361 int(num_cgroups),
362 boolean(enabled)))
363 return subsystems
364
365
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000366 def controller(self, kind):
367 if kind not in self.controllers:
368 self.logger.warning('Controller %s not available', kind)
369 return None
370 return self.controllers[kind]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100371
Patrick Bellasi28739392016-04-22 11:43:49 +0100372 def run_into(self, cgroup, cmdline):
373 """
374 Run the specified command into the specified CGroup
375 """
376 return self.target._execute_util(
377 'cgroups_run_into {} {}'.format(cgroup, cmdline),
378 as_root=True)
379
380
381 def cgroups_tasks_move(self, srcg, dstg, exclude=''):
382 """
383 Move all the tasks from the srcg CGroup to the dstg one.
384 A regexps of tasks names can be used to defined tasks which should not
385 be moved.
386 """
387 return self.target._execute_util(
388 'cgroups_tasks_move {} {} {}'.format(srcg, dstg, exclude),
389 as_root=True)
390