def check_clean():
     assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
         DeviceId('a0')] == Decimal('0')
     assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
         DeviceId('a1')] == Decimal('0')
     assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
         'a2')] == Decimal('0')
     assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
         'a3')] == Decimal('0')
     assert alloc_map.allocations[SlotName('cuda.device:3g.20gb-mig')][
         DeviceId('a4')] == Decimal('0')
def test_fraction_alloc_map_random_generated_allocations():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
        },
        allocation_strategy=FractionAllocationStrategy.FILL,
    )
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal('0')

    quantum = Decimal('.01')
    for _ in range(5):
        allocations = []
        for _ in range(10):
            result = alloc_map.allocate({
                SlotName('x'):
                Decimal(random.uniform(0, 0.1)).quantize(quantum, ROUND_DOWN),
            })
            allocations.append(result)
        assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] >= Decimal(
            '0')
        assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] >= Decimal(
            '0')
        for a in allocations:
            alloc_map.free(a)
        assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
            '0')
        assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
            '0')
 async def list_devices(self) -> Collection[CUDADevice]:
     if not self.enabled:
         return []
     all_devices = []
     num_devices = libcudart.get_device_count()
     for dev_id in map(lambda idx: DeviceId(str(idx)), range(num_devices)):
         if dev_id in self.device_mask:
             continue
         raw_info = libcudart.get_device_props(int(dev_id))
         sysfs_node_path = "/sys/bus/pci/devices/" \
                           f"{raw_info['pciBusID_str'].lower()}/numa_node"
         node: Optional[int]
         try:
             node = int(Path(sysfs_node_path).read_text().strip())
         except OSError:
             node = None
         dev_uuid, raw_dev_uuid = None, raw_info.get('uuid', None)
         if raw_dev_uuid is not None:
             dev_uuid = str(uuid.UUID(bytes=raw_dev_uuid))
         else:
             dev_uuid = '00000000-0000-0000-0000-000000000000'
         dev_info = CUDADevice(
             device_id=dev_id,
             hw_location=raw_info['pciBusID_str'],
             numa_node=node,
             memory_size=raw_info['totalGlobalMem'],
             processing_units=raw_info['multiProcessorCount'],
             model_name=raw_info['name'],
             uuid=dev_uuid,
         )
         all_devices.append(dev_info)
     return all_devices
Esempio n. 4
0
    async def gather_node_measures(
            self, ctx: StatContext) -> Sequence[NodeMeasurement]:
        _cstat = psutil.cpu_times(True)
        q = Decimal('0.000')
        total_cpu_used = cast(
            Decimal,
            sum((Decimal(c.user + c.system) * 1000).quantize(q)
                for c in _cstat))
        now, raw_interval = ctx.update_timestamp('cpu-node')
        interval = Decimal(raw_interval * 1000).quantize(q)

        return [
            NodeMeasurement(
                MetricKey('cpu_util'),
                MetricTypes.UTILIZATION,
                unit_hint='msec',
                current_hook=lambda metric: metric.stats.diff,
                per_node=Measurement(total_cpu_used, interval),
                per_device={
                    DeviceId(str(idx)): Measurement(
                        (Decimal(c.user + c.system) * 1000).quantize(q),
                        interval,
                    )
                    for idx, c in enumerate(_cstat)
                },
            ),
        ]
def test_exclusive_resource_slots():
    alloc_map = DiscretePropertyAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:1g.5gb-mig'),
                           Decimal(1)),  # noqa
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:1g.5gb-mig'),
                           Decimal(1)),  # noqa
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('cuda.device'),
                           Decimal(1)),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('cuda.device'),
                           Decimal(1)),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:3g.20gb-mig'),
                           Decimal(1)),  # noqa
        },
        exclusive_slot_types={
            'cuda.device:*-mig', 'cuda.device', 'cuda.shares'
        },
    )

    def check_clean():
        assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
            DeviceId('a0')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
            DeviceId('a1')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device')][DeviceId(
            'a2')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device')][DeviceId(
            'a3')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device:3g.20gb-mig')][
            DeviceId('a4')] == Decimal('0')

    with pytest.raises(InvalidResourceCombination):
        alloc_map.allocate({
            SlotName('cuda.device'): Decimal('2'),
            SlotName('cuda.device:1g.5gb-mig'): Decimal('1'),
        })
    check_clean()
Esempio n. 6
0
 async def restore_from_container(
     self,
     container: Container,
     alloc_map: AbstractAllocMap,
 ) -> None:
     assert isinstance(alloc_map, DiscretePropertyAllocMap)
     memory_limit = container.backend_obj['HostConfig']['Memory']
     alloc_map.apply_allocation({
         SlotName('mem'): {
             DeviceId('root'): memory_limit
         },
     })
Esempio n. 7
0
 async def list_devices(self) -> Collection[MemoryDevice]:
     # TODO: support NUMA?
     memory_size = psutil.virtual_memory().total
     return [
         MemoryDevice(
             device_id=DeviceId('root'),
             hw_location='root',
             numa_node=0,
             memory_size=memory_size,
             processing_units=0,
         )
     ]
    async def init(self, context: Any = None) -> None:
        rx_triple_version = re.compile(r'(\d+\.\d+\.\d+)')
        # Check nvidia-docker and docker versions
        try:
            proc = await asyncio.create_subprocess_exec(
                'nvidia-docker', 'version', '-f', '{{json .}}',
                stdout=asyncio.subprocess.PIPE,
            )
            stdout, _ = await proc.communicate()
            lines = stdout.decode().splitlines()
        except FileNotFoundError:
            log.warning('nvidia-docker is not installed.')
            log.info('CUDA acceleration is disabled.')
            self.enabled = False
            return
        m = rx_triple_version.search(lines[0])
        if m:
            self.nvdocker_version = tuple(map(int, m.group(1).split('.')))
        else:
            log.error('could not detect nvidia-docker version!')
            log.info('CUDA acceleration is disabled.')
            self.enabled = False
            return
        docker_version_data = json.loads(lines[1])
        m = rx_triple_version.search(docker_version_data['Server']['Version'])
        if m:
            self.docker_version = tuple(map(int, m.group(1).split('.')))
        else:
            log.error('could not detect docker version!')
            log.info('CUDA acceleration is disabled.')
            self.enabled = False
            return

        raw_device_mask = self.plugin_config.get('device_mask')
        if raw_device_mask is not None:
            self.device_mask = [
                *map(lambda dev_id: DeviceId(dev_id), raw_device_mask.split(','))
            ]
        try:
            detected_devices = await self.list_devices()
            log.info('detected devices:\n' + pformat(detected_devices))
            log.info('nvidia-docker version: {}', self.nvdocker_version)
            log.info('CUDA acceleration is enabled.')
        except ImportError:
            log.warning('could not load the CUDA runtime library.')
            log.info('CUDA acceleration is disabled.')
            self.enabled = False
        except RuntimeError as e:
            log.warning('CUDA init error: {}', e)
            log.info('CUDA acceleration is disabled.')
            self.enabled = False
Esempio n. 9
0
 async def list_devices(self) -> Collection[CPUDevice]:
     cores = await libnuma.get_available_cores()
     overcommit_factor = int(
         os.environ.get('BACKEND_CPU_OVERCOMMIT_FACTOR', '1'))
     assert 1 <= overcommit_factor <= 4
     return [
         CPUDevice(
             device_id=DeviceId(str(core_idx)),
             hw_location='root',
             numa_node=libnuma.node_of_cpu(core_idx),
             memory_size=0,
             processing_units=1 * overcommit_factor,
         ) for core_idx in sorted(cores)
     ]
Esempio n. 10
0
 async def gather_node_measures(
     self,
     ctx: StatContext,
 ) -> Sequence[NodeMeasurement]:
     dev_count = 0
     mem_avail_total = 0
     mem_used_total = 0
     mem_stats = {}
     util_total = 0
     util_stats = {}
     if self.enabled:
         try:
             dev_count = libnvml.get_device_count()
             for dev_id in map(lambda idx: DeviceId(str(idx)), range(dev_count)):
                 if dev_id in self.device_mask:
                     continue
                 dev_stat = libnvml.get_device_stats(int(dev_id))
                 mem_avail_total += dev_stat.mem_total
                 mem_used_total += dev_stat.mem_used
                 mem_stats[dev_id] = Measurement(Decimal(dev_stat.mem_used),
                                                 Decimal(dev_stat.mem_total))
                 util_total += dev_stat.gpu_util
                 util_stats[dev_id] = Measurement(Decimal(dev_stat.gpu_util), Decimal(100))
         except ImportError:
             log.warning('gather_node_measure(): NVML library is not found')
         except LibraryError as e:
             log.warning('gather_node_measure(): {!r}', e)
     return [
         NodeMeasurement(
             MetricKey('cuda_mem'),
             MetricTypes.USAGE,
             unit_hint='bytes',
             stats_filter=frozenset({'max'}),
             per_node=Measurement(Decimal(mem_used_total), Decimal(mem_avail_total)),
             per_device=mem_stats,
         ),
         NodeMeasurement(
             MetricKey('cuda_util'),
             MetricTypes.USAGE,
             unit_hint='percent',
             stats_filter=frozenset({'avg', 'max'}),
             per_node=Measurement(Decimal(util_total), Decimal(dev_count * 100)),
             per_device=util_stats,
         ),
     ]
Esempio n. 11
0
 def read_from_string(cls, text: str) -> 'KernelResourceSpec':
     kvpairs = {}
     for line in text.split('\n'):
         if '=' not in line:
             continue
         key, val = line.strip().split('=', maxsplit=1)
         kvpairs[key] = val
     allocations = cast(
         MutableMapping[DeviceName, MutableMapping[SlotName,
                                                   Mapping[DeviceId,
                                                           Decimal]]],
         defaultdict(lambda: defaultdict(Decimal)),
     )
     for key, val in kvpairs.items():
         if key.endswith('_SHARES'):
             slot_name = SlotName(key[:-7].lower())
             device_name = DeviceName(slot_name.split('.')[0])
             per_device_alloc: MutableMapping[DeviceId, Decimal] = {}
             for entry in val.split(','):
                 raw_dev_id, _, raw_alloc = entry.partition(':')
                 if not raw_dev_id or not raw_alloc:
                     continue
                 dev_id = DeviceId(raw_dev_id)
                 try:
                     if known_slot_types.get(slot_name, 'count') == 'bytes':
                         alloc = Decimal(BinarySize.from_str(raw_alloc))
                     else:
                         alloc = Decimal(raw_alloc)
                 except KeyError as e:
                     log.warning(
                         'A previously launched container has '
                         'unknown slot type: {}. Ignoring it.', e.args[0])
                     continue
                 per_device_alloc[dev_id] = alloc
             allocations[device_name][slot_name] = per_device_alloc
     mounts = [Mount.from_str(m) for m in kvpairs['MOUNTS'].split(',') if m]
     return cls(
         container_id=kvpairs.get('CID', 'unknown'),
         scratch_disk_size=BinarySize.finite_from_str(
             kvpairs['SCRATCH_SIZE']),
         allocations=dict(allocations),
         slots=ResourceSlot(json.loads(kvpairs['SLOTS'])),
         mounts=mounts,
     )
Esempio n. 12
0
    async def gather_node_measures(
            self, ctx: StatContext) -> Sequence[NodeMeasurement]:
        _mstat = psutil.virtual_memory()
        total_mem_used_bytes = Decimal(_mstat.total - _mstat.available)
        total_mem_capacity_bytes = Decimal(_mstat.total)
        _nstat = psutil.net_io_counters()
        net_rx_bytes = _nstat.bytes_recv
        net_tx_bytes = _nstat.bytes_sent

        def get_disk_stat():
            pruned_disk_types = frozenset(['squashfs', 'vfat', 'tmpfs'])
            total_disk_usage = Decimal(0)
            total_disk_capacity = Decimal(0)
            per_disk_stat = {}
            for disk_info in psutil.disk_partitions():
                if disk_info.fstype not in pruned_disk_types:
                    dstat = os.statvfs(disk_info.mountpoint)
                    disk_usage = Decimal(dstat.f_frsize *
                                         (dstat.f_blocks - dstat.f_bavail))
                    disk_capacity = Decimal(dstat.f_frsize * dstat.f_blocks)
                    per_disk_stat[disk_info.device] = Measurement(
                        disk_usage, disk_capacity)
                    total_disk_usage += disk_usage
                    total_disk_capacity += disk_capacity
            return total_disk_usage, total_disk_capacity, per_disk_stat

        loop = current_loop()
        total_disk_usage, total_disk_capacity, per_disk_stat = \
            await loop.run_in_executor(None, get_disk_stat)
        return [
            NodeMeasurement(
                MetricKey('mem'),
                MetricTypes.USAGE,
                unit_hint='bytes',
                stats_filter=frozenset({'max'}),
                per_node=Measurement(total_mem_used_bytes,
                                     total_mem_capacity_bytes),
                per_device={
                    DeviceId('root'):
                    Measurement(total_mem_used_bytes, total_mem_capacity_bytes)
                },
            ),
            NodeMeasurement(
                MetricKey('disk'),
                MetricTypes.USAGE,
                unit_hint='bytes',
                per_node=Measurement(total_disk_usage, total_disk_capacity),
                per_device=per_disk_stat,
            ),
            NodeMeasurement(
                MetricKey('net_rx'),
                MetricTypes.RATE,
                unit_hint='bps',
                current_hook=lambda metric: metric.stats.rate,
                per_node=Measurement(Decimal(net_rx_bytes)),
                per_device={
                    DeviceId('node'): Measurement(Decimal(net_rx_bytes))
                },
            ),
            NodeMeasurement(
                MetricKey('net_tx'),
                MetricTypes.RATE,
                unit_hint='bps',
                current_hook=lambda metric: metric.stats.rate,
                per_node=Measurement(Decimal(net_tx_bytes)),
                per_device={
                    DeviceId('node'): Measurement(Decimal(net_tx_bytes))
                },
            ),
        ]
def test_fraction_alloc_map():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
        },
        allocation_strategy=FractionAllocationStrategy.FILL,
    )
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal('0')

    result = alloc_map.allocate({
        SlotName('x'): Decimal('1.5'),
    })
    assert result[SlotName('x')][DeviceId('a0')] == Decimal('1.0')
    assert result[SlotName('x')][DeviceId('a1')] == Decimal('0.5')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '1.0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.5')

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({
            SlotName('x'): Decimal('1.5'),
        })
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '1.0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.5')

    alloc_map.free(result)
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal('0')
def test_fraction_alloc_map_many_device():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a5'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a6'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a7'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
        },
        allocation_strategy=FractionAllocationStrategy.FILL,
    )
    for idx in range(8):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({
        SlotName('x'): Decimal('7.95'),
    })
    for idx in range(7):
        assert result[SlotName('x')][DeviceId(f'a{idx}')] == Decimal('1.0')
    assert result[SlotName('x')][DeviceId('a7')] == Decimal('0.95')
    for idx in range(7):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('1.0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a7')] == Decimal(
        '0.95')

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({
            SlotName('x'): Decimal('1.0'),
        })
    for idx in range(7):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('1.0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a7')] == Decimal(
        '0.95')

    alloc_map.free(result)
    for idx in range(8):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')
def test_heterogeneous_resource_slots_with_fractional_alloc_map():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:1g.5gb-mig'),
                           Decimal(1)),  # noqa
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:1g.5gb-mig'),
                           Decimal(1)),  # noqa
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('cuda.shares'),
                           Decimal('1.0')),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('cuda.shares'),
                           Decimal('1.0')),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.UNIQUE,
                           SlotName('cuda.device:3g.20gb-mig'),
                           Decimal(1)),  # noqa
        },
        exclusive_slot_types={
            'cuda.device:*-mig', 'cuda.device', 'cuda.shares'
        },
        allocation_strategy=FractionAllocationStrategy.FILL,
    )

    def check_clean():
        assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
            DeviceId('a0')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][
            DeviceId('a1')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
            'a2')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
            'a3')] == Decimal('0')
        assert alloc_map.allocations[SlotName('cuda.device:3g.20gb-mig')][
            DeviceId('a4')] == Decimal('0')

    check_clean()

    # check allocation of non-unique slots
    result = alloc_map.allocate({SlotName('cuda.shares'): Decimal('2.0')})
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a1')] == Decimal('0')
    assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
        'a2')] == Decimal('1.0')
    assert alloc_map.allocations[SlotName('cuda.shares')][DeviceId(
        'a3')] == Decimal('1.0')
    assert alloc_map.allocations[SlotName('cuda.device:3g.20gb-mig')][DeviceId(
        'a4')] == Decimal('0')
    alloc_map.free(result)
    check_clean()

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({SlotName('cuda.shares'): Decimal('2.5')})
    check_clean()

    # allocating zero means no-op.
    alloc_map.allocate({SlotName('cuda.device:1g.5gb-mig'): Decimal('0')})
    check_clean()

    # any allocation request for unique slots should specify the amount 1.
    with pytest.raises(InvalidResourceArgument):
        alloc_map.allocate(
            {SlotName('cuda.device:1g.5gb-mig'): Decimal('0.3')})
    with pytest.raises(InvalidResourceArgument):
        alloc_map.allocate(
            {SlotName('cuda.device:1g.5gb-mig'): Decimal('1.5')})
    check_clean()

    # test alloaction of unique slots
    result1 = alloc_map.allocate(
        {SlotName('cuda.device:1g.5gb-mig'): Decimal('1')})
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a0')] == Decimal('1')
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a1')] == Decimal('0')
    result2 = alloc_map.allocate(
        {SlotName('cuda.device:1g.5gb-mig'): Decimal('1')})
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a0')] == Decimal('1')
    assert alloc_map.allocations[SlotName('cuda.device:1g.5gb-mig')][DeviceId(
        'a1')] == Decimal('1')
    with pytest.raises(InsufficientResource):
        alloc_map.allocate({SlotName('cuda.device:1g.5gb-mig'): Decimal('1')})
    alloc_map.free(result1)
    alloc_map.free(result2)
    check_clean()
def test_quantum_size(alloc_strategy):
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1)),  # noqa
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1)),  # noqa
        },
        quantum_size=Decimal("0.25"),
        allocation_strategy=alloc_strategy,
    )
    result = alloc_map.allocate({
        SlotName('x'): Decimal("0.5"),
    })
    assert sum(alloc_map.allocations[SlotName('x')].values()) == Decimal("0.5")
    alloc_map.free(result)

    result = alloc_map.allocate({
        SlotName('x'): Decimal("1.5"),
    })
    assert sum(alloc_map.allocations[SlotName('x')].values()) == Decimal("1.5")
    if alloc_strategy == FractionAllocationStrategy.EVENLY:
        assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
            "0.75")
        assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
            "0.75")
    else:
        assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
            "1.00")
        assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
            "0.50")
    alloc_map.free(result)

    # inputs are not multiple of 0.25
    with pytest.raises(NotMultipleOfQuantum):
        alloc_map.allocate({
            SlotName('x'): Decimal("0.52"),
        })
    with pytest.raises(NotMultipleOfQuantum):
        alloc_map.allocate({
            SlotName('x'): Decimal("0.42"),
        })
    with pytest.raises(NotMultipleOfQuantum):
        alloc_map.allocate({
            SlotName('x'): Decimal("3.99"),
        })

    if alloc_strategy == FractionAllocationStrategy.EVENLY:
        # input IS multiple of 0.25 but the CALCULATED allocations are not multiple of 0.25
        with pytest.raises(InsufficientResource, match="multiple-of-quantum"):
            alloc_map.allocate({
                SlotName('x'): Decimal("1.75"),  # divided to 0.88 and 0.87
            })
    else:
        # In this case, it satisfies the quantum condition, because the capacity of devices are
        # multiples of the quantum.
        alloc_map.allocate({
            SlotName('x'): Decimal("1.75"),
        })
        assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
            "1.00")
        assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
            "0.75")

        # So let's change the situation.
        alloc_map = FractionAllocMap(
            device_slots={
                DeviceId('a0'):
                DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'),
                               Decimal(1)),  # noqa
                DeviceId('a1'):
                DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'),
                               Decimal(1)),  # noqa
            },
            quantum_size=Decimal("0.3"),
            allocation_strategy=alloc_strategy,
        )
        with pytest.raises(NotMultipleOfQuantum):
            alloc_map.allocate({
                SlotName('x'): Decimal("0.5"),
            })
        with pytest.raises(InsufficientResource, match="multiple-of-quantum"):
            alloc_map.allocate({
                SlotName('x'): Decimal("1.2"),
            })
def test_discrete_alloc_map_large_number():
    alloc_map = DiscretePropertyAllocMap(device_slots={
        DeviceId('a0'):
        DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(100)),
        DeviceId('a1'):
        DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(100)),
    }, )
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == 0
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == 0

    result = alloc_map.allocate({
        SlotName('x'): Decimal('130'),
    })
    assert result[SlotName('x')][DeviceId('a0')] == 100
    assert result[SlotName('x')][DeviceId('a1')] == 30
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == 100
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == 30

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({
            SlotName('x'): Decimal('71'),
        })
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == 100
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == 30

    alloc_map.free(result)
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == 0
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == 0
def test_fraction_alloc_map_even_allocation_many_devices_2():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a5'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a6'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
            DeviceId('a7'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('1.0')),
        },
        allocation_strategy=FractionAllocationStrategy.EVENLY,
    )
    result = alloc_map.allocate({SlotName('x'): Decimal('6')})
    count_0 = 0
    count_1 = 0
    # NOTE: the even allocator favors the tail of device list when it fills up.
    # So we rely on the counting of desire per-device allocations instead of matching
    # the device index and the allocations.
    for idx in range(8):
        if alloc_map.allocations[SlotName('x')][DeviceId(
                f'a{idx}')] == Decimal('1.0'):
            count_1 += 1
        if alloc_map.allocations[SlotName('x')][DeviceId(
                f'a{idx}')] == Decimal('0'):
            count_0 += 1
    assert count_0 == 2
    assert count_1 == 6
    alloc_map.free(result)
    for idx in range(8):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')
def test_fraction_alloc_map_even_allocation_many_devices():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(2)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(3)),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(3)),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(5)),
        },
        allocation_strategy=FractionAllocationStrategy.EVENLY,
    )
    result = alloc_map.allocate({SlotName('x'): Decimal('6')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal('3')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal('3')
    alloc_map.free(result)
    for idx in range(4):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.5)),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(2)),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(3)),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(3)),
            DeviceId('a5'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(4)),
            DeviceId('a6'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(4.5)),
            DeviceId('a7'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(5)),
            DeviceId('a8'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(5)),
        },
        allocation_strategy=FractionAllocationStrategy.EVENLY,
    )

    result = alloc_map.allocate({SlotName('x'): Decimal('6')},
                                min_memory=Decimal('2.5'))
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal('3')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a4')] == Decimal('3')
    alloc_map.free(result)
    for idx in range(9):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('11')},
                                min_memory=Decimal('0.84'))
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '2.75')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a4')] == Decimal(
        '2.75')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a5')] == Decimal(
        '2.75')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a5')] == Decimal(
        '2.75')
    alloc_map.free(result)
    for idx in range(9):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')
def test_fraction_alloc_map_even_allocation_fractions():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.8')),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.75')),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.7')),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.3')),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.0')),
        },
        allocation_strategy=FractionAllocationStrategy.EVENLY,
    )
    result = alloc_map.allocate({SlotName('x'): Decimal('2.31')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.67')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.67')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.67')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '0.3')
    alloc_map.free(result)
    for idx in range(4):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('2')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.67')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.67')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.66')
    alloc_map.free(result)
    for idx in range(3):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')
def test_fraction_alloc_map_even_allocation():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(0.05)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(0.1)),
            DeviceId('a2'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(0.2)),
            DeviceId('a3'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(0.3)),
            DeviceId('a4'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(0.0)),
        },
        allocation_strategy=FractionAllocationStrategy.EVENLY,
    )
    for idx in range(5):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({
            SlotName('x'): Decimal('0.66'),
        })

    with pytest.raises(InsufficientResource):
        alloc_map.allocate({
            SlotName('x'): Decimal('0.06'),
        },
                           min_memory=Decimal(0.6))
    for _ in range(20):
        alloc_map.allocate({
            SlotName('x'): Decimal('0.01'),
        })

    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.05')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.1')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.05')
    alloc_map.free({
        SlotName('x'): {
            DeviceId('a0'): Decimal('0.05'),
            DeviceId('a1'): Decimal('0.1'),
            DeviceId('a2'): Decimal('0.05')
        }
    })
    for idx in range(0):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('0.2')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.2')

    alloc_map.free(result)
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('0.2')},
                                min_memory=Decimal('0.25'))
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '0.2')
    alloc_map.free(result)
    for idx in range(5):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('0.5')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.2')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '0.3')
    alloc_map.free(result)
    for idx in range(5):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('0.65')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.05')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.1')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.2')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '0.3')
    alloc_map.free(result)
    for idx in range(5):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    result = alloc_map.allocate({SlotName('x'): Decimal('0.6')},
                                min_memory=Decimal('0.1'))
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.1')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.2')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a3')] == Decimal(
        '0.3')
    alloc_map.free(result)
    for idx in range(5):
        assert alloc_map.allocations[SlotName('x')][DeviceId(
            f'a{idx}')] == Decimal('0')

    alloc_map = FractionAllocMap(device_slots={
        DeviceId('a0'):
        DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.3')),
        DeviceId('a1'):
        DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.3')),
        DeviceId('a2'):
        DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal('0.9')),
    }, )
    result = alloc_map.allocate({SlotName('x'): Decimal('1')})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.3')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.3')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a2')] == Decimal(
        '0.4')
def test_fraction_alloc_map_iteration():
    alloc_map = FractionAllocMap(
        device_slots={
            DeviceId('a0'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
            DeviceId('a1'):
            DeviceSlotInfo(SlotTypes.COUNT, SlotName('x'), Decimal(1.0)),
        },
        allocation_strategy=FractionAllocationStrategy.FILL,
        quantum_size=Decimal("0.00001"))
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal('0')

    for _ in range(1000):
        alloc_map.allocate({
            SlotName('x'): Decimal('0.00001'),
        })
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.005')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.005')

    alloc_map.free({SlotName('x'): {DeviceId('a0'): Decimal('0.00001')}})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal(
        '0.00499')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.005')

    for _ in range(499):
        alloc_map.free({SlotName('x'): {DeviceId('a0'): Decimal('0.00001')}})
    assert alloc_map.allocations[SlotName('x')][DeviceId('a0')] == Decimal('0')
    assert alloc_map.allocations[SlotName('x')][DeviceId('a1')] == Decimal(
        '0.005')