12d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
22d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# Use of this source code is governed by a BSD-style license that can be
32d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# found in the LICENSE file.
42d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
52d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
62d8047e8b2d901bec66d483664d8b6322501d245Prashanth B"""Cache module for rdb requests/host objects.
72d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
82d8047e8b2d901bec66d483664d8b6322501d245Prashanth BThis module supplies the following api:
92d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    1. A cache backend.
102d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    2. A cache manager for the backend.
112d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    3. A memoize decorator to encapsulate caching logic.
122d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
132d8047e8b2d901bec66d483664d8b6322501d245Prashanth BThis cache manager functions as a lookaside buffer for host requests.
142d8047e8b2d901bec66d483664d8b6322501d245Prashanth BIts correctness is contingent on the following conditions:
152d8047e8b2d901bec66d483664d8b6322501d245Prashanth B1. The concurrency of the rdb remains 0.
162d8047e8b2d901bec66d483664d8b6322501d245Prashanth B2. Clients of the cache don't trust the leased bit on the cached object.
172d8047e8b2d901bec66d483664d8b6322501d245Prashanth B3. The cache is created at the start of a single batched request,
182d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    populated during the request, and completely discarded at the end.
192d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
202d8047e8b2d901bec66d483664d8b6322501d245Prashanth BRather than caching individual hosts, the cache manager maintains
212d8047e8b2d901bec66d483664d8b6322501d245Prashanth B'cache lines'. A cache line is defined as a key: value pair, where
222d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bthe key is as returned by get_key, and the value is a list of RDBHosts
232d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bthat match the key. The following limitations are placed on cache lines:
242d8047e8b2d901bec66d483664d8b6322501d245Prashanth B1. A new line can only contain unleased hosts.
252d8047e8b2d901bec66d483664d8b6322501d245Prashanth B2. A key can only be set once, with a single line, before removal.
262d8047e8b2d901bec66d483664d8b6322501d245Prashanth B3. Every 'get' deletes the entire line.
272d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
282d8047e8b2d901bec66d483664d8b6322501d245Prashanth BConsider the following examples:
292d8047e8b2d901bec66d483664d8b6322501d245Prashanth BNormal case: 3 grouped requests, all with the same deps/acls, but different
302d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bpriorities/parent_job_ids. The requests (X+Y+Z) > matching hosts (K):
312d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request1, count=X)- hits the database, takes X hosts, caches (K-X)
322d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request2, count=Y) - hits the cache and is fully satisfied, caches (K-(X+Y))
332d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request3, count=Z) - hits the cache, needs to acquire (X+Y+Z)-K next tick]:
342d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
352d8047e8b2d901bec66d483664d8b6322501d245Prashanth B Host Count |  RDB                         | Cache
362d8047e8b2d901bec66d483664d8b6322501d245Prashanth B------------------------------------------------------------------
372d8047e8b2d901bec66d483664d8b6322501d245Prashanth BX:          | request1                     | {}
382d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK:          | find_hosts(deps, acls)       |
392d8047e8b2d901bec66d483664d8b6322501d245Prashanth BX:          | leased_hosts                 |
402d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK-X:        | ---------------------------> | {key: [K-X hosts]}
412d8047e8b2d901bec66d483664d8b6322501d245Prashanth BY<K-X:      | request2 <---[K-X hosts]---- | {}
422d8047e8b2d901bec66d483664d8b6322501d245Prashanth BY:          | leased_hosts                 |
432d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK-(X+Y):    | ---------------------------> | {key: [K-(X+Y) hosts]}
442d8047e8b2d901bec66d483664d8b6322501d245Prashanth BZ>K-(X+Y):  | request3 <-[K-(X+Y) hosts]-- | {}
452d8047e8b2d901bec66d483664d8b6322501d245Prashanth BZ-(K-(X+Y)):| leased_hosts                 |
462d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
472d8047e8b2d901bec66d483664d8b6322501d245Prashanth BSince hosts are only released by the scheduler there is no way the
482d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bthird request could have been satisfied completely even if we had checked
492d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bthe database real-time.
502d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
512d8047e8b2d901bec66d483664d8b6322501d245Prashanth BStale cache entries: 3 grouped requests that don't have the same deps/acls.
522d8047e8b2d901bec66d483664d8b6322501d245Prashanth BP(1,2,3) are priorities, with P3 being the highest:
532d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request1(deps=[a,b], P3), Count=X) - Caches hosts
542d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request2(deps=[a], P2), Count=Y) - hits the database
552d8047e8b2d901bec66d483664d8b6322501d245Prashanth B (request3(deps=[a,b], P1)], Count=Z) - Tries to use cached hosts but fails
562d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
572d8047e8b2d901bec66d483664d8b6322501d245Prashanth B Host Count |  RDB                         | Cache
582d8047e8b2d901bec66d483664d8b6322501d245Prashanth B------------------------------------------------------------------
592d8047e8b2d901bec66d483664d8b6322501d245Prashanth BX:          | request1(deps=[a,b])         | {}
602d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK:          | find_hosts(deps=[a,b])       |
612d8047e8b2d901bec66d483664d8b6322501d245Prashanth BX:          | leased_hosts                 |
622d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK-X:        | ---------------------------> | {deps=[a,b]: [(K-X) hosts]}
632d8047e8b2d901bec66d483664d8b6322501d245Prashanth BY<K-X:      | request2(deps=[a])           | {}
642d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK-X:        | find_hosts(deps=[a])         |
652d8047e8b2d901bec66d483664d8b6322501d245Prashanth BY:          | leased_hosts                 |
662d8047e8b2d901bec66d483664d8b6322501d245Prashanth BK-(X+Y):    | ---------------------------> | {deps=[a]: [(K-(X+Y)) hosts],
672d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            |                              |        | overlap |
682d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            |                              |  deps=[a, b], [(K-X) hosts]}
692d8047e8b2d901bec66d483664d8b6322501d245Prashanth BZ:          | request3(deps=[a,b])<-[K-X]--| {deps=[a]: [K-(X+Y) hosts]}
702d8047e8b2d901bec66d483664d8b6322501d245Prashanth BZ-(K-(X+Y)):| leased_hosts                 | {deps=[a]: [N-Y hosts]}
712d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
722d8047e8b2d901bec66d483664d8b6322501d245Prashanth BNote that in the last case, even though the cache returns hosts that
732d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bhave already been assigned to request2, request3 cannot use them. This is
742d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bacceptable because the number of hosts we lease per tick is << the number
752d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bof requests, so it's faster to check leased bits real time than query for hosts.
762d8047e8b2d901bec66d483664d8b6322501d245Prashanth B"""
772d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
782d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
792d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bimport abc
802d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bimport collections
812d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bimport logging
822d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
832d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bimport common
841e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Blackfrom autotest_lib.client.common_lib.cros.graphite import autotest_stats
852d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bfrom autotest_lib.client.common_lib.global_config import global_config
862d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bfrom autotest_lib.scheduler import rdb_utils
872d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
882d8047e8b2d901bec66d483664d8b6322501d245Prashanth BMEMOIZE_KEY = 'memoized_hosts'
892d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
902d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bdef memoize_hosts(func):
912d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """Decorator used to memoize through the cache manager.
922d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
932d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    @param func: The function/method to decorate.
942d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        Before calling this function we check the cache for values matching
952d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        its request argument, and anything returned by the function is cached
962d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        cached under the same request.
972d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """
982d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def cache(self, request, count, **kwargs):
992d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Caching function for the memoize decorator.
1002d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1012d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param request: The rdb request, as defined in rdb_requests.
1022d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param count: The count of elements needed to satisfy the request.
1032d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param kwargs:
1042d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            Named args for the memoized function. This map should not contain
1052d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            the key MEMOIZED_KEY, as this is reserved for the passing of
1062d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            the cached/memoized hosts to the function itself.
1072d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
1082d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        cache_key = self.cache.get_key(request.deps, request.acls)
1092d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        try:
1102d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            kwargs[MEMOIZE_KEY] = self.cache.get_line(cache_key)
1112d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        except rdb_utils.CacheMiss:
1122d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            pass
1132d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        hosts = func(self, request, count, **kwargs)
1142d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self.cache.set_line(cache_key, hosts)
1152d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return hosts
1162d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    return cache
1172d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1182d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1192d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bclass CacheBackend(object):
1202d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """Base class for a cache backend."""
1212d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    __metaclass__ = abc.ABCMeta
1222d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1232d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def set(self, key, value):
1242d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Set a key.
1252d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1262d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key to set.
1272d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param value: The value to cache.
1282d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
1292d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        pass
1302d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1312d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1322d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def get(self, key):
1332d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Get the value stored under a key.
1342d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1352d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key to retrieve the value for.
1362d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: The value stored under the key.
1372d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @raises KeyError: If the key isn't present in the cache.
1382d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
1392d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        pass
1402d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1412d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1422d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def delete(self, key):
1432d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Delete the key, value pair from the cache.
1442d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1452d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key used to find the key, value pair to delete.
1462d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @raises KeyError: If the key isn't already in the cache.
1472d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
1482d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        pass
1492d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1502d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1512d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def has_key(self, key):
1522d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Check if the key exists in the cache.
1532d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1542d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key to check.
1552d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: True if the key is in the cache.
1562d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
1572d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return False
1582d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1592d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1602d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bclass DummyCacheBackend(CacheBackend):
1612d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """A dummy cache backend.
1622d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1632d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    This cache will claim to have no keys. Every get is a cache miss.
1642d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """
1652d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1662d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def get(self, key):
1672d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        raise KeyError
1682d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1692d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1702d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bclass InMemoryCacheBackend(CacheBackend):
1712d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """In memory cache backend.
1722d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1732d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    Uses a simple dictionary to store key, value pairs.
1742d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """
1752d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def __init__(self):
1762d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self._cache = {}
1772d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1782d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def get(self, key):
1792d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return self._cache[key]
1802d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1812d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def set(self, key, value):
1822d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self._cache[key] = value
1832d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1842d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def delete(self, key):
1852d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self._cache.pop(key)
1862d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1872d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def has_key(self, key):
1882d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return key in self._cache
1892d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1902d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# TODO: Implement a MemecacheBackend, invalidate when unleasing a host, refactor
1912d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# the AcquireHostRequest to contain a core of (deps, acls) that we can use as
1922d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# the key for population and invalidation. The caching manager is still valid,
1932d8047e8b2d901bec66d483664d8b6322501d245Prashanth B# regardless of the backend.
1942d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1952d8047e8b2d901bec66d483664d8b6322501d245Prashanth Bclass RDBHostCacheManager(object):
1962d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    """RDB Cache manager."""
1972d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
1982d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    key = collections.namedtuple('key', ['deps', 'acls'])
1992d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    use_cache = global_config.get_config_value(
2002d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            'RDB', 'use_cache', type=bool, default=True)
2012d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2022d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def __init__(self):
2032d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self._cache_backend = (InMemoryCacheBackend()
2042d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                               if self.use_cache else DummyCacheBackend())
2052d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self.hits = 0
2062d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self.misses = 0
2072d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self.stale_entries = []
2082d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2092d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2102d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def mean_staleness(self):
2112d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Compute the average stale entries per line.
2122d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2132d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: A floating point representing the mean staleness.
2142d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
2152d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return (reduce(lambda x, y: float(x+y), self.stale_entries)/
2162d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                len(self.stale_entries)) if self.stale_entries else 0
2172d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2182d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2192d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def hit_ratio(self):
2202d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Compute the hit ratio of this cache.
2212d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2222d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: A floating point percentage of the hit ratio.
2232d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
2242d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        if not self.hits and not self.misses:
2252d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            return 0
2262d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        requests = float(self.hits + self.misses)
2272d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return (self.hits/requests) * 100
2282d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2292d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2302d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def record_stats(self):
2312d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Record stats about the cache managed by this instance."""
2322d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        hit_ratio = self.hit_ratio()
2332d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        staleness = self.mean_staleness()
2342d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        logging.debug('Cache stats: hit ratio: %.2f%%, '
2352d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                      'avg staleness per line: %.2f%%.', hit_ratio, staleness)
2361e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black        autotest_stats.Gauge(rdb_utils.RDB_STATS_KEY).send(
2371e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black                'cache.hit_ratio', hit_ratio)
2381e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black        autotest_stats.Gauge(rdb_utils.RDB_STATS_KEY).send(
2391e1c41b1b4a1b97c0b7086b8430856ed45e064d3Gabe Black                'cache.stale_entries', staleness)
2402d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2412d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2422d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    @classmethod
2432d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def get_key(cls, deps, acls):
2442d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Return a key for the given deps, acls.
2452d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2462d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param deps: A list of deps, as taken by the AcquireHostRequest.
2472d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param acls: A list of acls, as taken by the AcquireHostRequest.
2482d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: A cache key for the given deps/acls.
2492d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
2502d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # All requests with the same deps, acls should hit the same cache line.
2512d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # TODO: Do something smarter with acls, only one needs to match.
2522d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return cls.key(deps=frozenset(deps), acls=frozenset(acls))
2532d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2542d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2552d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def get_line(self, key):
2562d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Clear and return the cache line matching the key.
2572d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2582d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key the desired cache_line is stored under.
2592d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @return: A list of rdb hosts matching the key, or None.
2602d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2612d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @raises rdb_utils.CacheMiss: If the key isn't in the cache.
2622d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
2632d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        try:
2642d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            cache_line = self._cache_backend.get(key)
2652d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        except KeyError:
2662d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            self.misses += 1
2672d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            raise rdb_utils.CacheMiss('Key %s not in cache' % (key,))
2682d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self.hits += 1
2692d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        self._cache_backend.delete(key)
2702d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        return list(cache_line)
2712d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2722d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2732d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def _check_line(self, line, key):
2742d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Sanity check a cache line.
2752d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2762d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        This method assumes that a cache line is made up of RDBHost objects,
2772d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        and checks to see if they all match each other/the key passed in.
2782d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        Checking is done in terms of host labels and acls, note that the hosts
2792d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        in the line can have different deps/acls, as long as they all have the
2802d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        deps required by the key, and at least one matching acl of the key.
2812d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2822d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param line: The cache line value.
2832d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param key: The key the line will be stored under.
2842d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @raises rdb_utils.RDBException:
2852d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            If one of the hosts in the cache line is already leased.
2862d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            The cache already has a different line under the given key.
2872d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            The given key doesn't match the hosts in the line.
2882d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
2892d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # Note that this doesn't mean that all hosts in the cache are unleased.
2902d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        if any(host.leased for host in line):
2912d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            raise rdb_utils.RDBException('Cannot cache leased hosts %s' % line)
2922d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
2932d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # Confirm that the given line can be used to service the key by checking
2942d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # that all hosts have the deps mentioned in the key, and at least one
2952d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # matching acl.
2962d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        h_keys = set([self.get_key(host.labels, host.acls) for host in line])
2972d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        for h_key in h_keys:
2982d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            if (not h_key.deps.issuperset(key.deps) or
2992d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                    not key.acls.intersection(h_key.acls)):
3002d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                raise rdb_utils.RDBException('Given key: %s does not match key '
3012d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                        'computed from hosts in line: %s' % (key, h_keys))
3022d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        if self._cache_backend.has_key(key):
3032d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            raise rdb_utils.RDBException('Cannot override a cache line. It '
3042d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                    'must be cleared before setting. Key: %s, hosts %s' %
3052d8047e8b2d901bec66d483664d8b6322501d245Prashanth B                    (key, line))
3062d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
3072d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
3082d8047e8b2d901bec66d483664d8b6322501d245Prashanth B    def set_line(self, key, hosts):
3092d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """Cache a list of similar hosts.
3102d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
3112d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        set_line will no-op if:
3122d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            The hosts aren't all unleased.
3132d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            The hosts don't have deps/acls matching the key.
3142d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            A cache line under the same key already exists.
3152d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        The first 2 cases will lead to a cache miss in the corresponding get.
3162d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
3172d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @param hosts: A list of unleased hosts with the same deps/acls.
3182d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        @raises RDBException: If hosts is None, since None is reserved for
3192d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            key expiration.
3202d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        """
3212d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        if hosts is None:
3222d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            raise rdb_utils.RDBException('Cannot set None in the cache.')
3232d8047e8b2d901bec66d483664d8b6322501d245Prashanth B
3242d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # An empty list means no hosts matching the request are available.
3252d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        # This can happen if a previous request leased all matching hosts.
3262d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        if not hosts or not self.use_cache:
3272d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            self._cache_backend.set(key, [])
3282d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            return
3292d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        try:
3302d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            self._check_line(hosts, key)
3312d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        except rdb_utils.RDBException as e:
3322d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            logging.error(e)
3332d8047e8b2d901bec66d483664d8b6322501d245Prashanth B        else:
3342d8047e8b2d901bec66d483664d8b6322501d245Prashanth B            self._cache_backend.set(key, set(hosts))
335