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 #include <binder/ProcessState.h>
18 #include <gmock/gmock.h>
19 #include <gtest/gtest.h>
20 #include <gtest_matchers.h>
21 #include <stats_subscription.h>
22 #include <stdint.h>
23 #include <utils/Looper.h>
24
25 #include <chrono>
26 #include <string>
27 #include <thread>
28 #include <vector>
29
30 #include "packages/modules/StatsD/statsd/src/shell/shell_config.pb.h"
31 #include "packages/modules/StatsD/statsd/src/shell/shell_data.pb.h"
32 #include "statslog_statsdtest.h"
33
34 #ifdef __ANDROID__
35
36 using namespace testing;
37 using android::Looper;
38 using android::ProcessState;
39 using android::sp;
40 using android::os::statsd::Atom;
41 using android::os::statsd::ShellData;
42 using android::os::statsd::ShellSubscription;
43 using android::os::statsd::TestAtomReported_State_OFF;
44 using android::os::statsd::TrainExperimentIds;
45 using android::os::statsd::util::BytesField;
46 using android::os::statsd::util::SCREEN_BRIGHTNESS_CHANGED;
47 using android::os::statsd::util::stats_write;
48 using android::os::statsd::util::TEST_ATOM_REPORTED;
49 using android::os::statsd::util::TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF;
50 using std::string;
51 using std::vector;
52 using std::this_thread::sleep_for;
53
54 namespace {
55
56 class SubscriptionTest : public Test {
57 public:
SubscriptionTest()58 SubscriptionTest() : looper(Looper::prepare(/*opts=*/0)) {
59 const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
60 ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
61
62 *trainExpIds.mutable_experiment_id() = {expIds.begin(), expIds.end()};
63 trainExpIds.SerializeToString(&trainExpIdsBytes);
64 }
65
~SubscriptionTest()66 ~SubscriptionTest() {
67 const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
68 ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
69 }
70
71 protected:
SetUp()72 void SetUp() override {
73 // Start the Binder thread pool.
74 ProcessState::self()->startThreadPool();
75 }
76
TearDown()77 void TearDown() {
78 // Clear any dangling subscriptions from statsd.
79 if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
80 AStatsManager_removeSubscription(subId);
81 }
82 }
83
LogTestAtomReported(int32_t intFieldValue)84 void LogTestAtomReported(int32_t intFieldValue) __INTRODUCED_IN(__ANDROID_API_T__) {
85 const BytesField bytesField(trainExpIdsBytes.data(), trainExpIdsBytes.size());
86 stats_write(TEST_ATOM_REPORTED, uids.data(), uids.size(), tags, intFieldValue,
87 /*long_field=*/2LL, /*float_field=*/3.0F,
88 /*string_field=*/string1.c_str(),
89 /*boolean_field=*/false,
90 /*state=*/TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF, bytesField,
91 repeatedInts, repeatedLongs, repeatedFloats, repeatedStrings,
92 &(repeatedBool[0]), /*repeatedBoolSize=*/2, repeatedEnums);
93 }
94
95 int32_t subId;
96
97 // TestAtomReported fields.
98 const vector<int32_t> uids = {1};
99 const string tag = "test";
100 const vector<char const*> tags = {tag.c_str()};
101
102 // 100 int64s for the MODE_BYTES field to push atom size to over 1K.
103 const vector<int64_t> expIds = vector<int64_t>(100, INT64_MAX);
104
105 const vector<int32_t> repeatedInts{1};
106 const vector<int64_t> repeatedLongs{2LL};
107 const vector<float> repeatedFloats{3.0F};
108 const string string1 = "ABC";
109 const vector<char const*> repeatedStrings = {string1.c_str()};
110 const bool repeatedBool[2] = {false, true};
111 const vector<int32_t> repeatedEnums = {TEST_ATOM_REPORTED__REPEATED_ENUM_FIELD__OFF};
112 TrainExperimentIds trainExpIds;
113 string trainExpIdsBytes;
114
115 private:
116 sp<Looper> looper;
117 };
118
119 // Stores arguments passed in subscription callback.
120 struct CallbackData {
121 int32_t subId;
122 AStatsManager_SubscriptionCallbackReason reason;
123 vector<uint8_t> payload;
124 int count; // Stores number of times the callback is invoked.
125 };
126
callback(int32_t subscription_id,AStatsManager_SubscriptionCallbackReason reason,uint8_t * _Nonnull payload,size_t num_bytes,void * _Nullable cookie)127 static void callback(int32_t subscription_id, AStatsManager_SubscriptionCallbackReason reason,
128 uint8_t* _Nonnull payload, size_t num_bytes, void* _Nullable cookie) {
129 CallbackData* data = static_cast<CallbackData*>(cookie);
130 data->subId = subscription_id;
131 data->reason = reason;
132 data->payload.assign(payload, payload + num_bytes);
133 data->count++;
134 }
135
136 constexpr static int WAIT_MS = 500;
137
TEST_F(SubscriptionTest,TestSubscription)138 TEST_F(SubscriptionTest, TestSubscription) {
139 if (__builtin_available(android __STATSD_SUBS_MIN_API__, *)) {
140 ShellSubscription config;
141 config.add_pushed()->set_atom_id(TEST_ATOM_REPORTED);
142 config.add_pushed()->set_atom_id(SCREEN_BRIGHTNESS_CHANGED);
143
144 string configBytes;
145 config.SerializeToString(&configBytes);
146
147 CallbackData callbackData{/*subId=*/0,
148 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED,
149 /*payload=*/{},
150 /*count=*/0};
151
152 // Add subscription.
153 subId = AStatsManager_addSubscription(reinterpret_cast<const uint8_t*>(configBytes.data()),
154 configBytes.size(), &callback, &callbackData);
155 ASSERT_GT(subId, 0);
156 sleep_for(std::chrono::milliseconds(WAIT_MS));
157
158 // Log events without exceeding statsd cache.
159 stats_write(SCREEN_BRIGHTNESS_CHANGED, 100);
160 LogTestAtomReported(1);
161 sleep_for(std::chrono::milliseconds(WAIT_MS));
162
163 // Verify no callback occurred yet.
164 EXPECT_EQ(callbackData.subId, 0);
165 EXPECT_EQ(callbackData.reason,
166 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
167 EXPECT_EQ(callbackData.count, 0);
168 ASSERT_TRUE(callbackData.payload.empty());
169
170 // Log another TestAtomReported to overflow cache.
171 LogTestAtomReported(2);
172 sleep_for(std::chrono::milliseconds(WAIT_MS));
173
174 // Verify callback occurred.
175 EXPECT_EQ(callbackData.subId, subId);
176 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
177 EXPECT_EQ(callbackData.count, 1);
178 ASSERT_GT(callbackData.payload.size(), 0);
179
180 ShellData actualShellData;
181 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
182 callbackData.payload.size()));
183
184 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 3);
185 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
186
187 ASSERT_GE(actualShellData.atom_size(), 3);
188
189 // Verify atom 1.
190 Atom expectedAtom;
191 expectedAtom.mutable_screen_brightness_changed()->set_level(100);
192 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
193
194 // Verify atom 2.
195 expectedAtom.Clear();
196 auto* testAtomReported = expectedAtom.mutable_test_atom_reported();
197 auto* attributionNode = testAtomReported->add_attribution_node();
198 attributionNode->set_uid(uids[0]);
199 attributionNode->set_tag(tag);
200 testAtomReported->set_int_field(1);
201 testAtomReported->set_long_field(2LL);
202 testAtomReported->set_float_field(3.0F);
203 testAtomReported->set_string_field(string1);
204 testAtomReported->set_boolean_field(false);
205 testAtomReported->set_state(TestAtomReported_State_OFF);
206 *testAtomReported->mutable_bytes_field() = trainExpIds;
207 *testAtomReported->mutable_repeated_int_field() = {repeatedInts.begin(),
208 repeatedInts.end()};
209 *testAtomReported->mutable_repeated_long_field() = {repeatedLongs.begin(),
210 repeatedLongs.end()};
211 *testAtomReported->mutable_repeated_float_field() = {repeatedFloats.begin(),
212 repeatedFloats.end()};
213 *testAtomReported->mutable_repeated_string_field() = {repeatedStrings.begin(),
214 repeatedStrings.end()};
215 *testAtomReported->mutable_repeated_boolean_field() = {&repeatedBool[0],
216 &repeatedBool[0] + 2};
217 *testAtomReported->mutable_repeated_enum_field() = {repeatedEnums.begin(),
218 repeatedEnums.end()};
219 EXPECT_THAT(actualShellData.atom(1), EqAtom(expectedAtom));
220
221 // Verify atom 3.
222 testAtomReported->set_int_field(2);
223 EXPECT_THAT(actualShellData.atom(2), EqAtom(expectedAtom));
224
225 // Log another ScreenBrightnessChanged atom. No callback should occur.
226 stats_write(SCREEN_BRIGHTNESS_CHANGED, 99);
227 sleep_for(std::chrono::milliseconds(WAIT_MS));
228 EXPECT_EQ(callbackData.count, 1);
229
230 // Flush subscription. Callback should occur.
231 AStatsManager_flushSubscription(subId);
232 sleep_for(std::chrono::milliseconds(WAIT_MS));
233
234 EXPECT_EQ(callbackData.subId, subId);
235 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_FLUSH_REQUESTED);
236 EXPECT_EQ(callbackData.count, 2);
237 ASSERT_GT(callbackData.payload.size(), 0);
238
239 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
240 callbackData.payload.size()));
241
242 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
243 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
244
245 ASSERT_GE(actualShellData.atom_size(), 1);
246
247 // Verify atom 1.
248 expectedAtom.Clear();
249 expectedAtom.mutable_screen_brightness_changed()->set_level(99);
250 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
251
252 // Log another ScreenBrightnessChanged atom. No callback should occur.
253 stats_write(SCREEN_BRIGHTNESS_CHANGED, 98);
254 sleep_for(std::chrono::milliseconds(WAIT_MS));
255 EXPECT_EQ(callbackData.count, 2);
256
257 // Trigger callback through cache timeout.
258 // Two 500 ms sleeps have occurred already so the total sleep is 71000 ms since last
259 // callback invocation.
260 sleep_for(std::chrono::milliseconds(70'000));
261 EXPECT_EQ(callbackData.subId, subId);
262 EXPECT_EQ(callbackData.reason, ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_STATSD_INITIATED);
263 EXPECT_EQ(callbackData.count, 3);
264 ASSERT_GT(callbackData.payload.size(), 0);
265
266 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
267 callbackData.payload.size()));
268
269 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
270 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
271
272 ASSERT_GE(actualShellData.atom_size(), 1);
273
274 // Verify atom 1.
275 expectedAtom.Clear();
276 expectedAtom.mutable_screen_brightness_changed()->set_level(98);
277 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
278
279 // Log another ScreenBrightnessChanged atom. No callback should occur.
280 stats_write(SCREEN_BRIGHTNESS_CHANGED, 97);
281 sleep_for(std::chrono::milliseconds(WAIT_MS));
282 EXPECT_EQ(callbackData.count, 3);
283
284 // End subscription. Final callback should occur.
285 AStatsManager_removeSubscription(subId);
286 sleep_for(std::chrono::milliseconds(WAIT_MS));
287
288 EXPECT_EQ(callbackData.subId, subId);
289 EXPECT_EQ(callbackData.reason,
290 ASTATSMANAGER_SUBSCRIPTION_CALLBACK_REASON_SUBSCRIPTION_ENDED);
291 EXPECT_EQ(callbackData.count, 4);
292 ASSERT_GT(callbackData.payload.size(), 0);
293
294 ASSERT_TRUE(actualShellData.ParseFromArray(callbackData.payload.data(),
295 callbackData.payload.size()));
296
297 ASSERT_GE(actualShellData.elapsed_timestamp_nanos_size(), 1);
298 EXPECT_THAT(actualShellData.elapsed_timestamp_nanos(), Each(Gt(0LL)));
299
300 ASSERT_GE(actualShellData.atom_size(), 1);
301
302 // Verify atom 1.
303 expectedAtom.Clear();
304 expectedAtom.mutable_screen_brightness_changed()->set_level(97);
305 EXPECT_THAT(actualShellData.atom(0), EqAtom(expectedAtom));
306 } else {
307 GTEST_SKIP();
308 }
309 }
310
311 } // namespace
312
313 #else
314 GTEST_LOG_(INFO) << "This test does nothing.\n";
315 #endif
316