def __init__(self, value: str, container: ContainerInterface, common_labels: dict): assert isinstance(value, str) self.cgroup = container.get_cgroup() self._original_value = value self.value = _parse_cpuset(value) self.common_labels = common_labels self.labels_updater = LabelsUpdater(common_labels or {}) # First core self.min_value = 0 # Last core self.max_value = self.cgroup.platform_cpus - 1
def __init__(self, value: str, container: ContainerInterface, common_labels: dict): assert isinstance(value, str) self.cgroup = container.get_cgroup() self.subcgroups = container.get_subcgroups() self.value = value self.common_labels = common_labels self.labels_updater = LabelsUpdater(common_labels or {}) self.min_value = 0 self.max_value = None # TO BE UPDATED by subclass
def test_labels_updater(input_metrics, common_labels, expected_metrics): LabelsUpdater(common_labels).update_labels(input_metrics) assert input_metrics == expected_metrics
def __post_init__(self): assert isinstance(self.rdt_allocation, RDTAllocation), 'type error on %r' % self self.labels_updater = LabelsUpdater(self.common_labels)
class RDTAllocationValue(AllocationValue): """Wrapper over immutable RDTAllocation object to perform validation, serialization and enforce isolation on RDT resources.""" # Name of tasks, that RDTAllocation was assigned to. # Is used as resgroup.name if RDTAllocation.name is None container_name: str rdt_allocation: RDTAllocation resgroup: ResGroup get_pids: Callable[[], List[str]] # Used as pid provider platform_sockets: int rdt_information: RDTInformation rdt_groups: RDTGroups common_labels: Dict[str, str] source_resgroup: Optional[ ResGroup] = None # if not none try to _cleanup it at the end def __post_init__(self): assert isinstance(self.rdt_allocation, RDTAllocation), 'type error on %r' % self self.labels_updater = LabelsUpdater(self.common_labels) def __repr__(self): return repr(self.rdt_allocation) def __eq__(self, other): return self.rdt_allocation == other.rdt_allocation def _copy(self, rdt_allocation: RDTAllocation, source_resgroup=None, resgroup=None): return RDTAllocationValue( container_name=self.container_name, rdt_allocation=rdt_allocation, get_pids=self.get_pids, resgroup=resgroup if resgroup is not None else self.resgroup, platform_sockets=self.platform_sockets, rdt_information=self.rdt_information, source_resgroup=source_resgroup, rdt_groups=self.rdt_groups, common_labels=self.common_labels) def generate_metrics(self) -> List[Metric]: """Encode RDT Allocation as metrics. Note: - cache allocation: generated two metrics, with number of cache ways and mask of bits (encoded as int) - memory bandwidth: is encoded as int, representing MB/s or percentage """ # Empty object generate no metric. if not self.rdt_allocation.l3 and not self.rdt_allocation.mb: return [] group_name = self.get_resgroup_name() metrics = [] if self.rdt_allocation.l3: domains = _parse_schemata_file_row(self.rdt_allocation.l3) for domain_id, raw_value in domains.items(): metrics.extend([ Metric(name='allocation_rdt_l3_cache_ways', value=_count_enabled_bits(raw_value), type=MetricType.GAUGE, labels=dict( allocation_type='rdt_l3_cache_ways', group_name=group_name, domain_id=domain_id, container_name=self.container_name, )), Metric(name='allocation_rdt_l3_mask', value=int(raw_value, 16), type=MetricType.GAUGE, labels=dict( allocation_type='rdt_l3_mask', group_name=group_name, domain_id=domain_id, container_name=self.container_name, )) ]) if self.rdt_allocation.mb: domains = _parse_schemata_file_row(self.rdt_allocation.mb) for domain_id, raw_value in domains.items(): # NOTE: raw_value is treated as int, ignoring unit used (MB or %) value = int(raw_value) metrics.append( Metric(name='allocation_rdt_mb', value=value, type=MetricType.GAUGE, labels=dict( allocation_type='rdt_mb', group_name=group_name, domain_id=domain_id, container_name=self.container_name, ))) self.labels_updater.update_labels(metrics) return metrics def get_resgroup_name(self): """Return explicitly set resgroup name of inferred from covering container. """ return self.rdt_allocation.name if self.rdt_allocation.name is not None \ else self.container_name def calculate_changeset(self, current: Optional['RDTAllocationValue']): """Merge with existing RDTAllocation objects and return sum of the allocations (target_rdt_allocation) and allocations that need to be updated (rdt_allocation_changeset). current can be None - means we have just spotted the task, and we're moving it from default root group. current cannot have empty name in rdt_allocation.name !!!! """ assert isinstance( current, (type(None), RDTAllocationValue)), 'type error on current=%r ' % current # Any rdt_allocation that comes with current have to have rdt_allocation.name set) assert current is None or (current.rdt_allocation is not None) new: RDTAllocationValue = self new_group_name = new.get_resgroup_name() # new name, then new allocation will be used (overwrite) but no merge if current is None: # New tasks or is moved from root group. log.debug( 'resctrl changeset: new name or no previous allocation exists (moving from root ' 'group!)') return new, new._copy(new.rdt_allocation, resgroup=ResGroup(name=new_group_name), source_resgroup=ResGroup(name='')) current_group_name = current.get_resgroup_name() if current_group_name != new_group_name: # We need to move to another group. log.debug('resctrl changeset: move to new group=%r from=%r', current_group_name, new_group_name) return new, new._copy( new.rdt_allocation, resgroup=ResGroup(name=new_group_name), source_resgroup=ResGroup(name=current.get_resgroup_name())) else: log.debug( 'resctrl changeset: merging existing rdt allocation (the same resgroup name)' ) # Prepare target first, overwrite current l3 & mb values with new target_rdt_allocation = RDTAllocation( name=current.rdt_allocation.name, l3=new.rdt_allocation.l3 or current.rdt_allocation.l3, mb=new.rdt_allocation.mb or current.rdt_allocation.mb, ) target = current._copy(target_rdt_allocation) # Prepare changeset # Logic: if new value exists and is different from old one use the new. if _is_rdt_suballocation_changed(current.rdt_allocation.l3, new.rdt_allocation.l3): new_l3 = new.rdt_allocation.l3 else: log.debug('changeset l3: no change between: %r and %r' % (current.rdt_allocation.l3, new.rdt_allocation.l3)) new_l3 = None if _is_rdt_suballocation_changed(current.rdt_allocation.mb, new.rdt_allocation.mb): new_mb = new.rdt_allocation.mb else: log.debug('changeset l3: no change between: %r and %r' % (current.rdt_allocation.mb, new.rdt_allocation.mb)) new_mb = None if new_l3 or new_mb: # Only return something if schemata resources differs. rdt_allocation_changeset = RDTAllocation( name=new.rdt_allocation.name, l3=new_l3, mb=new_mb, ) changeset = current._copy(rdt_allocation_changeset) return target, changeset else: return target, None def validate(self): """Check L3 mask according platform.rdt_ features.""" if self.rdt_allocation.l3: if not self.rdt_information.rdt_cache_control_enabled: raise InvalidAllocations( 'Allocator requested RDT cache allocation but ' 'RDT cache control is not enabled!') validate_l3_string(self.rdt_allocation.l3, self.platform_sockets, self.rdt_information.cbm_mask, self.rdt_information.min_cbm_bits) if self.rdt_allocation.mb: if not self.rdt_information.rdt_mb_control_enabled: raise InvalidAllocations( 'Allocator requested RDT MB allocation but ' 'RDT memory bandwidth is not enabled!') normalized_mb_string = normalize_mb_string( self.rdt_allocation.mb, self.platform_sockets, self.rdt_information.mb_min_bandwidth, self.rdt_information.mb_bandwidth_gran) replace(self.rdt_allocation, mb=normalized_mb_string) self.rdt_groups.validate(self) def perform_allocations(self): """Enforce L3 or MB isolation including: - moving to new group if source_group is not None - update schemata file for given RDT resources - remove old group (source) optional """ # Move to appropriate group first. if self.source_resgroup is not None: log.debug( 'resctrl: perform_allocations moving to new group (from %r to %r)', self.source_resgroup.name, self.resgroup.name) # three cases (to root, from root, or between new resgroups) self.resgroup.add_pids(pids=self.get_pids(), mongroup_name=self.container_name) if len(self.source_resgroup.get_mon_groups()) == 1: self.source_resgroup.remove(self.container_name) # Now update the schemata file. if self.rdt_groups.should_perform_schemata_write(self): lines = [] if self.rdt_allocation.l3 and \ self.rdt_information.rdt_cache_control_enabled: lines.append(self.rdt_allocation.l3) if self.rdt_allocation.mb and \ self.rdt_information.rdt_mb_control_enabled: lines.append(self.rdt_allocation.mb) if lines: log.debug('resctrl: perform_allocations update schemata in %r', self.resgroup.name) self.resgroup.write_schemata(lines)
class CPUSetAllocationValue(AllocationValue): def __init__(self, value: str, container: ContainerInterface, common_labels: dict): assert isinstance(value, str) self.cgroup = container.get_cgroup() self._original_value = value self.value = _parse_cpuset(value) self.common_labels = common_labels self.labels_updater = LabelsUpdater(common_labels or {}) # First core self.min_value = 0 # Last core self.max_value = self.cgroup.platform_cpus - 1 def __repr__(self): return repr(self.value) def __eq__(self, other: 'CPUSetAllocationValue') -> bool: """Compare cpuset value to another value by taking value into consideration.""" assert isinstance(other, CPUSetAllocationValue) return self.value == other.value def calculate_changeset(self, current: 'CPUSetAllocationValue') \ -> Tuple['CPUSetAllocationValue', Optional['CPUSetAllocationValue']]: if current is None: # There is no old value, so there is a change value_changed = True else: # If we have old value compare them. assert isinstance(current, CPUSetAllocationValue) value_changed = (self != current) if value_changed: return self, self else: return current, None def generate_metrics(self) -> List[Metric]: assert isinstance(self.value, list) metrics = [ Metric(name='allocation_cpuset', value=self.value, type=MetricType.GAUGE, labels=dict(allocation_type='cpuset')) ] self.labels_updater.update_labels(metrics) return metrics def validate(self): if len(self.value) > 0: if self.value[0] < self.min_value or self.value[ -1] > self.max_value: raise InvalidAllocations('{} not in range <{};{}>'.format( self._original_value, self.min_value, self.max_value)) else: raise InvalidAllocations('{} is invalid argument!'.format( self._original_value)) def perform_allocations(self): self.validate() normalized_cpus = _normalize_cpuset(self.value) normalized_mems = _normalize_cpuset( list(range(0, self.cgroup.platform_sockets))) self.cgroup.set_cpuset(normalized_cpus, normalized_mems)