1 // Copyright (C) 2020 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/external/StatsPullerManager.h"
16
17 #include <aidl/android/os/IPullAtomResultReceiver.h>
18 #include <aidl/android/util/StatsEventParcel.h>
19 #include <gmock/gmock.h>
20 #include <gtest/gtest.h>
21
22 #include <thread>
23
24 #include "stats_event.h"
25 #include "tests/statsd_test_util.h"
26
27 using aidl::android::util::StatsEventParcel;
28 using ::ndk::SharedRefBase;
29 using std::make_shared;
30 using std::shared_ptr;
31 using std::vector;
32
33 namespace android {
34 namespace os {
35 namespace statsd {
36
37 namespace {
38
39 int pullTagId1 = 10101;
40 int pullTagId2 = 10102;
41 int uid1 = 9999;
42 int uid2 = 8888;
43 ConfigKey configKey(50, 12345);
44 ConfigKey badConfigKey(60, 54321);
45 int unregisteredUid = 98765;
46 int64_t coolDownNs = NS_PER_SEC;
47 int64_t timeoutNs = NS_PER_SEC / 2;
48
createSimpleEvent(int32_t atomId,int32_t value)49 AStatsEvent* createSimpleEvent(int32_t atomId, int32_t value) {
50 AStatsEvent* event = AStatsEvent_obtain();
51 AStatsEvent_setAtomId(event, atomId);
52 AStatsEvent_writeInt32(event, value);
53 AStatsEvent_build(event);
54 return event;
55 }
56
57 class FakePullAtomCallback : public BnPullAtomCallback {
58 public:
FakePullAtomCallback(int32_t uid,int32_t pullDurationNs=0)59 FakePullAtomCallback(int32_t uid, int32_t pullDurationNs = 0)
60 : mUid(uid), mDurationNs(pullDurationNs) {};
onPullAtom(int atomTag,const shared_ptr<IPullAtomResultReceiver> & resultReceiver)61 Status onPullAtom(int atomTag,
62 const shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
63 onPullAtomCalled(atomTag);
64
65 vector<StatsEventParcel> parcels;
66 AStatsEvent* event = createSimpleEvent(atomTag, mUid);
67 size_t size;
68 uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
69
70 StatsEventParcel p;
71 // vector.assign() creates a copy, but this is inevitable unless
72 // stats_event.h/c uses a vector as opposed to a buffer.
73 p.buffer.assign(buffer, buffer + size);
74 parcels.push_back(std::move(p));
75 AStatsEvent_release(event);
76
77 if (mDurationNs > 0) {
78 std::this_thread::sleep_for(std::chrono::nanoseconds(mDurationNs));
79 }
80
81 resultReceiver->pullFinished(atomTag, /*success*/ true, parcels);
82 return Status::ok();
83 }
84 int32_t mUid;
85 int32_t mDurationNs;
86
onPullAtomCalled(int atomTag) const87 virtual void onPullAtomCalled(int atomTag) const {};
88 };
89
90 class FakePullUidProvider : public PullUidProvider {
91 public:
getPullAtomUids(int atomId)92 vector<int32_t> getPullAtomUids(int atomId) override {
93 if (atomId == pullTagId1) {
94 return {uid2, uid1};
95 } else if (atomId == pullTagId2) {
96 return {uid2};
97 }
98 return {};
99 }
100 };
101
102 class MockPullAtomCallback : public FakePullAtomCallback {
103 public:
MockPullAtomCallback(int32_t uid,int32_t pullDurationNs=0)104 MockPullAtomCallback(int32_t uid, int32_t pullDurationNs = 0)
105 : FakePullAtomCallback(uid, pullDurationNs) {
106 }
107
108 MOCK_METHOD(void, onPullAtomCalled, (int), (const override));
109 };
110
111 class MockPullDataReceiver : public PullDataReceiver {
112 public:
113 virtual ~MockPullDataReceiver() = default;
114
115 MOCK_METHOD(void, onDataPulled,
116 (const std::vector<std::shared_ptr<LogEvent>>&, PullResult, int64_t), (override));
117
isPullNeeded() const118 bool isPullNeeded() const override {
119 return true;
120 };
121 };
122
createPullerManagerAndRegister(int32_t pullDurationMs=0)123 sp<StatsPullerManager> createPullerManagerAndRegister(int32_t pullDurationMs = 0) {
124 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
125 shared_ptr<FakePullAtomCallback> cb1 =
126 SharedRefBase::make<FakePullAtomCallback>(uid1, pullDurationMs);
127 pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1);
128 shared_ptr<FakePullAtomCallback> cb2 =
129 SharedRefBase::make<FakePullAtomCallback>(uid2, pullDurationMs);
130 pullerManager->RegisterPullAtomCallback(uid2, pullTagId1, coolDownNs, timeoutNs, {}, cb2);
131 pullerManager->RegisterPullAtomCallback(uid1, pullTagId2, coolDownNs, timeoutNs, {}, cb1);
132 return pullerManager;
133 }
134 } // anonymous namespace
135
TEST(StatsPullerManagerTest,TestPullInvalidUid)136 TEST(StatsPullerManagerTest, TestPullInvalidUid) {
137 sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
138
139 vector<shared_ptr<LogEvent>> data;
140 EXPECT_FALSE(pullerManager->Pull(pullTagId1, {unregisteredUid}, /*timestamp =*/1, &data));
141 }
142
TEST(StatsPullerManagerTest,TestPullChoosesCorrectUid)143 TEST(StatsPullerManagerTest, TestPullChoosesCorrectUid) {
144 sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
145
146 vector<shared_ptr<LogEvent>> data;
147 EXPECT_TRUE(pullerManager->Pull(pullTagId1, {uid1}, /*timestamp =*/1, &data));
148 ASSERT_EQ(data.size(), 1);
149 EXPECT_EQ(data[0]->GetTagId(), pullTagId1);
150 ASSERT_EQ(data[0]->getValues().size(), 1);
151 EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid1);
152 }
153
TEST(StatsPullerManagerTest,TestPullInvalidConfigKey)154 TEST(StatsPullerManagerTest, TestPullInvalidConfigKey) {
155 sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
156 sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
157 pullerManager->RegisterPullUidProvider(configKey, uidProvider);
158
159 vector<shared_ptr<LogEvent>> data;
160 EXPECT_FALSE(pullerManager->Pull(pullTagId1, badConfigKey, /*timestamp =*/1, &data));
161 }
162
TEST(StatsPullerManagerTest,TestPullConfigKeyGood)163 TEST(StatsPullerManagerTest, TestPullConfigKeyGood) {
164 sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
165 sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
166 pullerManager->RegisterPullUidProvider(configKey, uidProvider);
167
168 vector<shared_ptr<LogEvent>> data;
169 EXPECT_TRUE(pullerManager->Pull(pullTagId1, configKey, /*timestamp =*/1, &data));
170 EXPECT_EQ(data[0]->GetTagId(), pullTagId1);
171 ASSERT_EQ(data[0]->getValues().size(), 1);
172 EXPECT_EQ(data[0]->getValues()[0].mValue.int_value, uid2);
173 }
174
TEST(StatsPullerManagerTest,TestPullConfigKeyNoPullerWithUid)175 TEST(StatsPullerManagerTest, TestPullConfigKeyNoPullerWithUid) {
176 sp<StatsPullerManager> pullerManager = createPullerManagerAndRegister();
177 sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
178 pullerManager->RegisterPullUidProvider(configKey, uidProvider);
179
180 vector<shared_ptr<LogEvent>> data;
181 EXPECT_FALSE(pullerManager->Pull(pullTagId2, configKey, /*timestamp =*/1, &data));
182 }
183
TEST(StatsPullerManagerTest,TestSameAtomIsPulledInABatch)184 TEST(StatsPullerManagerTest, TestSameAtomIsPulledInABatch) {
185 // define 2 puller callbacks with small duration each to guaranty that
186 // call sequence callback A + callback B will invalidate pull cache
187 // for callback A if PullerManager does not group receivers by tagId
188
189 const int64_t pullDurationNs = (int)(timeoutNs * 0.9);
190
191 sp<StatsPullerManager> pullerManager = new StatsPullerManager();
192 auto cb1 = SharedRefBase::make<StrictMock<MockPullAtomCallback>>(uid1, pullDurationNs);
193 pullerManager->RegisterPullAtomCallback(uid1, pullTagId1, coolDownNs, timeoutNs, {}, cb1);
194 auto cb2 = SharedRefBase::make<StrictMock<MockPullAtomCallback>>(uid2, pullDurationNs);
195 pullerManager->RegisterPullAtomCallback(uid2, pullTagId2, coolDownNs, timeoutNs, {}, cb2);
196
197 sp<FakePullUidProvider> uidProvider = new FakePullUidProvider();
198 pullerManager->RegisterPullUidProvider(configKey, uidProvider);
199
200 const int64_t bucketBoundary = NS_PER_SEC * 60 * 60 * 1; // 1 hour
201
202 // create 10 receivers to simulate 10 distinct metrics for pulled atoms
203 // add 10 metric where 5 depends on atom A and 5 on atom B
204 vector<sp<MockPullDataReceiver>> receivers;
205 receivers.reserve(10);
206 for (int i = 0; i < 10; i++) {
207 auto receiver = new StrictMock<MockPullDataReceiver>();
208 EXPECT_CALL(*receiver, onDataPulled(_, _, _)).Times(1);
209 receivers.push_back(receiver);
210
211 const int32_t atomTag = i % 2 == 0 ? pullTagId1 : pullTagId2;
212 pullerManager->RegisterReceiver(atomTag, configKey, receiver, bucketBoundary,
213 bucketBoundary);
214 }
215
216 // check that only 2 pulls will be done and remaining 8 pulls from cache
217 EXPECT_CALL(*cb1, onPullAtomCalled(pullTagId1)).Times(1);
218 EXPECT_CALL(*cb2, onPullAtomCalled(pullTagId2)).Times(1);
219
220 // validate that created 2 receivers groups just for 2 atoms with 5 receivers in each
221 ASSERT_EQ(pullerManager->mReceivers.size(), 2);
222 ASSERT_EQ(pullerManager->mReceivers.begin()->second.size(), 5);
223 ASSERT_EQ(pullerManager->mReceivers.rbegin()->second.size(), 5);
224
225 // simulate pulls
226 pullerManager->OnAlarmFired(bucketBoundary + 1);
227
228 // to allow async pullers to complete + some extra time
229 std::this_thread::sleep_for(std::chrono::nanoseconds(pullDurationNs * 3));
230 }
231
232 } // namespace statsd
233 } // namespace os
234 } // namespace android
235