Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c27ef908 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "BluetoothMetrics: Implement and test metric_id_allocator and lru" am:...

Merge "BluetoothMetrics: Implement and test metric_id_allocator and lru" am: 56cda3ea am: f6b3e6e2 am: 82bbf984 am: 68ede599 am: 0807405b

Change-Id: I67a09ccafbb051fab9d80e7ad2ab93aa1ee74701
parents b069914b 0807405b
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -12,6 +12,7 @@ cc_library_static {
    srcs: [
    srcs: [
        "address_obfuscator.cc",
        "address_obfuscator.cc",
        "message_loop_thread.cc",
        "message_loop_thread.cc",
        "metric_id_allocator.cc",
        "metrics.cc",
        "metrics.cc",
        "once_timer.cc",
        "once_timer.cc",
        "repeating_timer.cc",
        "repeating_timer.cc",
@@ -40,8 +41,10 @@ cc_test {
    srcs: [
    srcs: [
        "address_obfuscator_unittest.cc",
        "address_obfuscator_unittest.cc",
        "leaky_bonded_queue_unittest.cc",
        "leaky_bonded_queue_unittest.cc",
        "lru_unittest.cc",
        "message_loop_thread_unittest.cc",
        "message_loop_thread_unittest.cc",
        "metrics_unittest.cc",
        "metrics_unittest.cc",
        "metric_id_allocator_unittest.cc",
        "once_timer_unittest.cc",
        "once_timer_unittest.cc",
        "repeating_timer_unittest.cc",
        "repeating_timer_unittest.cc",
        "state_machine_unittest.cc",
        "state_machine_unittest.cc",

system/common/lru.h

0 → 100644
+178 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2020 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#pragma once

#include <functional>
#include <iterator>
#include <list>
#include <mutex>
#include <thread>
#include <unordered_map>

#include <base/logging.h>

namespace bluetooth {

namespace common {

template <typename K, typename V>
class LruCache {
 public:
  using Node = std::pair<K, V>;
  using LruEvictionCallback = std::function<void(K, V)>;
  /**
   * Constructor of the cache
   *
   * @param capacity maximum size of the cache
   * @param log_tag, keyword to put at the head of log.
   * @param lru_eviction_callback a call back will be called when the cache is
   * full and Put() is called
   */
  LruCache(const size_t& capacity, const std::string& log_tag,
           LruEvictionCallback lru_eviction_callback)
      : capacity_(capacity), lru_eviction_callback_(lru_eviction_callback) {
    if (capacity_ == 0) {
      // don't allow invalid capacity
      LOG(FATAL) << log_tag << " unable to have 0 LRU Cache capacity";
    }
  }

  ~LruCache() { Clear(); }

  /**
   * Clear the cache
   */
  void Clear() {
    std::lock_guard<std::mutex> lock(lru_mutex_);
    lru_map_.clear();
    node_list_.clear();
  }

  /**
   * Get the value of a key, and move the key to the head of cache, if there is
   * one
   *
   * @param key
   * @param value, output parameter of value of the key
   * @return true if the cache has the key
   */
  bool Get(const K& key, V* value) {
    std::lock_guard<std::mutex> lock(lru_mutex_);
    auto map_iterator = lru_map_.find(key);
    if (map_iterator == lru_map_.end()) {
      return false;
    }
    auto& list_iterator = map_iterator->second;
    auto node = *list_iterator;
    node_list_.erase(list_iterator);
    node_list_.push_front(node);
    map_iterator->second = node_list_.begin();
    *value = node.second;
    return true;
  }

  /**
   * Check if the cache has the input key, move the key to the head
   * if there is one
   *
   * @param key
   * @return true if the cache has the key
   */
  bool HasKey(const K& key) {
    V dummy_value;
    return Get(key, &dummy_value);
  }

  /**
   * Put a key-value pair to the head of cache
   *
   * @param key
   * @param value
   * @return true if tail value is popped
   */
  bool Put(const K& key, const V& value) {
    if (HasKey(key)) {
      // hasKey() calls get(), therefore already move the node to the head
      std::lock_guard<std::mutex> lock(lru_mutex_);
      lru_map_[key]->second = value;
      return false;
    }

    bool value_popped = false;
    std::lock_guard<std::mutex> lock(lru_mutex_);
    // remove tail
    if (lru_map_.size() == capacity_) {
      lru_map_.erase(node_list_.back().first);
      node_list_.pop_back();
      lru_eviction_callback_(node_list_.back().first, node_list_.back().second);
      value_popped = true;
    }
    // insert to dummy next;
    Node add(key, value);
    node_list_.push_front(add);
    lru_map_[key] = node_list_.begin();
    return value_popped;
  }

  /**
   * Delete a key from cache
   *
   * @param key
   * @return true if delete successfully
   */
  bool Remove(const K& key) {
    std::lock_guard<std::mutex> lock(lru_mutex_);
    if (lru_map_.count(key) == 0) {
      return false;
    }

    // remove from the list
    auto& iterator = lru_map_[key];
    node_list_.erase(iterator);

    // delete key from map
    lru_map_.erase(key);

    return true;
  }

  /**
   * Return size of the cache
   *
   * @return size of the cache
   */
  int Size() const {
    std::lock_guard<std::mutex> lock(lru_mutex_);
    return lru_map_.size();
  }

 private:
  std::list<Node> node_list_;
  size_t capacity_;
  std::unordered_map<K, typename std::list<Node>::iterator> lru_map_;
  LruEvictionCallback lru_eviction_callback_;
  mutable std::mutex lru_mutex_;

  // delete copy constructor
  LruCache(LruCache const&) = delete;
  LruCache& operator=(LruCache const&) = delete;
};

}  // namespace common
}  // namespace bluetooth
+242 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2020 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#include <base/logging.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <limits>

#include "common/lru.h"

namespace testing {

using bluetooth::common::LruCache;

TEST(BluetoothLruCacheTest, LruCacheMainTest1) {
  int* value = new int(0);
  int dummy = 0;
  int* pointer = &dummy;
  auto callback = [pointer](int a, int b) { (*pointer) = 1; };
  LruCache<int, int> cache(3, "testing", callback);  // capacity = 3;
  cache.Put(1, 10);
  EXPECT_EQ(cache.Size(), 1);
  cache.Put(2, 20);
  cache.Put(3, 30);
  EXPECT_EQ(cache.Size(), 3);
  EXPECT_EQ(dummy, 0);

  // 1, 2, 3 should be in cache
  EXPECT_TRUE(cache.Get(1, value));
  EXPECT_EQ(*value, 10);
  EXPECT_TRUE(cache.Get(2, value));
  EXPECT_EQ(*value, 20);
  EXPECT_TRUE(cache.Get(3, value));
  EXPECT_EQ(*value, 30);
  EXPECT_EQ(cache.Size(), 3);

  cache.Put(4, 40);
  EXPECT_EQ(dummy, 1);
  // 2, 3, 4 should be in cache, 1 should not
  EXPECT_FALSE(cache.Get(1, value));
  EXPECT_TRUE(cache.Get(4, value));
  EXPECT_EQ(*value, 40);
  EXPECT_TRUE(cache.Get(2, value));
  EXPECT_EQ(*value, 20);
  EXPECT_TRUE(cache.Get(3, value));
  EXPECT_EQ(*value, 30);

  cache.Put(5, 50);
  EXPECT_EQ(cache.Size(), 3);
  // 2, 3, 5 should be in cache

  EXPECT_TRUE(cache.Remove(3));
  cache.Put(6, 60);
  // 2, 5, 6 should be in cache

  EXPECT_FALSE(cache.Get(3, value));
  EXPECT_FALSE(cache.Get(4, value));
  EXPECT_TRUE(cache.Get(2, value));
  EXPECT_EQ(*value, 20);
  EXPECT_TRUE(cache.Get(5, value));
  EXPECT_EQ(*value, 50);
  EXPECT_TRUE(cache.Get(6, value));
  EXPECT_EQ(*value, 60);
}

TEST(BluetoothLruCacheTest, LruCacheMainTest2) {
  int* value = new int(0);
  int dummy = 0;
  int* pointer = &dummy;
  auto callback = [pointer](int a, int b) { (*pointer)++; };
  LruCache<int, int> cache(2, "testing", callback);  // size = 2;
  cache.Put(1, 10);
  cache.Put(2, 20);
  EXPECT_EQ(dummy, 0);
  cache.Put(3, 30);
  EXPECT_EQ(dummy, 1);
  cache.Put(2, 200);
  EXPECT_EQ(dummy, 1);
  EXPECT_EQ(cache.Size(), 2);
  // 3, 2 should be in cache

  EXPECT_FALSE(cache.HasKey(1));
  EXPECT_TRUE(cache.Get(2, value));
  EXPECT_EQ(*value, 200);
  EXPECT_TRUE(cache.Get(3, value));
  EXPECT_EQ(*value, 30);

  cache.Put(4, 40);
  EXPECT_EQ(dummy, 2);
  // 3, 4 should be in cache

  EXPECT_FALSE(cache.HasKey(2));
  EXPECT_TRUE(cache.Get(3, value));
  EXPECT_EQ(*value, 30);
  EXPECT_TRUE(cache.Get(4, value));
  EXPECT_EQ(*value, 40);

  EXPECT_TRUE(cache.Remove(4));
  EXPECT_EQ(cache.Size(), 1);
  cache.Put(2, 2000);
  // 3, 2 should be in cache

  EXPECT_FALSE(cache.HasKey(4));
  EXPECT_TRUE(cache.Get(3, value));
  EXPECT_EQ(*value, 30);
  EXPECT_TRUE(cache.Get(2, value));
  EXPECT_EQ(*value, 2000);

  EXPECT_TRUE(cache.Remove(2));
  EXPECT_TRUE(cache.Remove(3));
  cache.Put(5, 50);
  cache.Put(1, 100);
  cache.Put(1, 1000);
  EXPECT_EQ(cache.Size(), 2);
  // 1, 5 should be in cache

  EXPECT_FALSE(cache.HasKey(2));
  EXPECT_FALSE(cache.HasKey(3));
  EXPECT_TRUE(cache.Get(1, value));
  EXPECT_EQ(*value, 1000);
  EXPECT_TRUE(cache.Get(5, value));
  EXPECT_EQ(*value, 50);
}

TEST(BluetoothLruCacheTest, LruCacheRemoveTest) {
  LruCache<int, int> cache(10, "testing", [](int a, int b) {});
  for (int key = 0; key <= 30; key++) {
    cache.Put(key, key * 100);
  }
  for (int key = 0; key <= 20; key++) {
    EXPECT_FALSE(cache.HasKey(key));
  }
  for (int key = 21; key <= 30; key++) {
    EXPECT_TRUE(cache.HasKey(key));
  }
  for (int key = 21; key <= 30; key++) {
    EXPECT_TRUE(cache.Remove(key));
  }
  for (int key = 21; key <= 30; key++) {
    EXPECT_FALSE(cache.HasKey(key));
  }
}

TEST(BluetoothLruCacheTest, LruCacheClearTest) {
  LruCache<int, int> cache(10, "testing", [](int a, int b) {});
  for (int key = 0; key < 10; key++) {
    cache.Put(key, key * 100);
  }
  for (int key = 0; key < 10; key++) {
    EXPECT_TRUE(cache.HasKey(key));
  }
  cache.Clear();
  for (int key = 0; key < 10; key++) {
    EXPECT_FALSE(cache.HasKey(key));
  }

  for (int key = 0; key < 10; key++) {
    cache.Put(key, key * 1000);
  }
  for (int key = 0; key < 10; key++) {
    EXPECT_TRUE(cache.HasKey(key));
  }
}

TEST(BluetoothLruCacheTest, LruCachePressureTest) {
  auto started = std::chrono::high_resolution_clock::now();
  int max_size = 0xFFFFF;  // 2^20 = 1M
  LruCache<int, int> cache(static_cast<size_t>(max_size), "testing",
                           [](int a, int b) {});

  // fill the cache
  for (int key = 0; key < max_size; key++) {
    cache.Put(key, key);
  }

  // make sure the cache is full
  for (int key = 0; key < max_size; key++) {
    EXPECT_TRUE(cache.HasKey(key));
  }

  // refresh the entire cache
  for (int key = 0; key < max_size; key++) {
    int new_key = key + max_size;
    cache.Put(new_key, new_key);
    EXPECT_FALSE(cache.HasKey(key));
    EXPECT_TRUE(cache.HasKey(new_key));
  }

  // clear the entire cache
  int* value = new int(0);
  for (int key = max_size; key < 2 * max_size; key++) {
    EXPECT_TRUE(cache.Get(key, value));
    EXPECT_EQ(*value, key);
    EXPECT_TRUE(cache.Remove(key));
  }
  EXPECT_EQ(cache.Size(), 0);

  // test execution time
  auto done = std::chrono::high_resolution_clock::now();
  int execution_time =
      std::chrono::duration_cast<std::chrono::milliseconds>(done - started)
          .count();
  // always around 750ms on flame. 1400 ms on crosshatch, 6800 ms on presubmit
  // Shouldn't be more than 10000ms
  EXPECT_LT(execution_time, 10000);
}

TEST(BluetoothLruCacheTest, BluetoothLruMultiThreadPressureTest) {
  LruCache<int, int> cache(100, "testing", [](int a, int b) {});
  auto pointer = &cache;
  // make sure no deadlock
  std::vector<std::thread> workers;
  for (int key = 0; key < 100; key++) {
    workers.push_back(std::thread([key, pointer]() {
      pointer->Put(key, key);
      EXPECT_TRUE(pointer->HasKey(key));
      EXPECT_TRUE(pointer->Remove(key));
    }));
  }
  for (auto& worker : workers) {
    worker.join();
  }
  EXPECT_EQ(cache.Size(), 0);
}

}  // namespace testing
+173 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2020 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#include <base/logging.h>
#include <functional>
#include <mutex>
#include <thread>

#include "metric_id_allocator.h"

namespace bluetooth {

namespace common {

const std::string MetricIdAllocator::LOG_TAG = "BluetoothMetricIdAllocator";
const size_t MetricIdAllocator::kMaxNumUnpairedDevicesInMemory = 200;
const size_t MetricIdAllocator::kMaxNumPairedDevicesInMemory = 400;
const int MetricIdAllocator::kMinId = 1;
const int MetricIdAllocator::kMaxId = 65534;  // 2^16 - 2

// id space should always be larger than kMaxNumPairedDevicesInMemory +
// kMaxNumUnpairedDevicesInMemory
static_assert((MetricIdAllocator::kMaxNumUnpairedDevicesInMemory +
               MetricIdAllocator::kMaxNumPairedDevicesInMemory) <
                  (MetricIdAllocator::kMaxId - MetricIdAllocator::kMinId),
              "id space should always be larger than "
              "kMaxNumPairedDevicesInMemory + MaxNumUnpairedDevicesInMemory");

MetricIdAllocator::MetricIdAllocator()
    : paired_device_cache_(kMaxNumPairedDevicesInMemory, LOG_TAG,
                           [this](RawAddress dummy, int to_remove) {
                             this->id_set_.erase(to_remove);
                           }),
      temporary_device_cache_(kMaxNumUnpairedDevicesInMemory, LOG_TAG,
                              [this](RawAddress dummy, int to_remove) {
                                this->id_set_.erase(to_remove);
                              }) {}

bool MetricIdAllocator::Init(
    const std::unordered_map<RawAddress, int>& paired_device_map,
    Callback save_id_callback, Callback forget_device_callback) {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  if (initialized_) {
    return false;
  }

  // init paired_devices_map
  if (paired_device_map.size() > kMaxNumPairedDevicesInMemory) {
    LOG(FATAL)
        << LOG_TAG
        << "Paired device map is bigger than kMaxNumPairedDevicesInMemory";
    // fail loudly to let caller know
    return false;
  }

  next_id_ = kMinId;
  for (const std::pair<RawAddress, int>& p : paired_device_map) {
    if (p.second < kMinId || p.second > kMaxId) {
      LOG(FATAL) << LOG_TAG << "Invalid Bluetooth Metric Id in config";
    }
    paired_device_cache_.Put(p.first, p.second);
    id_set_.insert(p.second);
    next_id_ = std::max(next_id_, p.second + 1);
  }
  if (next_id_ > kMaxId) {
    next_id_ = kMinId;
  }

  // init callbacks
  save_id_callback_ = save_id_callback;
  forget_device_callback_ = forget_device_callback;

  return initialized_ = true;
}

MetricIdAllocator::~MetricIdAllocator() { Close(); }

bool MetricIdAllocator::Close() {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  if (!initialized_) {
    return false;
  }
  paired_device_cache_.Clear();
  temporary_device_cache_.Clear();
  id_set_.clear();
  initialized_ = false;
  return true;
}

MetricIdAllocator& MetricIdAllocator::GetInstance() {
  static MetricIdAllocator metric_id_allocator;
  return metric_id_allocator;
}

bool MetricIdAllocator::IsEmpty() const {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  return paired_device_cache_.Size() == 0 &&
         temporary_device_cache_.Size() == 0;
}

// call this function when a new device is scanned
int MetricIdAllocator::AllocateId(const RawAddress& mac_address) {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  int id = 0;
  // if already have an id, return it
  if (paired_device_cache_.Get(mac_address, &id)) {
    return id;
  }
  if (temporary_device_cache_.Get(mac_address, &id)) {
    return id;
  }

  // find next available id
  while (id_set_.count(next_id_) > 0) {
    next_id_++;
    if (next_id_ > kMaxId) {
      next_id_ = kMinId;
      LOG(WARNING) << LOG_TAG << "Bluetooth metric id overflow.";
    }
  }
  id = next_id_++;
  id_set_.insert(id);
  temporary_device_cache_.Put(mac_address, id);

  if (next_id_ > kMaxId) {
    next_id_ = kMinId;
  }
  return id;
}

// call this function when a device is paired
bool MetricIdAllocator::SaveDevice(const RawAddress& mac_address) {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  int id = 0;
  bool success = temporary_device_cache_.Get(mac_address, &id);
  success &= temporary_device_cache_.Remove(mac_address);
  if (success) {
    paired_device_cache_.Put(mac_address, id);
    success = save_id_callback_(mac_address, id);
  }
  return success;
}

// call this function when a device is forgotten
bool MetricIdAllocator::ForgetDevice(const RawAddress& mac_address) {
  std::lock_guard<std::mutex> lock(id_allocator_mutex_);
  int id = 0;
  bool success = paired_device_cache_.Get(mac_address, &id);
  success &= paired_device_cache_.Remove(mac_address);
  if (success) {
    id_set_.erase(id);
    success = forget_device_callback_(mac_address, id);
  }
  return success;
}

}  // namespace common
}  // namespace bluetooth
+128 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright 2020 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#pragma once

#include <mutex>
#include <string>
#include <thread>
#include <unordered_set>
#include "raw_address.h"

#include "lru.h"

namespace bluetooth {

namespace common {

class MetricIdAllocator {
 public:
  using Callback = std::function<bool(const RawAddress& address, const int id)>;

  static const size_t kMaxNumUnpairedDevicesInMemory;
  static const size_t kMaxNumPairedDevicesInMemory;

  static const int kMinId;
  static const int kMaxId;

  ~MetricIdAllocator();

  /**
   * Get the instance of singleton
   *
   * @return MetricIdAllocator&
   */
  static MetricIdAllocator& GetInstance();

  /**
   * Initialize the allocator
   *
   * @param paired_device_map map from mac_address to id already saved
   * in the disk before init
   * @param save_id_callback a callback that will be called after successfully
   * saving id for a paired device
   * @param forget_device_callback a callback that will be called after
   * successful id deletion for forgotten device,
   * @return true if successfully initialized
   */
  bool Init(const std::unordered_map<RawAddress, int>& paired_device_map,
            Callback save_id_callback, Callback forget_device_callback);

  /**
   * Close the allocator. should be called when Bluetooth process is killed
   *
   * @return true if successfully close
   */
  bool Close();

  /**
   * Check if no id saved in memory
   *
   * @return true if no id is saved
   */
  bool IsEmpty() const;

  /**
   * Allocate an id for a scanned device, or return the id if there is already
   * one
   *
   * @param mac_address mac address of Bluetooth device
   * @return the id of device
   */
  int AllocateId(const RawAddress& mac_address);

  /**
   * Save the id for a paired device
   *
   * @param mac_address mac address of Bluetooth device
   * @return true if save successfully
   */
  bool SaveDevice(const RawAddress& mac_address);

  /**
   * Delete the id for a device to be forgotten
   *
   * @param mac_address mac address of Bluetooth device
   * @return true if delete successfully
   */
  bool ForgetDevice(const RawAddress& mac_address);

 protected:
  // Singleton
  MetricIdAllocator();

 private:
  static const std::string LOG_TAG;
  mutable std::mutex id_allocator_mutex_;

  LruCache<RawAddress, int> paired_device_cache_;
  LruCache<RawAddress, int> temporary_device_cache_;
  std::unordered_set<int> id_set_;

  int next_id_{kMinId};
  bool initialized_{false};
  Callback save_id_callback_;
  Callback forget_device_callback_;

  // delete copy constructor for singleton
  MetricIdAllocator(MetricIdAllocator const&) = delete;
  MetricIdAllocator& operator=(MetricIdAllocator const&) = delete;
};

}  // namespace common
}  // namespace bluetooth
Loading