blob: c754914e37ea6f029a5dc4a5486942fe265a27fa [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
120 def move_tasks(self, source, dest):
121 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 command = 'for task in $(cat {}); do echo $task>{}; done'
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000125 self.target.execute(command.format(srcg.tasks_file, dstg.tasks_file),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100126 # this will always fail as some of the tasks
127 # are kthreads that cannot be migrated, but we
128 # don't care about those, so don't check exit
129 # code.
130 check_exit_code=False, as_root=True)
131 except KeyError as e:
132 raise ValueError('Unkown group: {}'.format(e))
133
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000134 def move_all_tasks_to(self, dest):
135 for cgroup in self._cgroups:
136 if cgroup != dest:
137 self.move_tasks(cgroup, dest)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100138
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000139class CGroup(object):
140
141 def __init__(self, controller, name, create=True):
142 self.logger = logging.getLogger('cgroups.' + controller.kind)
143 self.target = controller.target
144 self.controller = controller
145 self.name = name
146
147 # Control cgroup path
148 self.directory = controller.mount_point
149 if name != '/':
150 self.directory = self.target.path.join(controller.mount_point, name[1:])
151
152 # Setup path for tasks file
153 self.tasks_file = self.target.path.join(self.directory, 'tasks')
154 self.procs_file = self.target.path.join(self.directory, 'cgroup.procs')
155
156 if not create:
157 return
158
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000159 self.logger.debug('Creating cgroup %s', self.directory)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000160 self.target.execute('[ -d {0} ] || mkdir -p {0}'\
161 .format(self.directory), as_root=True)
162
163 def exists(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100164 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000165 self.target.execute('[ -d {0} ]'\
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100166 .format(self.directory), as_root=True)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000167 return True
168 except TargetError:
169 return False
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100170
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000171 def get(self):
172 conf = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100173
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000174 logging.debug('Reading %s attributes from:',
175 self.controller.kind)
176 logging.debug(' %s',
177 self.directory)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000178 output = self.target._execute_util(
179 'cgroups_get_attributes {} {}'.format(
Patrick Bellasi3acf5d52016-04-26 15:31:05 +0100180 self.directory, self.controller.kind),
181 as_root=True)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000182 for res in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000183 attr = res.split(':')[0]
184 value = res.split(':')[1]
185 conf[attr] = value
186
187 return conf
188
189 def set(self, **attrs):
190 for idx in attrs:
191 if isiterable(attrs[idx]):
192 attrs[idx] = list_to_ranges(attrs[idx])
193 # Build attribute path
Patrick Bellasi658005a2016-04-26 15:26:44 +0100194 if self.controller._noprefix:
195 attr_name = '{}'.format(idx)
196 else:
197 attr_name = '{}.{}'.format(self.controller.kind, idx)
198 path = self.target.path.join(self.directory, attr_name)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000199
200 self.logger.debug('Set attribute [%s] to: %s"',
201 path, attrs[idx])
202
203 # Set the attribute value
Patrick Bellasi658005a2016-04-26 15:26:44 +0100204 try:
205 self.target.write_value(path, attrs[idx])
206 except TargetError:
207 # Check if the error is due to a non-existing attribute
208 attrs = self.get()
209 if idx not in attrs:
210 raise ValueError('Controller [{}] does not provide attribute [{}]'\
211 .format(self.controller.kind, attr_name))
212 raise
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000213
214 def get_tasks(self):
215 task_ids = self.target.read_value(self.tasks_file).split()
216 logging.debug('Tasks: %s', task_ids)
217 return map(int, task_ids)
218
219 def add_task(self, tid):
220 self.target.write_value(self.tasks_file, tid, verify=False)
221
222 def add_tasks(self, tasks):
223 for tid in tasks:
224 self.add_task(tid)
225
226 def add_proc(self, pid):
227 self.target.write_value(self.procs_file, pid, verify=False)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100228
229CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled')
230
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100231class CgroupsModule(Module):
232
233 name = 'cgroups'
Patrick Bellasi616f2292016-05-13 18:17:22 +0100234 stage = 'setup'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100235 cgroup_root = '/sys/fs/cgroup'
236
237 @staticmethod
238 def probe(target):
Patrick Bellasi4b58c572016-04-26 15:33:23 +0100239 if not target.is_rooted:
240 return False
241 if target.file_exists('/proc/cgroups'):
242 return True
243 return target.config.has('cgroups')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100244
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100245 def __init__(self, target):
246 super(CgroupsModule, self).__init__(target)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000247
248 self.logger = logging.getLogger('CGroups')
249
250 # Initialize controllers mount point
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100251 mounted = self.target.list_file_systems()
252 if self.cgroup_root not in [e.mount_point for e in mounted]:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000253 self.target.execute('mount -t tmpfs {} {}'\
254 .format('cgroup_root',
255 self.cgroup_root),
256 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100257 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000258 self.logger.debug('cgroup_root already mounted at %s',
259 self.cgroup_root)
260
261 # Load list of available controllers
262 controllers = []
263 subsys = self.list_subsystems()
264 for (n, h, c, e) in subsys:
265 controllers.append(n)
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000266 self.logger.debug('Available controllers: %s', controllers)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000267
268 # Initialize controllers
269 self.controllers = {}
270 for idx in controllers:
271 controller = Controller(idx)
272 self.logger.debug('Init %s controller...', controller.kind)
273 if not controller.probe(self.target):
274 continue
275 try:
276 controller.mount(self.target, self.cgroup_root)
277 except TargetError:
278 message = 'cgroups {} controller is not supported by the target'
279 raise TargetError(message.format(controller.kind))
280 self.logger.debug('Controller %s enabled', controller.kind)
281 self.controllers[idx] = controller
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100282
283 def list_subsystems(self):
284 subsystems = []
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000285 for line in self.target.execute('{} cat /proc/cgroups'\
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000286 .format(self.target.busybox)).splitlines()[1:]:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100287 line = line.strip()
288 if not line or line.startswith('#'):
289 continue
290 name, hierarchy, num_cgroups, enabled = line.split()
291 subsystems.append(CgroupSubsystemEntry(name,
292 int(hierarchy),
293 int(num_cgroups),
294 boolean(enabled)))
295 return subsystems
296
297
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000298 def controller(self, kind):
299 if kind not in self.controllers:
300 self.logger.warning('Controller %s not available', kind)
301 return None
302 return self.controllers[kind]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100303
Patrick Bellasi28739392016-04-22 11:43:49 +0100304 def run_into(self, cgroup, cmdline):
305 """
306 Run the specified command into the specified CGroup
307 """
308 return self.target._execute_util(
309 'cgroups_run_into {} {}'.format(cgroup, cmdline),
310 as_root=True)
311
312
313 def cgroups_tasks_move(self, srcg, dstg, exclude=''):
314 """
315 Move all the tasks from the srcg CGroup to the dstg one.
316 A regexps of tasks names can be used to defined tasks which should not
317 be moved.
318 """
319 return self.target._execute_util(
320 'cgroups_tasks_move {} {} {}'.format(srcg, dstg, exclude),
321 as_root=True)
322