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

Commit 2393f461 authored by Chris Manton's avatar Chris Manton
Browse files

Introduce synchronized map counters

Bug: 188074901
Tag: #refactor
Test: gd/cert/run
BYPASS_LONG_LINES_REASON: Bluetooth likes 120 lines

Change-Id: Ie436f56cb167bd52eb795e6f379088c27b5f09c9
parent 04fd0b45
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -20,17 +20,18 @@ filegroup {
filegroup {
    name: "BluetoothCommonTestSources",
    srcs: [
        "blocking_queue_unittest.cc",
        "bidi_queue_unittest.cc",
        "blocking_queue_unittest.cc",
        "byte_array_test.cc",
        "circular_buffer_test.cc",
        "observer_registry_test.cc",
        "init_flags_test.cc",
        "list_map_test.cc",
        "lru_cache_test.cc",
        "metric_id_manager_unittest.cc",
        "multi_priority_queue_test.cc",
        "numbers_test.cc",
        "observer_registry_test.cc",
        "strings_test.cc",
        "sync_map_count_test.cc",
    ],
}
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 The Android Open Source Project
 *
 * 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 <cstddef>
#include <map>
#include <mutex>
#include <unordered_map>
#include <vector>

template <typename T>
class SyncMapCount {
 public:
  struct Item {
    T item;
    size_t count;
  };

 private:
  std::map<const T, std::size_t> map_;
  size_t max_size_{SIZE_MAX};
  mutable std::mutex mutex_;

  std::vector<Item> Vectorize() const {
    std::vector<Item> vec;
    for (auto& it : this->Get()) {
      vec.push_back(Item{it.first, it.second});
    }
    return vec;
  }

  std::vector<Item> GetSorted(std::function<bool(const Item& a, const Item& b)> sort_func) const {
    std::vector<Item> vec = Vectorize();
    sort(vec.begin(), vec.end(), [=](const Item& a, const Item& b) -> bool { return sort_func(a, b); });
    return vec;
  }

 public:
  SyncMapCount() : max_size_(SIZE_MAX) {}
  explicit SyncMapCount(size_t max_size) : max_size_(max_size) {}
  ~SyncMapCount() = default;

  void Put(const T item) {
    std::unique_lock<std::mutex> lock(mutex_);
    if (map_.size() == max_size_) return;
    (map_.count(item) > 0) ? map_[item] += 1 : map_[item] = 1;
  }

  std::map<const T, std::size_t> Get() const {
    std::unique_lock<std::mutex> lock(mutex_);
    return map_;
  }

  std::size_t Size() const {
    std::unique_lock<std::mutex> lock(mutex_);
    return map_.size();
  }

  void Clear() {
    std::unique_lock<std::mutex> lock(mutex_);
    map_.clear();
  }

  std::vector<Item> GetSortedHighToLow() const {
    return GetSorted([](const Item& a, const Item& b) -> bool { return a.count > b.count; });
  }

  std::vector<Item> GetSortedLowToHigh() const {
    return GetSorted([](const Item& a, const Item& b) -> bool { return a.count < b.count; });
  }
};
+156 −0
Original line number Diff line number Diff line
/*
 * Copyright 2021 The Android Open Source Project
 *
 * 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 "common/sync_map_count.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <cstddef>
#include <cstring>
#include <vector>

#include "os/log.h"

namespace testing {

const char* data[] = {
    "One",
    "Two",
    "Two",
    "Three",
    "Three",
    "Three",
    "AAA",
    "ZZZ",
    nullptr,
};

namespace {
void LoadStringMap(SyncMapCount<std::string>& map) {
  for (const char** p = data; *p != nullptr; p++) {
    map.Put(*p);
  }
}
}  // namespace

TEST(SyncMapCount, simple) {
  SyncMapCount<std::string> map;
  LoadStringMap(map);

  ASSERT_EQ(5, map.Size());

  auto m = map.Get();
  ASSERT_EQ(3, m["Three"]);
  ASSERT_EQ(2, m["Two"]);
  ASSERT_EQ(1, m["One"]);
}

TEST(SyncMapCount, sized) {
  SyncMapCount<std::string> map(2);
  LoadStringMap(map);

  ASSERT_EQ(2, map.Size());
}

TEST(SyncMapCount, sorted_string_value_low_to_high) {
  SyncMapCount<std::string> map;
  LoadStringMap(map);

  auto entries = map.GetSortedLowToHigh();
  ASSERT_EQ(3, entries[entries.size() - 1].count);
  ASSERT_EQ(2, entries[entries.size() - 2].count);
}

TEST(SyncMapCount, sorted_string_value_high_to_low) {
  SyncMapCount<std::string> map;
  LoadStringMap(map);

  auto entries = map.GetSortedHighToLow();
  ASSERT_EQ(3, entries[0].count);
  ASSERT_EQ(2, entries[1].count);
}

struct TestString {
  TestString(std::string string) : string_(string) {}
  std::string String() const {
    return string_;
  }

  bool operator<(const TestString& other) const {
    return (other.string_ > string_);
  }
  bool operator==(const TestString& other) const {
    return (other.string_ == string_);
  }

 private:
  std::string string_;
};

namespace {
void LoadTestStringMap(SyncMapCount<TestString>& map) {
  for (const char** p = data; *p != nullptr; p++) {
    map.Put(TestString(*p));
  }
}
}  // namespace

TEST(SyncMapCount, simple_struct) {
  SyncMapCount<TestString> map;
  LoadTestStringMap(map);

  ASSERT_EQ(5, map.Size());

  auto m = map.Get();
  ASSERT_EQ(3, m[TestString("Three")]);
  ASSERT_EQ(2, m[TestString("Two")]);
  ASSERT_EQ(1, m[TestString("One")]);
}

TEST(SyncMapCount, sorted_string_struct_value_low_to_high) {
  SyncMapCount<TestString> map;
  LoadTestStringMap(map);

  auto entries = map.GetSortedLowToHigh();
  ASSERT_EQ(3, entries[entries.size() - 1].count);
  ASSERT_EQ(2, entries[entries.size() - 2].count);
}

TEST(SyncMapCount, sorted_string_struct_value_high_to_low) {
  SyncMapCount<TestString> map;
  LoadTestStringMap(map);

  auto entries = map.GetSortedHighToLow();
  ASSERT_EQ(3, entries[0].count);
  ASSERT_EQ(2, entries[1].count);
}

TEST(SyncMapCount, locked_for_map_copy) {
  SyncMapCount<TestString> map;
  LoadTestStringMap(map);

  ASSERT_EQ(5, map.Size());
  std::vector<SyncMapCount<TestString>::Item> vec;
  for (auto& it : map.Get()) {
    map.Clear();
    vec.push_back(SyncMapCount<TestString>::Item{it.first, it.second});
  }
  ASSERT_EQ(0, map.Size());
  ASSERT_EQ(5, vec.size());
}

}  // namespace testing