1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <gtest/gtest_prod.h>
20 
21 #include <atomic>
22 #include <mutex>
23 #include <unordered_map>
24 #include <unordered_set>
25 
26 namespace android {
27 namespace os {
28 namespace statsd {
29 
30 /**
31  * Templating is for benchmarks only
32  *
33  * Based on benchmarks the more fast container to be used for atom ids filtering
34  * is unordered_set<int>
35  * #BM_LogEventFilterUnorderedSet                       391208 ns     390086 ns         1793
36  * #BM_LogEventFilterUnorderedSet2Consumers            1293527 ns    1289326 ns          543
37  * #BM_LogEventFilterSet                                613362 ns     611259 ns         1146
38  * #BM_LogEventFilterSet2Consumers                     1859397 ns    1854193 ns          378
39  *
40  * See @LogEventFilter definition below
41  */
42 template <typename T>
43 class LogEventFilterGeneric {
44 public:
45     virtual ~LogEventFilterGeneric() = default;
46 
setFilteringEnabled(bool isEnabled)47     virtual void setFilteringEnabled(bool isEnabled) {
48         mLogsFilteringEnabled = isEnabled;
49     }
50 
getFilteringEnabled()51     bool getFilteringEnabled() const {
52         return mLogsFilteringEnabled;
53     }
54 
55     /**
56      * @brief Tests atom id with list of interesting atoms
57      *        If Logs filtering is disabled - assume all atoms in use
58      *        Most of the time should be non-blocking call - only in case when setAtomIds() was
59      *        called the call will be blocking due to atom list needs to be synced up
60      * @param atomId
61      * @return true if atom is used by any of consumer or filtering is disabled
62      */
isAtomInUse(int atomId)63     virtual bool isAtomInUse(int atomId) const {
64         if (!mLogsFilteringEnabled) {
65             return true;
66         }
67 
68         // check if there is an updated set of interesting atom ids
69         if (mLocalSetUpdateCounter != mSetUpdateCounter.load(std::memory_order_relaxed)) {
70             std::lock_guard<std::mutex> guard(mTagIdsMutex);
71             mLocalSetUpdateCounter = mSetUpdateCounter.load(std::memory_order_relaxed);
72             mLocalTagIds.swap(mTagIds);
73         }
74         return mLocalTagIds.find(atomId) != mLocalTagIds.end();
75     }
76 
77     typedef const void* ConsumerId;
78 
79     typedef T AtomIdSet;
80     /**
81      * @brief Set the Atom Ids object
82      *
83      * @param tagIds set of atoms ids
84      * @param consumer used to differentiate the consumers to form proper superset of ids
85      */
setAtomIds(AtomIdSet tagIds,ConsumerId consumer)86     virtual void setAtomIds(AtomIdSet tagIds, ConsumerId consumer) {
87         std::lock_guard lock(mTagIdsMutex);
88         // update ids list from consumer
89         if (tagIds.size() == 0) {
90             mTagIdsPerConsumer.erase(consumer);
91         } else {
92             mTagIdsPerConsumer[consumer].swap(tagIds);
93         }
94         // populate the superset incorporating list of distinct atom ids from all consumers
95         mTagIds.clear();
96         for (const auto& [_, atomIds] : mTagIdsPerConsumer) {
97             mTagIds.insert(atomIds.begin(), atomIds.end());
98         }
99         mSetUpdateCounter.fetch_add(1, std::memory_order_relaxed);
100     }
101 
102 private:
103     std::atomic_bool mLogsFilteringEnabled = false;
104     std::atomic_int mSetUpdateCounter;
105     mutable int mLocalSetUpdateCounter;
106 
107     mutable std::mutex mTagIdsMutex;
108     std::unordered_map<ConsumerId, AtomIdSet> mTagIdsPerConsumer;
109     mutable AtomIdSet mTagIds;
110     mutable AtomIdSet mLocalTagIds;
111 
112     friend class LogEventFilterTest;
113 
114     FRIEND_TEST(LogEventFilterTest, TestEmptyFilter);
115     FRIEND_TEST(LogEventFilterTest, TestRemoveNonExistingEmptyFilter);
116     FRIEND_TEST(LogEventFilterTest, TestEmptyFilterDisabled);
117     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterFullOverlap);
118     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterPartialOverlap);
119     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabled);
120     FRIEND_TEST(LogEventFilterTest, TestNonEmptyFilterDisabledPartialOverlap);
121     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIds);
122     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerOverlapIdsRemoved);
123     FRIEND_TEST(LogEventFilterTest, TestMultipleConsumerEmptyFilter);
124 };
125 
126 typedef LogEventFilterGeneric<std::unordered_set<int>> LogEventFilter;
127 
128 }  // namespace statsd
129 }  // namespace os
130 }  // namespace android
131