blob: 1d7a24ade6e30df76a31aaab5d70be265d3904ab [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
37
38 self.logger = logging.getLogger('cgroups.'+self.kind)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010039 self.mount_point = None
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000040 self._cgroups = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010041
42 def probe(self, target):
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000043 try:
44 exists = target.execute('{} grep {} /proc/cgroups'\
45 .format(target.busybox, self.kind))
46 except TargetError:
47 return False
48 return True
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010049
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000050 def mount(self, target, mount_root):
51
52 mounted = target.list_file_systems()
53 if self.mount_name in [e.device for e in mounted]:
54 # Identify mount point if controller is already in use
55 self.mount_point = [
56 fs.mount_point
57 for fs in mounted
58 if fs.device == self.mount_name
59 ][0]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010060 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000061 # Mount the controller if not already in use
62 self.mount_point = target.path.join(mount_root, self.mount_name)
63 target.execute('mkdir -p {} 2>/dev/null'\
64 .format(self.mount_point), as_root=True)
65 target.execute('mount -t cgroup -o {} {} {}'\
66 .format(self.kind,
67 self.mount_name,
68 self.mount_point),
69 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010070
Patrick Bellasi7112cfe2016-02-25 16:54:16 +000071 self.logger.debug('Controller %s mounted under: %s',
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000072 self.kind, self.mount_point)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010073
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000074 # Mark this contoller as available
75 self.target = target
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010076
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000077 # Create root control group
78 self.cgroup('/')
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010079
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000080 def cgroup(self, name):
81 if not self.target:
82 raise RuntimeError('CGroup creation failed: {} controller not mounted'\
83 .format(self.kind))
84 if name not in self._cgroups:
85 self._cgroups[name] = CGroup(self, name)
86 return self._cgroups[name]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010087
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000088 def exists(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, create=False)
94 return self._cgroups[name].existe()
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010095
Patrick Bellasi9c9a7482015-11-03 11:49:31 +000096 def list_all(self):
97 self.logger.debug('Listing groups for %s controller', self.kind)
98 output = self.target.execute('{} find {} -type d'\
99 .format(self.target.busybox, self.mount_point))
100 cgroups = []
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000101 for cg in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000102 cg = cg.replace(self.mount_point + '/', '/')
103 cg = cg.replace(self.mount_point, '/')
104 cg = cg.strip()
105 if cg == '':
106 continue
107 self.logger.debug('Populate %s cgroup: %s', self.kind, cg)
108 cgroups.append(cg)
109 return cgroups
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100110
111 def move_tasks(self, source, dest):
112 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000113 srcg = self._cgroups[source]
114 dstg = self._cgroups[dest]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100115 command = 'for task in $(cat {}); do echo $task>{}; done'
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000116 self.target.execute(command.format(srcg.tasks_file, dstg.tasks_file),
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100117 # this will always fail as some of the tasks
118 # are kthreads that cannot be migrated, but we
119 # don't care about those, so don't check exit
120 # code.
121 check_exit_code=False, as_root=True)
122 except KeyError as e:
123 raise ValueError('Unkown group: {}'.format(e))
124
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000125 def move_all_tasks_to(self, dest):
126 for cgroup in self._cgroups:
127 if cgroup != dest:
128 self.move_tasks(cgroup, dest)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100129
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000130class CGroup(object):
131
132 def __init__(self, controller, name, create=True):
133 self.logger = logging.getLogger('cgroups.' + controller.kind)
134 self.target = controller.target
135 self.controller = controller
136 self.name = name
137
138 # Control cgroup path
139 self.directory = controller.mount_point
140 if name != '/':
141 self.directory = self.target.path.join(controller.mount_point, name[1:])
142
143 # Setup path for tasks file
144 self.tasks_file = self.target.path.join(self.directory, 'tasks')
145 self.procs_file = self.target.path.join(self.directory, 'cgroup.procs')
146
147 if not create:
148 return
149
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000150 self.logger.debug('Creating cgroup %s', self.directory)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000151 self.target.execute('[ -d {0} ] || mkdir -p {0}'\
152 .format(self.directory), as_root=True)
153
154 def exists(self):
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100155 try:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000156 self.target.execute('[ -d {0} ]'\
157 .format(self.directory))
158 return True
159 except TargetError:
160 return False
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100161
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000162 def get(self):
163 conf = {}
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100164
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000165 logging.debug('Reading %s attributes from:',
166 self.controller.kind)
167 logging.debug(' %s',
168 self.directory)
Patrick Bellasia65ff132016-02-23 12:11:06 +0000169 output = self.target._execute_util(
170 'cgroups_get_attributes {} {}'.format(
171 self.directory, self.controller.kind))
172 for res in output.splitlines():
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000173 attr = res.split(':')[0]
174 value = res.split(':')[1]
175 conf[attr] = value
176
177 return conf
178
179 def set(self, **attrs):
180 for idx in attrs:
181 if isiterable(attrs[idx]):
182 attrs[idx] = list_to_ranges(attrs[idx])
183 # Build attribute path
184 path = '{}.{}'.format(self.controller.kind, idx)
185 path = self.target.path.join(self.directory, path)
186
187 self.logger.debug('Set attribute [%s] to: %s"',
188 path, attrs[idx])
189
190 # Set the attribute value
191 self.target.write_value(path, attrs[idx])
192
193 def get_tasks(self):
194 task_ids = self.target.read_value(self.tasks_file).split()
195 logging.debug('Tasks: %s', task_ids)
196 return map(int, task_ids)
197
198 def add_task(self, tid):
199 self.target.write_value(self.tasks_file, tid, verify=False)
200
201 def add_tasks(self, tasks):
202 for tid in tasks:
203 self.add_task(tid)
204
205 def add_proc(self, pid):
206 self.target.write_value(self.procs_file, pid, verify=False)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100207
208CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled')
209
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100210class CgroupsModule(Module):
211
212 name = 'cgroups'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100213 cgroup_root = '/sys/fs/cgroup'
214
215 @staticmethod
216 def probe(target):
217 return target.config.has('cgroups') and target.is_rooted
218
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100219 def __init__(self, target):
220 super(CgroupsModule, self).__init__(target)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000221
222 self.logger = logging.getLogger('CGroups')
223
224 # Initialize controllers mount point
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100225 mounted = self.target.list_file_systems()
226 if self.cgroup_root not in [e.mount_point for e in mounted]:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000227 self.target.execute('mount -t tmpfs {} {}'\
228 .format('cgroup_root',
229 self.cgroup_root),
230 as_root=True)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100231 else:
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000232 self.logger.debug('cgroup_root already mounted at %s',
233 self.cgroup_root)
234
235 # Load list of available controllers
236 controllers = []
237 subsys = self.list_subsystems()
238 for (n, h, c, e) in subsys:
239 controllers.append(n)
Patrick Bellasi7112cfe2016-02-25 16:54:16 +0000240 self.logger.debug('Available controllers: %s', controllers)
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000241
242 # Initialize controllers
243 self.controllers = {}
244 for idx in controllers:
245 controller = Controller(idx)
246 self.logger.debug('Init %s controller...', controller.kind)
247 if not controller.probe(self.target):
248 continue
249 try:
250 controller.mount(self.target, self.cgroup_root)
251 except TargetError:
252 message = 'cgroups {} controller is not supported by the target'
253 raise TargetError(message.format(controller.kind))
254 self.logger.debug('Controller %s enabled', controller.kind)
255 self.controllers[idx] = controller
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100256
257 def list_subsystems(self):
258 subsystems = []
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000259 for line in self.target.execute('{} cat /proc/cgroups'\
Patrick Bellasie2e5e682016-02-23 12:12:28 +0000260 .format(self.target.busybox)).splitlines()[1:]:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100261 line = line.strip()
262 if not line or line.startswith('#'):
263 continue
264 name, hierarchy, num_cgroups, enabled = line.split()
265 subsystems.append(CgroupSubsystemEntry(name,
266 int(hierarchy),
267 int(num_cgroups),
268 boolean(enabled)))
269 return subsystems
270
271
Patrick Bellasi9c9a7482015-11-03 11:49:31 +0000272 def controller(self, kind):
273 if kind not in self.controllers:
274 self.logger.warning('Controller %s not available', kind)
275 return None
276 return self.controllers[kind]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100277