Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | # 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 |
| 16 | import logging |
| 17 | from collections import namedtuple |
| 18 | |
| 19 | from devlib.module import Module |
| 20 | from devlib.exception import TargetError |
| 21 | from devlib.utils.misc import list_to_ranges, isiterable |
| 22 | from devlib.utils.types import boolean |
| 23 | |
| 24 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 25 | class Controller(object): |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 26 | |
| 27 | def __new__(cls, arg): |
| 28 | if isinstance(arg, cls): |
| 29 | return arg |
| 30 | else: |
| 31 | return object.__new__(cls, arg) |
| 32 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 33 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 39 | self.mount_point = None |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 40 | self._cgroups = {} |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 41 | |
| 42 | def probe(self, target): |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 43 | try: |
| 44 | exists = target.execute('{} grep {} /proc/cgroups'\ |
| 45 | .format(target.busybox, self.kind)) |
| 46 | except TargetError: |
| 47 | return False |
| 48 | return True |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 49 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 50 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 60 | else: |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 61 | # 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 70 | |
Patrick Bellasi | 7112cfe | 2016-02-25 16:54:16 +0000 | [diff] [blame^] | 71 | self.logger.debug('Controller %s mounted under: %s', |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 72 | self.kind, self.mount_point) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 73 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 74 | # Mark this contoller as available |
| 75 | self.target = target |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 76 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 77 | # Create root control group |
| 78 | self.cgroup('/') |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 79 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 80 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 87 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 88 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 95 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 96 | 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 Bellasi | e2e5e68 | 2016-02-23 12:12:28 +0000 | [diff] [blame] | 101 | for cg in output.splitlines(): |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 102 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 110 | |
| 111 | def move_tasks(self, source, dest): |
| 112 | try: |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 113 | srcg = self._cgroups[source] |
| 114 | dstg = self._cgroups[dest] |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 115 | command = 'for task in $(cat {}); do echo $task>{}; done' |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 116 | self.target.execute(command.format(srcg.tasks_file, dstg.tasks_file), |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 117 | # 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 Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 125 | def move_all_tasks_to(self, dest): |
| 126 | for cgroup in self._cgroups: |
| 127 | if cgroup != dest: |
| 128 | self.move_tasks(cgroup, dest) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 129 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 130 | class 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 Bellasi | 7112cfe | 2016-02-25 16:54:16 +0000 | [diff] [blame^] | 150 | self.logger.debug('Creating cgroup %s', self.directory) |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 151 | self.target.execute('[ -d {0} ] || mkdir -p {0}'\ |
| 152 | .format(self.directory), as_root=True) |
| 153 | |
| 154 | def exists(self): |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 155 | try: |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 156 | self.target.execute('[ -d {0} ]'\ |
| 157 | .format(self.directory)) |
| 158 | return True |
| 159 | except TargetError: |
| 160 | return False |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 161 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 162 | def get(self): |
| 163 | conf = {} |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 164 | |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 165 | logging.debug('Reading %s attributes from:', |
| 166 | self.controller.kind) |
| 167 | logging.debug(' %s', |
| 168 | self.directory) |
Patrick Bellasi | a65ff13 | 2016-02-23 12:11:06 +0000 | [diff] [blame] | 169 | output = self.target._execute_util( |
| 170 | 'cgroups_get_attributes {} {}'.format( |
| 171 | self.directory, self.controller.kind)) |
| 172 | for res in output.splitlines(): |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 173 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 207 | |
| 208 | CgroupSubsystemEntry = namedtuple('CgroupSubsystemEntry', 'name hierarchy num_cgroups enabled') |
| 209 | |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 210 | class CgroupsModule(Module): |
| 211 | |
| 212 | name = 'cgroups' |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 213 | cgroup_root = '/sys/fs/cgroup' |
| 214 | |
| 215 | @staticmethod |
| 216 | def probe(target): |
| 217 | return target.config.has('cgroups') and target.is_rooted |
| 218 | |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 219 | def __init__(self, target): |
| 220 | super(CgroupsModule, self).__init__(target) |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 221 | |
| 222 | self.logger = logging.getLogger('CGroups') |
| 223 | |
| 224 | # Initialize controllers mount point |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 225 | mounted = self.target.list_file_systems() |
| 226 | if self.cgroup_root not in [e.mount_point for e in mounted]: |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 227 | self.target.execute('mount -t tmpfs {} {}'\ |
| 228 | .format('cgroup_root', |
| 229 | self.cgroup_root), |
| 230 | as_root=True) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 231 | else: |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 232 | 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 Bellasi | 7112cfe | 2016-02-25 16:54:16 +0000 | [diff] [blame^] | 240 | self.logger.debug('Available controllers: %s', controllers) |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 241 | |
| 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 256 | |
| 257 | def list_subsystems(self): |
| 258 | subsystems = [] |
Patrick Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 259 | for line in self.target.execute('{} cat /proc/cgroups'\ |
Patrick Bellasi | e2e5e68 | 2016-02-23 12:12:28 +0000 | [diff] [blame] | 260 | .format(self.target.busybox)).splitlines()[1:]: |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 261 | 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 Bellasi | 9c9a748 | 2015-11-03 11:49:31 +0000 | [diff] [blame] | 272 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 277 | |