Ejemplo n.º 1
0
    def test_reconfiguration_lock(self):
        mlos_object = MlosObject(int, int)

        assert not mlos_object._reconfiguration_lock._is_owned()
        with mlos_object.reconfiguration_lock():
            assert mlos_object._reconfiguration_lock._is_owned()
        assert not mlos_object._reconfiguration_lock._is_owned()
Ejemplo n.º 2
0
    def test_reconfiguration_lock(self):
        mlos_object = MlosObject(int, int)

        self.assertFalse(mlos_object._reconfiguration_lock._is_owned())
        with mlos_object.reconfiguration_lock():
            self.assertTrue(mlos_object._reconfiguration_lock._is_owned())
        self.assertFalse(mlos_object._reconfiguration_lock._is_owned())
Ejemplo n.º 3
0
    def __init__(self, logger):
        self.logger = logger
        self.current_config = None

        # Let's use an mlos_object to drive the runtime configuration of the cache
        self.mlos_object = MlosObject(
            smart_component_type=type(self),
            smart_component_runtime_attributes=self.RuntimeAttributes(
                component_id=id(self)))
        self.mlos_object.register()
        self.reconfigure()
Ejemplo n.º 4
0
    def __init__(self, logger):
        self.logger = logger
        self.mlos_object = MlosObject(
            smart_component_type=type(self),
            smart_component_runtime_attributes=self.RuntimeAttributes(
                component_id=id(self)))
        self.current_config = Configuration(component_type=SmartCache,
                                            values=self.default_config,
                                            id=-1)
        self.cache_implementation = LruCache(
            max_size=self.current_config.values.lru_cache_config.cache_size,
            logger=self.logger)
        self.mlos_object.register()

        self.reconfigure()
Ejemplo n.º 5
0
class SmartCacheWorkloadGenerator:
    """Instantiates one (or many) SmartCaches and subjects them to various workloads.

    The main idea here is to exercise all use-cases demanded of Mlos.

    Parameters
    ----------
    logger : Logger
        Logger.

    Attributes
    ----------
    RuntimeAttributes : MlosSmartComponentRuntimeAttributes
    default_config : Point
    telemetry_message_types : list
    mlos_object : MlosObject

    """
    RuntimeAttributes = MlosSmartComponentRuntimeAttributes(
        smart_component_name="SmartCacheWorkloadGenerator", attribute_names=[])

    default_config = smart_cache_workload_generator_default_config
    parameter_search_space = smart_cache_workload_generator_config_space
    telemetry_message_types = []

    def __init__(self, logger):
        self.logger = logger
        self.current_config = None

        # Let's use an mlos_object to drive the runtime configuration of the cache
        self.mlos_object = MlosObject(
            smart_component_type=type(self),
            smart_component_runtime_attributes=self.RuntimeAttributes(
                component_id=id(self)))
        self.mlos_object.register()
        self.reconfigure()

    def __del__(self):
        self.mlos_object.unregister()

    def reconfigure(self):
        new_config = self.mlos_object.config
        if new_config == self.current_config:
            return False

        self.mlos_object.send_telemetry_message(
            SmartCacheWorkloadGeneratorReconfigure(
                old_config_id=self.current_config.id
                if self.current_config is not None else 0,
                new_config_id=new_config.id))
        self.current_config = new_config
        return True

    def run(self, timeout_s=1):
        self.logger.info(
            f"Started the SmartCacheWorkloadGenerator. Duration={timeout_s}")
        self.reconfigure()
        smart_cache = SmartCache(logger=self.logger)

        start_time = datetime.datetime.utcnow()
        end_time = start_time + datetime.timedelta(seconds=timeout_s)

        while datetime.datetime.utcnow() < end_time:

            if self.current_config.values.workload_type == 'fibonacci':
                range_min = self.current_config.values.fibonacci_config.min
                range_max = range_min + self.current_config.values.fibonacci_config.range_width

                while datetime.datetime.utcnow() < end_time:
                    sequence_number = random.randint(range_min, range_max)
                    self.logger.debug(f"\tfib({sequence_number}) = ?")
                    result = self.fibonacci(sequence_number, smart_cache)
                    self.logger.debug(f"\tfib({sequence_number}) = {result}")

            elif self.current_config.values.workload_type == 'random_key_from_range':
                range_min = self.current_config.values.random_key_from_range_config.min
                range_max = range_min + self.current_config.values.random_key_from_range_config.range_width
                while datetime.datetime.utcnow() < end_time:
                    key = random.randint(range_min, range_max)
                    value = smart_cache.get(key)
                    if value is None:
                        value = str(key)
                        smart_cache.push(key, value)

            elif self.current_config.values.workload_type == 'sequential_key_from_range':
                range_min = self.current_config.values.sequential_key_from_range_config.min
                range_width = self.current_config.values.sequential_key_from_range_config.range_width
                i = 0
                while datetime.datetime.utcnow() < end_time:
                    key = range_min + (i % range_width)
                    value = smart_cache.get(key)
                    if value is None:
                        value = str(key)
                        smart_cache.push(key, value)
                    i += 1
            else:
                raise RuntimeError(
                    f"Unknown workload type: {self.current_config.values.workload_type}"
                )

        self.logger.info("Exiting the SmartCacheWorkloadGenerator.")

    class _FibonacciValue:
        def __init__(self, sequence_number, value, previous):
            self.sequence_number = sequence_number
            self.value = value
            self.previous = previous

    def fibonacci(self, sequence_number, smart_cache):
        existing_result = smart_cache.get(sequence_number)

        max_key_up_to = None
        if existing_result is None:
            all_cached_smaller_keys = [
                element.key for element in smart_cache
                if element.key < sequence_number
            ]
            if all_cached_smaller_keys:
                max_key_up_to = max(all_cached_smaller_keys)

        start = 1
        previous = 1
        current = 1

        if max_key_up_to is not None:
            cached_value = smart_cache.get(max_key_up_to)
            if cached_value is not None:
                previous = cached_value.previous
                current = cached_value.value
                start = cached_value.sequence_number

        for i in range(start + 1, sequence_number):
            previous, current = current, previous + current
            smart_cache.push(key=i,
                             value=self._FibonacciValue(sequence_number=i,
                                                        value=current,
                                                        previous=previous))

        return current
Ejemplo n.º 6
0
class SmartCache:
    """ A tunable and observable cache that takes advantage of Mlos.

    The goal here is to provide a bunch of cache implementations that are parameterizable.

    Parameters
    ----------
    logger : Logger
        Logger to use.

    Attributes
    ----------
    RuntimeAttributes : MlosSmartComponentRuntimeAttributes
    parameter_search_space : SimpleHypergrid
    default_config : Point
    telemetry_message_types : list
    runtime_decision_contexts : list
    """

    # Used during registration
    RuntimeAttributes = MlosSmartComponentRuntimeAttributes(
        smart_component_name="SmartCache", attribute_names=[])

    # Used to inform the Mlos Global Context about all types of telemetry messages that this component can emit
    telemetry_message_types = [
        (SmartCachePush, 0b1),
        (SmartCacheGet, 0b10),
        (SmartCacheEvict, 0b100),
    ]

    # Used to inform the Mlos Global Context about all types of runtime decisions that can be expected
    runtime_decision_contexts = [
        PushRuntimeDecisionContext,
        ReconfigurationRuntimeDecisionContext,
    ]

    default_config = smart_cache_config_store.default
    parameter_search_space = smart_cache_config_store.parameter_space

    def __init__(self, logger):
        self.logger = logger
        self.mlos_object = MlosObject(
            smart_component_type=type(self),
            smart_component_runtime_attributes=self.RuntimeAttributes(
                component_id=id(self)))
        self.current_config = Configuration(
            component_type=SmartCache,
            values=smart_cache_config_store.default,
            id=-1)
        self.cache_implementation = LruCache(
            max_size=self.current_config.values.lru_cache_config.cache_size,
            logger=self.logger)
        self.mlos_object.register()

        self.reconfigure()

    def __del__(self):
        self.mlos_object.unregister()

    def __iter__(self):
        return self.cache_implementation.__iter__()

    def __len__(self):
        return len(self.cache_implementation)

    def __contains__(self, item):
        return item in self.cache_implementation

    def push(self, key, value):
        self.reconfigure()  # TODO: make this less frequent

        if key in self:
            return

        should_push = self.mlos_object.make_runtime_decision(
            PushRuntimeDecisionContext(mlos_object=self.mlos_object,
                                       current_config=self.current_config))

        if not should_push:
            return

        if self.mlos_object.is_message_type_enabled(SmartCachePush):
            # Note that we hide this behind an 'is_enabled' check. This is for the cases
            # when assembling the message itself can be expensive.
            self.mlos_object.send_telemetry_message(SmartCachePush(key=key))
        cache_entry = CacheEntry(key, value)

        evicted_cache_entry = self.cache_implementation.push(cache_entry)

        if evicted_cache_entry is not None:
            # Note that here we skip the 'is message type enabled check' since assembling the message is cheap and
            # the check can be done by mlos_object
            self.mlos_object.send_telemetry_message(
                SmartCacheEvict(key=evicted_cache_entry.key))

    def get(self, key):
        if key not in self.cache_implementation:
            self.mlos_object.send_telemetry_message(
                SmartCacheGet(key=key, was_hit=False))
            return None
        self.mlos_object.send_telemetry_message(
            SmartCacheGet(key=key, was_hit=True))
        return self.cache_implementation.get(key)

    def reconfigure(self):
        """ Reconfigures the cache according to the configuration present in self.mlos_object

        :return:
        """
        smart_cache_reconfiguration_decision_runtime_context = ReconfigurationRuntimeDecisionContext(
            self.mlos_object)
        should_reconfigure = self.mlos_object.make_runtime_decision(
            smart_cache_reconfiguration_decision_runtime_context)
        if not should_reconfigure or self.current_config == self.mlos_object.config or self.mlos_object.config is None:
            return

        self.current_config = self.mlos_object.config
        self.logger.debug(
            f"Reconfiguring. New config values: {self.current_config.values.to_json()}"
        )

        if self.current_config.values.implementation == 'LRU':
            self.cache_implementation = LruCache(
                max_size=self.current_config.values.lru_cache_config.
                cache_size,
                logger=self.logger)
        elif self.current_config.values.implementation == 'MRU':
            self.cache_implementation = MruCache(
                max_size=self.current_config.values.mru_cache_config.
                cache_size,
                logger=self.logger)
        else:
            raise RuntimeError("Invalid config")